Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ Use tscircuit 0.0.2745 or newer, which exports these attributes as `is_output`,
`is_using_internal_pullup`, and `is_configured_for_i2c_sda` / `is_configured_for_i2c_scl`.
Capability flags such as `isGpio` alone do not select a pin function. Connected
pins with missing or unsupported functions produce an error identifying the
MCU, physical pins, source ports and required TSX attributes. Missing MCU port
MCU name/part number, physical pin numbers/labels and required TSX attributes,
without generated record IDs or a converter stack trace. Missing MCU port
records or traces referencing absent source ports also fail before a new
SysConfig file is written; no behavior is inferred from signal names.
If more than one supported MCU is present, select one with `--component U1`
Expand Down
37 changes: 21 additions & 16 deletions cli/sysconfig/convert.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,25 @@ if (!circuitJsonPath || !optionsPath || !outputPath || !configurationPath) {
);
}

const circuitJson = JSON.parse(await readFile(circuitJsonPath, "utf8"));
const options = JSON.parse(await readFile(optionsPath, "utf8"));
const converter = new CircuitJsonToSysConfigConverter(circuitJson, options);
converter.runUntilFinished();
const configuration = converter.getResolvedConfiguration();
const cc2340 = configuration.cc2340;
const converterOptions = getValidationOptions({ configuration, circuitJson });
try {
const circuitJson = JSON.parse(await readFile(circuitJsonPath, "utf8"));
const options = JSON.parse(await readFile(optionsPath, "utf8"));
const converter = new CircuitJsonToSysConfigConverter(circuitJson, options);
converter.runUntilFinished();
const configuration = converter.getResolvedConfiguration();
const cc2340 = configuration.cc2340;
const converterOptions = getValidationOptions({ configuration, circuitJson });

await writeFile(outputPath, converter.getOutput().getString());
await writeFile(
configurationPath,
JSON.stringify({
target: cc2340 ? "cc2340" : "am2434",
component: configuration.component,
converterOptions,
}),
);
await writeFile(outputPath, converter.getOutput().getString());
await writeFile(
configurationPath,
JSON.stringify({
target: cc2340 ? "cc2340" : "am2434",
component: configuration.component,
converterOptions,
}),
);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
}
3 changes: 2 additions & 1 deletion npm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ ti check-sysconfig ./board.circuit.tsx
Use tscircuit 0.0.2745 or newer for TSX export. For supported CC2340 circuits, GPIO/I2C choices come from existing MCU
`pinAttributes` exported to Circuit JSON. No separate request JSON is required
or read implicitly. Missing connected-pin functions fail with physical pin and
source-port details and the required TSX attributes. Missing MCU port records
signal labels and the required TSX attributes, without generated record IDs
or a converter stack trace. Missing MCU port records
or connected source-port records also fail before a new `.syscfg` is written.
Use `--component U1` when selecting between multiple MCUs.
Connected 32768 Hz two-terminal crystals on MCU pins 14/15 select the external
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"snapshot:update": "tsci snapshot --update",
"start": "tsci dev",
"test": "bun test",
"test:cli": "node --test tests/cli/*.test.mjs && bun test tests/cli/import-snapshot.test.tsx",
"test:cli": "node --test tests/cli/*.test.mjs && bun test tests/cli/import-snapshot.test.tsx tests/cli/sysconfig-errors-snapshot.test.ts",
"typecheck": "tsc --noEmit",
"format": "biome format --write",
"format:check": "biome format .",
Expand All @@ -35,7 +35,7 @@
"@types/bun": "^1.4.0",
"@types/react": "^19.2.17",
"bun-match-svg": "^0.0.17",
"circuit-json-to-sysconfig": "github:tscircuit/circuit-json-to-sysconfig#cc3e6f3fa7fbfab99bf60db1c19ca1858995bdd0",
"circuit-json-to-sysconfig": "github:tscircuit/circuit-json-to-sysconfig#3da9ac4b464a84eac13615dc684da7105065c685",
"circuit-to-svg": "0.0.410",
"tscircuit": "0.0.2745",
"typescript": "^5.0.0"
Expand Down
12 changes: 11 additions & 1 deletion scripts/test-npm-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,17 @@ async function testCli({ command, cwd }) {
args: ["generate-sysconfig", "incomplete-pins.circuit.json"],
cwd,
}),
/input_trace: missing source_port missing_input/,
(error) => {
assert.match(
error.message,
/Incomplete Circuit JSON pin records: 1 connection\(s\) reference missing pins[\s\S]*Rebuild the circuit/,
);
assert.doesNotMatch(
error.message,
/input_trace|missing_input|throw new Error|\bat \S+ \(/,
);
return true;
},
);
await assert.rejects(readFile(join(cwd, "incomplete-pins.syscfg")), {
code: "ENOENT",
Expand Down
130 changes: 130 additions & 0 deletions tests/cli/sysconfig-errors-snapshot.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { expect, test } from "bun:test";
import { spawnSync } from "node:child_process";
import {
existsSync,
mkdtempSync,
rmSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import type { CircuitJson } from "circuit-json";

function generateSysconfig({
inputName,
source,
}: {
inputName: string;
source: string;
}) {
const cwd = mkdtempSync(join(tmpdir(), "ti-sysconfig-snapshot-"));
try {
symlinkSync(
new URL("../../node_modules/", import.meta.url),
join(cwd, "node_modules"),
"junction",
);
writeFileSync(
join(cwd, "package.json"),
'{"private":true,"type":"module"}',
);
writeFileSync(join(cwd, inputName), source);
const result = spawnSync(
"node",
[
fileURLToPath(new URL("../../cli/ti.mjs", import.meta.url)),
"generate-sysconfig",
inputName,
],
{ cwd, encoding: "utf8" },
);
if (result.error) throw result.error;
return {
status: result.status,
stdout: result.stdout,
stderr: result.stderr,
sysconfigWritten: existsSync(join(cwd, "board.syscfg")),
};
} finally {
rmSync(cwd, { recursive: true, force: true });
}
}

test("Circuit JSON error output names unresolved pins without IDs or a stack", () => {
const circuitJson: CircuitJson = [
{
type: "source_component",
ftype: "simple_chip",
source_component_id: "source_component_123",
name: "U1",
manufacturer_part_number: "CC2340R52E0RGER",
},
{
type: "source_port",
source_component_id: "source_component_123",
source_port_id: "source_port_456",
name: "DIO11",
pin_number: 4,
is_gpio: true,
},
{
type: "source_port",
source_component_id: "source_component_123",
source_port_id: "source_port_789",
name: "DIO13",
pin_number: 6,
is_gpio: true,
},
{
type: "source_trace",
source_trace_id: "source_trace_321",
connected_source_port_ids: ["source_port_456", "source_port_789"],
connected_source_net_ids: [],
},
];
const { stderr, ...result } = generateSysconfig({
inputName: "board.circuit.json",
source: JSON.stringify(circuitJson),
});
expect(result).toEqual({ status: 1, stdout: "", sysconfigWritten: false });
expect(stderr).toMatchInlineSnapshot(`
"Failed to generate SysConfig: SysConfig conversion failed:
Unresolved CC2340 pin configuration for U1 (CC2340R52E0RGER):
- U1 pin 4 (DIO11): no GPIO direction or supported peripheral selected
- U1 pin 6 (DIO13): no GPIO direction or supported peripheral selected
Update U1's TSX pinAttributes with the intended function for each listed pin: set exactly one of isInput: true or isOutput: true for GPIO, or activeCapability: "i2c_sda" / "i2c_scl" for I2C.
Datasheet capabilities such as isGpio describe what a pin supports; they do not select how this board uses it.
"
`);
});

test("TSX error output explains board choices without a request file or stack", () => {
const { stderr, ...result } = generateSysconfig({
inputName: "board.circuit.tsx",
source: `export default () => (
<board width="10mm" height="10mm">
<chip name="U1" manufacturerPartNumber="CC2340R52E0RGER"
pinLabels={{ pin4: "DIO11", pin6: "DIO13" }}
pinAttributes={{
pin4: { isGpio: true },
pin6: { isGpio: true },
}} />
<net name="PMIC_LP" /><net name="CHARGER_INT" />
<trace from=".U1 > .DIO11" to="net.PMIC_LP" />
<trace from=".U1 > .DIO13" to="net.CHARGER_INT" />
</board>
);`,
});
expect(result).toEqual({ status: 1, stdout: "", sysconfigWritten: false });
expect(stderr).toMatchInlineSnapshot(`
"Failed to generate SysConfig: SysConfig conversion failed:
Unresolved CC2340 pin configuration for U1 (CC2340R52E0RGER):
- U1 pin 4 (DIO11): no GPIO direction or supported peripheral selected
- U1 pin 6 (DIO13): no GPIO direction or supported peripheral selected
Update U1's TSX pinAttributes with the intended function for each listed pin: set exactly one of isInput: true or isOutput: true for GPIO, or activeCapability: "i2c_sda" / "i2c_scl" for I2C.
Datasheet capabilities such as isGpio describe what a pin supports; they do not select how this board uses it.
"
`);
});
44 changes: 37 additions & 7 deletions tests/cli/sysconfig-from-circuit.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,13 @@ test("connected pins with missing roles fail without a request-file suggestion",
writeFileSync(join(f.cwd, "board.circuit.json"), JSON.stringify(incomplete));
assert.equal(await f.run(["generate-sysconfig", "board.circuit.json"]), 1);
assert.match(f.stderr.join("\n"), /U1.*CC2340R52E0RGER/);
assert.match(f.stderr.join("\n"), /pin 4.*pinAttributes/);
assert.doesNotMatch(f.stderr.join("\n"), /request file is missing/);
assert.match(f.stderr.join("\n"), /pin 4[\s\S]*pinAttributes/);
assert.doesNotMatch(
f.stderr.join("\n"),
/request file is missing|source_component_id|source_port_id|\bat \S+|throw new Error|convert\.mjs:\d/,
);
assert.doesNotMatch(f.stderr.join("\n"), /\(mcu,|\(output,/);
assert.match(f.stderr.join("\n"), /isInput: true or isOutput: true/);
assert.equal(existsSync(join(f.cwd, "board.syscfg")), false);
});

Expand All @@ -121,7 +126,7 @@ test("a missing connected pin record fails instead of writing partial SysConfig"
});
writeFileSync(join(f.cwd, "board.circuit.json"), JSON.stringify(incomplete));
assert.equal(await f.run(["generate-sysconfig", "board.circuit.json"]), 1);
assert.match(f.stderr.join("\n"), /input_trace: missing source_port input/);
assert.match(f.stderr.join("\n"), /1 connection\(s\) reference missing pins/);
assert.match(f.stderr.join("\n"), /Rebuild the circuit/);
assert.equal(existsSync(join(f.cwd, "board.syscfg")), false);
});
Expand All @@ -133,10 +138,7 @@ test("an MCU without pin records reports the component and required attributes",
JSON.stringify([circuit()[0]]),
);
assert.equal(await f.run(["generate-sysconfig", "board.circuit.json"]), 1);
assert.match(
f.stderr.join("\n"),
/U1.*CC2340R52E0RGER.*no source_port records/,
);
assert.match(f.stderr.join("\n"), /U1.*CC2340R52E0RGER.*no MCU pin records/);
assert.match(f.stderr.join("\n"), /pinAttributes/);
assert.equal(existsSync(join(f.cwd, "board.syscfg")), false);
});
Expand Down Expand Up @@ -211,3 +213,31 @@ test("TSX pinAttributes export selected GPIO roles without a request file", asyn
true,
);
});

