@typecad/cuttlefish 1.0.0-alpha.7 → 1.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/config.d.ts +32 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +2 -2
- package/dist/api/shared/display-adapter.js +9 -119
- package/dist/api/shared/display-adapters/sdl.js +29 -29
- package/dist/api/shared/display-profile.d.ts +38 -33
- package/dist/api/shared/display-profile.js +22 -170
- package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
- package/dist/api/shared/framework-manifest-registry.js +8 -6
- package/dist/api/shared/framework-manifest.d.ts +147 -28
- package/dist/api/shared/framework-manifest.js +35 -2
- package/dist/api/shared/graphics-strategy.d.ts +15 -6
- package/dist/api/shared/hal-op-ir.d.ts +340 -3
- package/dist/api/shared/hal-op-ir.js +34 -0
- package/dist/api/shared/index.d.ts +9 -1
- package/dist/api/shared/index.js +11 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/platform-strategy.d.ts +104 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +17 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/validate-framework-manifest.js +77 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli.js +125 -121
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +20 -19
- package/dist/config-schema.d.ts +107 -65
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +74 -77
- package/dist/create/board-spec.js +0 -1
- package/dist/create/init-scaffold.d.ts +7 -3
- package/dist/create/init-scaffold.js +0 -62
- package/dist/create/init-templates.js +62 -16
- package/dist/create/init-wizard.js +32 -27
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +23 -1
- package/dist/emit/emitters/function-emitter-impl.js +96 -6
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.js +80 -1
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +217 -36
- package/dist/emit/emitters/top-level-prep.js +6 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +17 -8
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +81 -5
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +7 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +21 -5
- package/dist/emit/utils/async-state-machine.js +39 -12
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/install/framework-catalog.d.ts +53 -0
- package/dist/install/framework-catalog.js +107 -0
- package/dist/install/handle-install.d.ts +35 -0
- package/dist/install/handle-install.js +177 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.js +3 -0
- package/dist/ir/adc-range-validation.js +14 -1
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +4 -4
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +150 -0
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.js +10 -6
- package/dist/ir/hal/hal-parser.js +16 -9
- package/dist/ir/hal/hal-plugins.js +164 -3
- package/dist/ir/identifier-collector.js +21 -4
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +8 -16
- package/dist/ir/ownership-analysis.js +6 -0
- package/dist/ir/pin-capability-validation.js +37 -0
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +49 -18
- package/dist/ir/program-analysis.d.ts +42 -0
- package/dist/ir/program-analysis.js +243 -6
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/timing-validation.js +11 -2
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +4 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +51 -0
- package/dist/ir/type-resolution.js +18 -0
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +6 -1
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/orchestrator/graph-builder.js +8 -7
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/stores/display-profile-store.d.ts +0 -1
- package/dist/stores/display-profile-store.js +0 -1
- package/dist/testing.d.ts +4 -0
- package/dist/testing.js +5 -0
- package/dist/transpile.js +124 -35
- package/dist/types.d.ts +22 -2
- package/dist/ui-hook.d.ts +0 -2
- package/dist/utils/cli.d.ts +2 -2
- package/dist/utils/cli.js +68 -3
- package/package.json +20 -5
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/libdef/idf-discovery.d.ts +0 -7
- package/dist/libdef/idf-discovery.js +0 -59
- package/dist/licenses.d.ts +0 -185
- package/dist/licenses.js +0 -963
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Contract schema version. Only 1 is currently defined. */
|
|
3
|
+
export declare const CONTRACT_VERSION: 1;
|
|
4
|
+
/**
|
|
5
|
+
* An external component on the same net as an MCU pin. Validated LENIENTLY:
|
|
6
|
+
* cuttlefish does not act on component metadata, so we only require the fields
|
|
7
|
+
* the firmware toolchain reads (reference + dnp) and accept unknown extras
|
|
8
|
+
* (voltage, wattage, tolerance, ...) without rejecting the contract. This
|
|
9
|
+
* keeps cuttlefish forward-compatible with typecad.net adding component fields.
|
|
10
|
+
*/
|
|
11
|
+
declare const ContractComponentSchema: z.ZodObject<{
|
|
12
|
+
reference: z.ZodString;
|
|
13
|
+
dnp: z.ZodBoolean;
|
|
14
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
15
|
+
reference: z.ZodString;
|
|
16
|
+
dnp: z.ZodBoolean;
|
|
17
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
18
|
+
reference: z.ZodString;
|
|
19
|
+
dnp: z.ZodBoolean;
|
|
20
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
21
|
+
/**
|
|
22
|
+
* Info about a single connected MCU pin. The fields cuttlefish consumes
|
|
23
|
+
* (pinName, pinType, net) are required; boardName is optional (present only
|
|
24
|
+
* when typecad.net's Component.typehal map was set).
|
|
25
|
+
*/
|
|
26
|
+
declare const ContractPinSchema: z.ZodObject<{
|
|
27
|
+
pinName: z.ZodString;
|
|
28
|
+
pinType: z.ZodString;
|
|
29
|
+
boardName: z.ZodOptional<z.ZodString>;
|
|
30
|
+
net: z.ZodString;
|
|
31
|
+
externalComponents: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
32
|
+
reference: z.ZodString;
|
|
33
|
+
dnp: z.ZodBoolean;
|
|
34
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
35
|
+
reference: z.ZodString;
|
|
36
|
+
dnp: z.ZodBoolean;
|
|
37
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
38
|
+
reference: z.ZodString;
|
|
39
|
+
dnp: z.ZodBoolean;
|
|
40
|
+
}, z.ZodTypeAny, "passthrough">>, "many">>;
|
|
41
|
+
}, "strict", z.ZodTypeAny, {
|
|
42
|
+
pinName: string;
|
|
43
|
+
pinType: string;
|
|
44
|
+
net: string;
|
|
45
|
+
externalComponents: z.objectOutputType<{
|
|
46
|
+
reference: z.ZodString;
|
|
47
|
+
dnp: z.ZodBoolean;
|
|
48
|
+
}, z.ZodTypeAny, "passthrough">[];
|
|
49
|
+
boardName?: string | undefined;
|
|
50
|
+
}, {
|
|
51
|
+
pinName: string;
|
|
52
|
+
pinType: string;
|
|
53
|
+
net: string;
|
|
54
|
+
boardName?: string | undefined;
|
|
55
|
+
externalComponents?: z.objectInputType<{
|
|
56
|
+
reference: z.ZodString;
|
|
57
|
+
dnp: z.ZodBoolean;
|
|
58
|
+
}, z.ZodTypeAny, "passthrough">[] | undefined;
|
|
59
|
+
}>;
|
|
60
|
+
/** Which peripheral buses have all their required pins wired. */
|
|
61
|
+
declare const AvailablePeripheralsSchema: z.ZodObject<{
|
|
62
|
+
i2c: z.ZodBoolean;
|
|
63
|
+
spi: z.ZodBoolean;
|
|
64
|
+
uart: z.ZodBoolean;
|
|
65
|
+
}, "strict", z.ZodTypeAny, {
|
|
66
|
+
i2c: boolean;
|
|
67
|
+
spi: boolean;
|
|
68
|
+
uart: boolean;
|
|
69
|
+
}, {
|
|
70
|
+
i2c: boolean;
|
|
71
|
+
spi: boolean;
|
|
72
|
+
uart: boolean;
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* The top-level contract object. `.strict()` so a future v2 contract with new
|
|
76
|
+
* top-level keys is rejected clearly (complementing the version check) rather
|
|
77
|
+
* than silently accepted with the new fields dropped.
|
|
78
|
+
*/
|
|
79
|
+
export declare const HwContractSchema: z.ZodObject<{
|
|
80
|
+
version: z.ZodLiteral<1>;
|
|
81
|
+
mcu: z.ZodObject<{
|
|
82
|
+
symbol: z.ZodString;
|
|
83
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
84
|
+
symbol: z.ZodString;
|
|
85
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
86
|
+
symbol: z.ZodString;
|
|
87
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
88
|
+
connectedPins: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
89
|
+
pinName: z.ZodString;
|
|
90
|
+
pinType: z.ZodString;
|
|
91
|
+
boardName: z.ZodOptional<z.ZodString>;
|
|
92
|
+
net: z.ZodString;
|
|
93
|
+
externalComponents: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
94
|
+
reference: z.ZodString;
|
|
95
|
+
dnp: z.ZodBoolean;
|
|
96
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
97
|
+
reference: z.ZodString;
|
|
98
|
+
dnp: z.ZodBoolean;
|
|
99
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
100
|
+
reference: z.ZodString;
|
|
101
|
+
dnp: z.ZodBoolean;
|
|
102
|
+
}, z.ZodTypeAny, "passthrough">>, "many">>;
|
|
103
|
+
}, "strict", z.ZodTypeAny, {
|
|
104
|
+
pinName: string;
|
|
105
|
+
pinType: string;
|
|
106
|
+
net: string;
|
|
107
|
+
externalComponents: z.objectOutputType<{
|
|
108
|
+
reference: z.ZodString;
|
|
109
|
+
dnp: z.ZodBoolean;
|
|
110
|
+
}, z.ZodTypeAny, "passthrough">[];
|
|
111
|
+
boardName?: string | undefined;
|
|
112
|
+
}, {
|
|
113
|
+
pinName: string;
|
|
114
|
+
pinType: string;
|
|
115
|
+
net: string;
|
|
116
|
+
boardName?: string | undefined;
|
|
117
|
+
externalComponents?: z.objectInputType<{
|
|
118
|
+
reference: z.ZodString;
|
|
119
|
+
dnp: z.ZodBoolean;
|
|
120
|
+
}, z.ZodTypeAny, "passthrough">[] | undefined;
|
|
121
|
+
}>>;
|
|
122
|
+
availablePeripherals: z.ZodObject<{
|
|
123
|
+
i2c: z.ZodBoolean;
|
|
124
|
+
spi: z.ZodBoolean;
|
|
125
|
+
uart: z.ZodBoolean;
|
|
126
|
+
}, "strict", z.ZodTypeAny, {
|
|
127
|
+
i2c: boolean;
|
|
128
|
+
spi: boolean;
|
|
129
|
+
uart: boolean;
|
|
130
|
+
}, {
|
|
131
|
+
i2c: boolean;
|
|
132
|
+
spi: boolean;
|
|
133
|
+
uart: boolean;
|
|
134
|
+
}>;
|
|
135
|
+
}, "strict", z.ZodTypeAny, {
|
|
136
|
+
mcu: {
|
|
137
|
+
symbol: string;
|
|
138
|
+
} & {
|
|
139
|
+
[k: string]: unknown;
|
|
140
|
+
};
|
|
141
|
+
version: 1;
|
|
142
|
+
connectedPins: Record<string, {
|
|
143
|
+
pinName: string;
|
|
144
|
+
pinType: string;
|
|
145
|
+
net: string;
|
|
146
|
+
externalComponents: z.objectOutputType<{
|
|
147
|
+
reference: z.ZodString;
|
|
148
|
+
dnp: z.ZodBoolean;
|
|
149
|
+
}, z.ZodTypeAny, "passthrough">[];
|
|
150
|
+
boardName?: string | undefined;
|
|
151
|
+
}>;
|
|
152
|
+
availablePeripherals: {
|
|
153
|
+
i2c: boolean;
|
|
154
|
+
spi: boolean;
|
|
155
|
+
uart: boolean;
|
|
156
|
+
};
|
|
157
|
+
}, {
|
|
158
|
+
mcu: {
|
|
159
|
+
symbol: string;
|
|
160
|
+
} & {
|
|
161
|
+
[k: string]: unknown;
|
|
162
|
+
};
|
|
163
|
+
version: 1;
|
|
164
|
+
connectedPins: Record<string, {
|
|
165
|
+
pinName: string;
|
|
166
|
+
pinType: string;
|
|
167
|
+
net: string;
|
|
168
|
+
boardName?: string | undefined;
|
|
169
|
+
externalComponents?: z.objectInputType<{
|
|
170
|
+
reference: z.ZodString;
|
|
171
|
+
dnp: z.ZodBoolean;
|
|
172
|
+
}, z.ZodTypeAny, "passthrough">[] | undefined;
|
|
173
|
+
}>;
|
|
174
|
+
availablePeripherals: {
|
|
175
|
+
i2c: boolean;
|
|
176
|
+
spi: boolean;
|
|
177
|
+
uart: boolean;
|
|
178
|
+
};
|
|
179
|
+
}>;
|
|
180
|
+
export type ContractComponent = z.infer<typeof ContractComponentSchema>;
|
|
181
|
+
export type ContractPin = z.infer<typeof ContractPinSchema>;
|
|
182
|
+
export type AvailablePeripherals = z.infer<typeof AvailablePeripheralsSchema>;
|
|
183
|
+
export type HwContract = z.infer<typeof HwContractSchema>;
|
|
184
|
+
/**
|
|
185
|
+
* Parses a TypeCAD contract file.
|
|
186
|
+
*
|
|
187
|
+
* The version is checked first as a fast path so a v2 (or later) contract
|
|
188
|
+
* fails with "unsupported version, update cuttlefish" rather than a cascade
|
|
189
|
+
* of field-mismatch errors. After the version check, the full schema is
|
|
190
|
+
* applied for precise per-field validation.
|
|
191
|
+
*
|
|
192
|
+
* @throws on an unreadable file, invalid JSON, unsupported version, or
|
|
193
|
+
* schema-validation failure (with a path-annotated message).
|
|
194
|
+
*/
|
|
195
|
+
export declare function parseContractFile(contractPath: string): HwContract;
|
|
196
|
+
/**
|
|
197
|
+
* Matches contract pins to canonical MCU pin names (e.g. 'PB5', 'PC4').
|
|
198
|
+
*
|
|
199
|
+
* Strategy (boardName-first, substring fallback):
|
|
200
|
+
* 1. If the contract pin carries a `boardName` (populated by typecad.net when
|
|
201
|
+
* Component.typehal was set) and that name appears in mcuPinNames, use it
|
|
202
|
+
* directly — it is the user-validated mapping.
|
|
203
|
+
* 2. Otherwise, substring-match the KiCAD `pinName` against mcuPinNames
|
|
204
|
+
* (e.g. "XTAL1/PB6" contains "PB6"). This covers contracts emitted without
|
|
205
|
+
* a typehal map.
|
|
206
|
+
*
|
|
207
|
+
* Power/ground pins are excluded. The result is de-duplicated.
|
|
208
|
+
*/
|
|
209
|
+
export declare function matchConnectedPins(contract: HwContract, mcuPinNames: readonly string[]): string[];
|
|
210
|
+
/**
|
|
211
|
+
* Selects which MCU peripheral instance names to re-export based on the
|
|
212
|
+
* contract's availablePeripherals. If the contract says SPI isn't wired,
|
|
213
|
+
* SPI0 (and friends) is dropped from the narrowed board — so `SPI0.begin()`
|
|
214
|
+
* becomes a compile error on a board whose PCB doesn't route SPI.
|
|
215
|
+
*/
|
|
216
|
+
export declare function selectPeripherals(contract: HwContract, mcuPeripheralNames: readonly string[]): string[];
|
|
217
|
+
export {};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Contract parser — reads a TypeCAD hardware contract (*.contract.json)
|
|
3
|
+
//
|
|
4
|
+
// A contract is produced by the sister project typecad.net (TypeScript +
|
|
5
|
+
// KiCAD). It describes the physical connections of an MCU on a designed PCB:
|
|
6
|
+
// which pins are wired, to what nets, and which peripherals (I2C/SPI/UART)
|
|
7
|
+
// are available. This module parses that file and narrows it against an MCU
|
|
8
|
+
// package's TypeCADManifest to find the concrete MCU pin/peripheral names
|
|
9
|
+
// that the firmware is allowed to touch.
|
|
10
|
+
//
|
|
11
|
+
// The matched names feed board-generator.ts, which writes a narrowed
|
|
12
|
+
// `.cuttlefish/board.ts` so that `import { ... } from '@typecad/board'`
|
|
13
|
+
// exposes only the pins the actual PCB has wired — a pin used elsewhere on
|
|
14
|
+
// the board becomes a compile error in the firmware.
|
|
15
|
+
//
|
|
16
|
+
// Zod is used (rather than a plain JSON.parse + cast) because the contract is
|
|
17
|
+
// a cross-project interchange format: it crosses a trust boundary from a
|
|
18
|
+
// different codebase (typecad.net) with its own release cadence. Precise
|
|
19
|
+
// validation at this boundary turns silent "board generated with no pins"
|
|
20
|
+
// failures into clear "expected boolean, received string at
|
|
21
|
+
// availablePeripherals.i2c" errors. zod is already a runtime dependency of
|
|
22
|
+
// cuttlefish (config-schema.ts).
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import { z } from 'zod';
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Contract schema — the authoritative shape of a *.contract.json file.
|
|
28
|
+
//
|
|
29
|
+
// The types exported below (HwContract, ContractPin, ...) are DERIVED from
|
|
30
|
+
// this schema via z.infer, so the runtime validation and the compile-time
|
|
31
|
+
// type cannot drift. This mirrors the shape produced by
|
|
32
|
+
// typeCAD/@typecad/typecad/src/contract.ts; the schema is the local source of
|
|
33
|
+
// truth so cuttlefish pins to a stable contract format regardless of
|
|
34
|
+
// typecad.net's internal evolution.
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/** Contract schema version. Only 1 is currently defined. */
|
|
37
|
+
export const CONTRACT_VERSION = 1;
|
|
38
|
+
/**
|
|
39
|
+
* An external component on the same net as an MCU pin. Validated LENIENTLY:
|
|
40
|
+
* cuttlefish does not act on component metadata, so we only require the fields
|
|
41
|
+
* the firmware toolchain reads (reference + dnp) and accept unknown extras
|
|
42
|
+
* (voltage, wattage, tolerance, ...) without rejecting the contract. This
|
|
43
|
+
* keeps cuttlefish forward-compatible with typecad.net adding component fields.
|
|
44
|
+
*/
|
|
45
|
+
const ContractComponentSchema = z.object({
|
|
46
|
+
reference: z.string(),
|
|
47
|
+
dnp: z.boolean(),
|
|
48
|
+
}).passthrough();
|
|
49
|
+
/**
|
|
50
|
+
* Info about a single connected MCU pin. The fields cuttlefish consumes
|
|
51
|
+
* (pinName, pinType, net) are required; boardName is optional (present only
|
|
52
|
+
* when typecad.net's Component.typehal map was set).
|
|
53
|
+
*/
|
|
54
|
+
const ContractPinSchema = z.object({
|
|
55
|
+
pinName: z.string(),
|
|
56
|
+
pinType: z.string(),
|
|
57
|
+
boardName: z.string().optional(),
|
|
58
|
+
net: z.string(),
|
|
59
|
+
externalComponents: z.array(ContractComponentSchema).default([]),
|
|
60
|
+
}).strict();
|
|
61
|
+
/** Which peripheral buses have all their required pins wired. */
|
|
62
|
+
const AvailablePeripheralsSchema = z.object({
|
|
63
|
+
i2c: z.boolean(),
|
|
64
|
+
spi: z.boolean(),
|
|
65
|
+
uart: z.boolean(),
|
|
66
|
+
}).strict();
|
|
67
|
+
/** MCU identifying metadata. Only `symbol` is load-bearing for cuttlefish. */
|
|
68
|
+
const ContractMcuSchema = z.object({
|
|
69
|
+
symbol: z.string(),
|
|
70
|
+
}).passthrough();
|
|
71
|
+
/**
|
|
72
|
+
* The top-level contract object. `.strict()` so a future v2 contract with new
|
|
73
|
+
* top-level keys is rejected clearly (complementing the version check) rather
|
|
74
|
+
* than silently accepted with the new fields dropped.
|
|
75
|
+
*/
|
|
76
|
+
export const HwContractSchema = z.object({
|
|
77
|
+
version: z.literal(CONTRACT_VERSION),
|
|
78
|
+
mcu: ContractMcuSchema,
|
|
79
|
+
connectedPins: z.record(z.string(), ContractPinSchema),
|
|
80
|
+
availablePeripherals: AvailablePeripheralsSchema,
|
|
81
|
+
}).strict();
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// KiCAD electrical-type and net-name tables for power-pin exclusion.
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
/** KiCAD pin types that are not user GPIO and must not be re-exported as digital pins. */
|
|
86
|
+
const NON_GPIO_PIN_TYPES = new Set([
|
|
87
|
+
'power_in',
|
|
88
|
+
'power_out',
|
|
89
|
+
'passive', // typically decoupling/nc ties
|
|
90
|
+
]);
|
|
91
|
+
/** Net names that indicate a power/ground rail, not a signal. */
|
|
92
|
+
const POWER_NET_NAMES = new Set(['VCC', 'GND', 'VDD', 'VSS', '+3V3', '+5V', 'AVCC', 'AREF']);
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Parsing
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
/**
|
|
97
|
+
* Reformats a ZodError into a single readable, path-annotated message.
|
|
98
|
+
* Zod's default .message is a JSON blob; users want "at availablePeripherals.i2c:
|
|
99
|
+
* expected boolean, received string".
|
|
100
|
+
*/
|
|
101
|
+
function formatZodError(err, contractPath) {
|
|
102
|
+
const issues = err.issues.map((issue) => {
|
|
103
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
|
104
|
+
return ` at ${path}: ${issue.message}`;
|
|
105
|
+
});
|
|
106
|
+
return `Contract ${contractPath} failed validation:\n${issues.join('\n')}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Parses a TypeCAD contract file.
|
|
110
|
+
*
|
|
111
|
+
* The version is checked first as a fast path so a v2 (or later) contract
|
|
112
|
+
* fails with "unsupported version, update cuttlefish" rather than a cascade
|
|
113
|
+
* of field-mismatch errors. After the version check, the full schema is
|
|
114
|
+
* applied for precise per-field validation.
|
|
115
|
+
*
|
|
116
|
+
* @throws on an unreadable file, invalid JSON, unsupported version, or
|
|
117
|
+
* schema-validation failure (with a path-annotated message).
|
|
118
|
+
*/
|
|
119
|
+
export function parseContractFile(contractPath) {
|
|
120
|
+
let content;
|
|
121
|
+
try {
|
|
122
|
+
content = fs.readFileSync(contractPath, 'utf-8');
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
throw new Error(`Could not read contract file at ${contractPath}: ${err.message}`);
|
|
126
|
+
}
|
|
127
|
+
let parsed;
|
|
128
|
+
try {
|
|
129
|
+
parsed = JSON.parse(content);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
throw new Error(`Contract file ${contractPath} is not valid JSON: ${err.message}`);
|
|
133
|
+
}
|
|
134
|
+
// Fast path: reject an unsupported version before the full schema runs, so
|
|
135
|
+
// the error names the real problem rather than listing every field that
|
|
136
|
+
// differs from v1.
|
|
137
|
+
const version = parsed?.version;
|
|
138
|
+
if (version !== CONTRACT_VERSION) {
|
|
139
|
+
throw new Error(`Unsupported contract version in ${contractPath}: expected ${CONTRACT_VERSION}, got ${String(version)}. ` +
|
|
140
|
+
`This may be a newer contract format; update @typecad/cuttlefish.`);
|
|
141
|
+
}
|
|
142
|
+
// Full schema validation.
|
|
143
|
+
try {
|
|
144
|
+
return HwContractSchema.parse(parsed);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
if (err instanceof z.ZodError) {
|
|
148
|
+
throw new Error(formatZodError(err, contractPath));
|
|
149
|
+
}
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Matching — narrows the contract against an MCU package's manifest.
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
/**
|
|
157
|
+
* Returns true if a contract pin represents a power/ground/decoupling connection
|
|
158
|
+
* rather than a usable GPIO. Such pins must not be re-exported as digital pins.
|
|
159
|
+
*/
|
|
160
|
+
function isPowerPin(pin) {
|
|
161
|
+
if (NON_GPIO_PIN_TYPES.has(pin.pinType))
|
|
162
|
+
return true;
|
|
163
|
+
if (POWER_NET_NAMES.has(pin.net))
|
|
164
|
+
return true;
|
|
165
|
+
// KiCAD auto-generated nets like "net6" are signal nets; named power rails are not.
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Matches contract pins to canonical MCU pin names (e.g. 'PB5', 'PC4').
|
|
170
|
+
*
|
|
171
|
+
* Strategy (boardName-first, substring fallback):
|
|
172
|
+
* 1. If the contract pin carries a `boardName` (populated by typecad.net when
|
|
173
|
+
* Component.typehal was set) and that name appears in mcuPinNames, use it
|
|
174
|
+
* directly — it is the user-validated mapping.
|
|
175
|
+
* 2. Otherwise, substring-match the KiCAD `pinName` against mcuPinNames
|
|
176
|
+
* (e.g. "XTAL1/PB6" contains "PB6"). This covers contracts emitted without
|
|
177
|
+
* a typehal map.
|
|
178
|
+
*
|
|
179
|
+
* Power/ground pins are excluded. The result is de-duplicated.
|
|
180
|
+
*/
|
|
181
|
+
export function matchConnectedPins(contract, mcuPinNames) {
|
|
182
|
+
const mcuSet = new Set(mcuPinNames);
|
|
183
|
+
const matched = new Set();
|
|
184
|
+
for (const pin of Object.values(contract.connectedPins)) {
|
|
185
|
+
if (isPowerPin(pin))
|
|
186
|
+
continue;
|
|
187
|
+
// (1) boardName-first: explicit, firmware-friendly mapping.
|
|
188
|
+
if (pin.boardName && mcuSet.has(pin.boardName)) {
|
|
189
|
+
matched.add(pin.boardName);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
// (2) substring fallback: the MCU port name appears inside the KiCAD pin name.
|
|
193
|
+
// Compound KiCAD names like "XTAL1/PB6" or "~{RESET}/PC6" are split on "/"
|
|
194
|
+
// and each segment is checked so we don't false-match on a prefix.
|
|
195
|
+
const segments = pin.pinName.replace(/[~{}]/g, '').split(/[/-]/);
|
|
196
|
+
for (const mcuPin of mcuPinNames) {
|
|
197
|
+
if (segments.includes(mcuPin)) {
|
|
198
|
+
matched.add(mcuPin);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return Array.from(matched);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Selects which MCU peripheral instance names to re-export based on the
|
|
207
|
+
* contract's availablePeripherals. If the contract says SPI isn't wired,
|
|
208
|
+
* SPI0 (and friends) is dropped from the narrowed board — so `SPI0.begin()`
|
|
209
|
+
* becomes a compile error on a board whose PCB doesn't route SPI.
|
|
210
|
+
*/
|
|
211
|
+
export function selectPeripherals(contract, mcuPeripheralNames) {
|
|
212
|
+
const avail = contract.availablePeripherals ?? { i2c: false, spi: false, uart: false };
|
|
213
|
+
return mcuPeripheralNames.filter((name) => {
|
|
214
|
+
// Match instance-family prefixes: I2C0/I2C1 → "i2c", SPI0/SPI1 → "spi", UART0 → "uart".
|
|
215
|
+
if (/^I2C\d/.test(name))
|
|
216
|
+
return avail.i2c;
|
|
217
|
+
if (/^SPI\d/.test(name))
|
|
218
|
+
return avail.spi;
|
|
219
|
+
if (/^UART\d/.test(name))
|
|
220
|
+
return avail.uart;
|
|
221
|
+
// Unknown peripheral families are preserved (don't silently drop something we don't recognize).
|
|
222
|
+
return true;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ResolvedCuttlefishConfig } from '../config-loader.js';
|
|
2
|
+
export type { HwContract, ContractPin, ContractComponent, AvailablePeripherals, } from './contract-parser.js';
|
|
3
|
+
export { parseContractFile, matchConnectedPins, selectPeripherals } from './contract-parser.js';
|
|
4
|
+
export { generateBoardFile, CUTTLEFISH_DIR } from './board-generator.js';
|
|
5
|
+
export type { GenerateBoardOptions } from './board-generator.js';
|
|
6
|
+
/**
|
|
7
|
+
* Orchestrates contract-based board generation for a resolved config:
|
|
8
|
+
* 1. Reads the contract file at `config.contract` (resolved relative to the
|
|
9
|
+
* project root, i.e. the dir containing cuttlefish.config.ts).
|
|
10
|
+
* 2. Dynamically imports the MCU package's `TypeCADManifest` to discover the
|
|
11
|
+
* canonical pin and peripheral names.
|
|
12
|
+
* 3. Matches contract pins → MCU pin names and selects peripherals.
|
|
13
|
+
* 4. Writes the narrowed `.cuttlefish/board.ts`.
|
|
14
|
+
*
|
|
15
|
+
* This is the step that re-opens the typecad.net → cuttlefish interop. After it
|
|
16
|
+
* runs, the existing `export * from './board.js'` in cuttlefish-env.d.ts
|
|
17
|
+
* resolves to a board exposing only the pins the actual PCB has wired.
|
|
18
|
+
*
|
|
19
|
+
* @throws on a missing/unreadable/unparseable contract, an unsupported version,
|
|
20
|
+
* or if the MCU package can't be loaded for its manifest.
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateContractBoard(config: ResolvedCuttlefishConfig): Promise<string>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Contract module barrel — entry point for contract-based board generation.
|
|
3
|
+
//
|
|
4
|
+
// Exports the parser, the board generator, and the `generateContractBoard`
|
|
5
|
+
// orchestrator that the CLI calls when `config.contract` is set. The env.d.ts
|
|
6
|
+
// emitted by config-loader.ts points `@typecad/board` at `./board.js`, and this
|
|
7
|
+
// module is what writes that file.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { parseContractFile, matchConnectedPins, selectPeripherals } from './contract-parser.js';
|
|
11
|
+
import { generateBoardFile } from './board-generator.js';
|
|
12
|
+
export { parseContractFile, matchConnectedPins, selectPeripherals } from './contract-parser.js';
|
|
13
|
+
export { generateBoardFile, CUTTLEFISH_DIR } from './board-generator.js';
|
|
14
|
+
/**
|
|
15
|
+
* Orchestrates contract-based board generation for a resolved config:
|
|
16
|
+
* 1. Reads the contract file at `config.contract` (resolved relative to the
|
|
17
|
+
* project root, i.e. the dir containing cuttlefish.config.ts).
|
|
18
|
+
* 2. Dynamically imports the MCU package's `TypeCADManifest` to discover the
|
|
19
|
+
* canonical pin and peripheral names.
|
|
20
|
+
* 3. Matches contract pins → MCU pin names and selects peripherals.
|
|
21
|
+
* 4. Writes the narrowed `.cuttlefish/board.ts`.
|
|
22
|
+
*
|
|
23
|
+
* This is the step that re-opens the typecad.net → cuttlefish interop. After it
|
|
24
|
+
* runs, the existing `export * from './board.js'` in cuttlefish-env.d.ts
|
|
25
|
+
* resolves to a board exposing only the pins the actual PCB has wired.
|
|
26
|
+
*
|
|
27
|
+
* @throws on a missing/unreadable/unparseable contract, an unsupported version,
|
|
28
|
+
* or if the MCU package can't be loaded for its manifest.
|
|
29
|
+
*/
|
|
30
|
+
export async function generateContractBoard(config) {
|
|
31
|
+
if (!config.contract) {
|
|
32
|
+
throw new Error('generateContractBoard called without config.contract');
|
|
33
|
+
}
|
|
34
|
+
if (!config.mcu) {
|
|
35
|
+
throw new Error(`A 'contract' config requires an 'mcu' package to narrow against. ` +
|
|
36
|
+
`Add e.g. mcu: '@typecad/mcu-atmega328p' to cuttlefish.config.ts.`);
|
|
37
|
+
}
|
|
38
|
+
const projectDir = path.dirname(config.configPath);
|
|
39
|
+
const contractPath = path.isAbsolute(config.contract)
|
|
40
|
+
? config.contract
|
|
41
|
+
: path.resolve(projectDir, config.contract);
|
|
42
|
+
// (1) Parse the contract.
|
|
43
|
+
const contract = parseContractFile(contractPath);
|
|
44
|
+
// (2) Load the MCU manifest. The MCU package is a peer at runtime — import
|
|
45
|
+
// it dynamically so cuttlefish doesn't hard-depend on any single MCU.
|
|
46
|
+
let manifest;
|
|
47
|
+
try {
|
|
48
|
+
const mod = (await import(config.mcu));
|
|
49
|
+
if (!mod.TypeCADManifest) {
|
|
50
|
+
throw new Error(`'${config.mcu}' does not export a TypeCADManifest.`);
|
|
51
|
+
}
|
|
52
|
+
manifest = mod.TypeCADManifest;
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
throw new Error(`Could not load TypeCADManifest from MCU package '${config.mcu}' for contract-based ` +
|
|
56
|
+
`board generation: ${err.message}`);
|
|
57
|
+
}
|
|
58
|
+
// (3) Match + select.
|
|
59
|
+
const connectedPins = matchConnectedPins(contract, manifest.pinNames);
|
|
60
|
+
const peripherals = selectPeripherals(contract, manifest.peripheralNames);
|
|
61
|
+
// (4) Emit the narrowed board.
|
|
62
|
+
return generateBoardFile({
|
|
63
|
+
projectDir,
|
|
64
|
+
mcuPackage: config.mcu,
|
|
65
|
+
connectedPins,
|
|
66
|
+
peripherals,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -53,8 +53,8 @@ function insertAfter(filePath, marker, insertion, searchForId) {
|
|
|
53
53
|
export function scaffoldBoardPackages(spec, opts = {}) {
|
|
54
54
|
const arch = spec.architecture;
|
|
55
55
|
const rootDir = opts.rootDir ? path.resolve(opts.rootDir) : findMonorepoRoot(process.cwd());
|
|
56
|
-
const mcuDir = path.join(rootDir, '
|
|
57
|
-
const boardDir = path.join(rootDir, '
|
|
56
|
+
const mcuDir = path.join(rootDir, 'mcus', `mcu-${arch}`);
|
|
57
|
+
const boardDir = path.join(rootDir, 'boards', `board-${arch}`);
|
|
58
58
|
// Overwrite protection
|
|
59
59
|
const mcuExists = fs.existsSync(mcuDir);
|
|
60
60
|
const boardExists = fs.existsSync(boardDir);
|
|
@@ -93,8 +93,8 @@ export function scaffoldBoardPackages(spec, opts = {}) {
|
|
|
93
93
|
if (fs.existsSync(rootPjPath)) {
|
|
94
94
|
try {
|
|
95
95
|
const rootPj = JSON.parse(fs.readFileSync(rootPjPath, 'utf8'));
|
|
96
|
-
if (Array.isArray(rootPj.workspaces) && !rootPj.workspaces.includes(`
|
|
97
|
-
rootPj.workspaces.push(`
|
|
96
|
+
if (Array.isArray(rootPj.workspaces) && !rootPj.workspaces.includes(`mcus/mcu-${arch}`)) {
|
|
97
|
+
rootPj.workspaces.push(`mcus/mcu-${arch}`, `boards/board-${arch}`);
|
|
98
98
|
fs.writeFileSync(rootPjPath, JSON.stringify(rootPj, null, 2) + '\n', 'utf8');
|
|
99
99
|
}
|
|
100
100
|
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// tool. Each function takes a BoardSpec and returns the file content as a
|
|
4
4
|
// string. No filesystem I/O — the orchestrator (board-codegen.ts) writes files.
|
|
5
5
|
//
|
|
6
|
-
// Golden reference: the hand-written
|
|
7
|
-
//
|
|
6
|
+
// Golden reference: the hand-written mcus/mcu-esp32c6/ and
|
|
7
|
+
// boards/board-esp32c6/ packages. These generators reproduce them from a
|
|
8
8
|
// C6 spec.
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
@@ -71,7 +71,7 @@ export function genMcuTsconfig(_spec) {
|
|
|
71
71
|
experimentalDecorators: true, emitDecoratorMetadata: true,
|
|
72
72
|
},
|
|
73
73
|
include: ['src/**/*.ts'],
|
|
74
|
-
references: [{ path: '
|
|
74
|
+
references: [{ path: '../../packages/hal' }, { path: '../../packages/cuttlefish' }],
|
|
75
75
|
}, null, 2) + '\n';
|
|
76
76
|
}
|
|
77
77
|
export function genMcuPins(spec) {
|
|
@@ -408,7 +408,7 @@ export function genBoardTsconfig(spec) {
|
|
|
408
408
|
},
|
|
409
409
|
include: ['src/**/*.ts'],
|
|
410
410
|
references: [
|
|
411
|
-
{ path: '
|
|
411
|
+
{ path: '../../packages/hal' }, { path: '../../packages/cuttlefish' }, { path: `../../mcus/mcu-${spec.architecture}` },
|
|
412
412
|
],
|
|
413
413
|
}, null, 2) + '\n';
|
|
414
414
|
}
|
|
@@ -535,7 +535,6 @@ export function genBoardIndex(spec) {
|
|
|
535
535
|
// Build config
|
|
536
536
|
lines.push(` build: {`);
|
|
537
537
|
lines.push(` frameworks: {`);
|
|
538
|
-
lines.push(` platformio: '${spec.platformioTarget}',`);
|
|
539
538
|
lines.push(` arduino: '${spec.fqbn}',`);
|
|
540
539
|
lines.push(` },`);
|
|
541
540
|
lines.push(` defines: {`);
|