test("a TSX pin failure prints actionable labels without the converter stack", async (t) => {
const f = fixture(t);
writeFileSync(
join(f.cwd, "board.circuit.tsx"),
`export default () => (
<board width="10mm" height="10mm">
<chip name="U1" manufacturerPartNumber="CC2340R52E0RGER"
pinLabels={{ pin4: "DIO11", pin6: "DIO13" }}
pinAttributes={{ pin4: { isGpio: true }, pin6: { isGpio: true } }} />
<net name="PMIC_LP" /><net name="CHARGER_INT" />
<trace from=".U1 > .DIO11" to="net.PMIC_LP" />
<trace from=".U1 > .DIO13" to="net.CHARGER_INT" />
</board>
);`,
);
assert.equal(await f.run(["generate-sysconfig", "board.circuit.tsx"]), 1);
const message = f.stderr.join("\n");
assert.match(message, /U1 \(CC2340R52E0RGER\)/);
assert.match(message, /U1 pin 4 \(DIO11\)/);
assert.match(message, /U1 pin 6 \(DIO13\)/);
assert.match(message, /Update U1's TSX pinAttributes/);
assert.doesNotMatch(
message,
/source_component_\d|source_port_\d|\bat \S+|throw new Error/,
);
assert.equal(existsSync(join(f.cwd, "board.syscfg")), false);
});
4 changes: 4 additions & 0 deletions tests/cli/sysconfig.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ test("missing pin functions are reported without requiring a firmware request fi
const message = f.stderr.join("\n");
assert.match(message, /Unresolved CC2340 pin configuration/);
assert.match(message, /pinAttributes/);
assert.doesNotMatch(
message,
/source_component_\d|source_port_\d|throw new Error|\bat \S+/,
);
assert.match(message, /pin 9/);
assert.doesNotMatch(
message,
Expand Down
Loading