@typecad/simulator 0.1.0-alpha.1

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -0
  3. package/dist/board/board-sim.d.ts +52 -0
  4. package/dist/board/board-sim.js +188 -0
  5. package/dist/board/from-definition.d.ts +43 -0
  6. package/dist/board/from-definition.js +82 -0
  7. package/dist/bus/i2c-sim.d.ts +61 -0
  8. package/dist/bus/i2c-sim.js +155 -0
  9. package/dist/bus/serial-sim.d.ts +71 -0
  10. package/dist/bus/serial-sim.js +241 -0
  11. package/dist/bus/spi-sim.d.ts +65 -0
  12. package/dist/bus/spi-sim.js +202 -0
  13. package/dist/contracts.d.ts +223 -0
  14. package/dist/contracts.js +87 -0
  15. package/dist/gpio/analog-pin-sim.d.ts +40 -0
  16. package/dist/gpio/analog-pin-sim.js +75 -0
  17. package/dist/gpio/digital-pin-sim.d.ts +68 -0
  18. package/dist/gpio/digital-pin-sim.js +172 -0
  19. package/dist/gpio/interrupt-sim.d.ts +69 -0
  20. package/dist/gpio/interrupt-sim.js +118 -0
  21. package/dist/gpio/pwm-pin-sim.d.ts +41 -0
  22. package/dist/gpio/pwm-pin-sim.js +85 -0
  23. package/dist/helpers.d.ts +40 -0
  24. package/dist/helpers.js +57 -0
  25. package/dist/index.d.ts +18 -0
  26. package/dist/index.js +21 -0
  27. package/dist/types.d.ts +92 -0
  28. package/dist/types.js +4 -0
  29. package/package.json +62 -0
  30. package/src/board/board-sim.ts +208 -0
  31. package/src/board/from-definition.ts +91 -0
  32. package/src/bus/i2c-sim.ts +212 -0
  33. package/src/bus/serial-sim.ts +280 -0
  34. package/src/bus/spi-sim.ts +269 -0
  35. package/src/contracts.ts +330 -0
  36. package/src/gpio/analog-pin-sim.ts +91 -0
  37. package/src/gpio/digital-pin-sim.ts +234 -0
  38. package/src/gpio/interrupt-sim.ts +151 -0
  39. package/src/gpio/pwm-pin-sim.ts +103 -0
  40. package/src/helpers.ts +100 -0
  41. package/src/index.ts +59 -0
  42. package/src/types.ts +104 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 typecad0
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # @typecad/simulator
2
+
3
+ Node.js hardware simulation runtime for TypeCAD.
4
+
5
+ ## Overview
6
+
7
+ `@typecad/simulator` provides a simulated hardware runtime for TypeCAD firmware and tests. It exposes simulated GPIO pins, serial ports, I2C buses, SPI buses, PWM pins, and interrupt pins so you can verify hardware logic without using a physical board.
8
+
9
+ ## When to use the simulator
10
+
11
+ `@typecad/simulator` is best for testing and validating hardware-facing logic in Node.js before you use a real board. It is not a transpiler output checker, and it does not replace `@typecad/expect` for firmware-level hardware tests.
12
+
13
+ Use the simulator when you want to:
14
+
15
+ - unit test control logic for buttons, LEDs, and buses
16
+ - verify that a parser sends the correct serial bytes
17
+ - mock sensor input through analog or I2C injection
18
+ - exercise interrupt-driven code paths without physical hardware
19
+ - run CI-friendly tests that are fast and deterministic
20
+
21
+ Do not use it for:
22
+
23
+ - verifying TypeCAD transpilation output
24
+ - measuring real ADC noise or analog timing
25
+ - checking exact microsecond timing behavior
26
+
27
+ ## Quick start
28
+
29
+ ```ts
30
+ import { createSimBoard } from '@typecad/simulator';
31
+
32
+ const board = createSimBoard({ boardType: 'arduino-uno' });
33
+
34
+ const button = board.digital(2).asInputPullUp();
35
+ const led = board.digital(13).asOutput();
36
+
37
+ button.injectValue(0); // simulate a pressed active-low button
38
+ if (!button.read()) {
39
+ led.high();
40
+ }
41
+
42
+ console.log(led.getBitValue()); // 1
43
+ ```
44
+
45
+ ## Example: button-driven LED logic
46
+
47
+ This pattern is the real value of the simulator: you can test logic driven by external inputs without a board.
48
+
49
+ ```ts
50
+ import { describe, it, expect } from 'vitest';
51
+ import { createSimBoard } from '@typecad/simulator';
52
+
53
+ function updateLed(button: any, led: any) {
54
+ if (!button.read()) {
55
+ led.high();
56
+ } else {
57
+ led.low();
58
+ }
59
+ }
60
+
61
+ describe('button toggle logic', () => {
62
+ it('turns the LED on when the button is pressed', () => {
63
+ const board = createSimBoard({ boardType: 'arduino-uno' });
64
+ const button = board.digital(2).asInputPullUp();
65
+ const led = board.digital(13).asOutput();
66
+
67
+ button.injectValue(0); // press button
68
+ updateLed(button, led);
69
+
70
+ expect(led.getBitValue()).toBe(1);
71
+ });
72
+ });
73
+ ```
74
+
75
+ ## How to use
76
+
77
+ ### Create a simulated board
78
+
79
+ Use `createSimBoard()` with a board type and optional custom counts:
80
+
81
+ - `boardType` — board identifier such as `arduino-uno` or `arduino-nano`. Known board types carry default PWM/interrupt pin maps; custom/unknown types have no capability pins unless declared via `pwmPins`/`interruptPins`.
82
+ - `digitalPinCount` — number of digital pins
83
+ - `analogPinCount` — number of analog pins
84
+ - `uartCount` — number of serial ports
85
+ - `i2cBusCount` — number of I2C buses
86
+ - `spiBusCount` — number of SPI buses
87
+ - `pwmPins` / `interruptPins` — override the capability pin numbers (defaults to the board type's map, or empty for custom boards)
88
+
89
+ ### Access simulated peripherals
90
+
91
+ The `SimBoard` instance exposes typed accessors:
92
+
93
+ - `board.digital(pin)` — `SimDigitalPin`
94
+ - `board.analog(pin)` — `SimAnalogPin`
95
+ - `board.pwm(pin)` — `SimPWMPin`
96
+ - `board.interrupt(pin)` — `SimInterruptPin`
97
+ - `board.serial(port)` — `SimSerialPort`
98
+ - `board.i2c(bus)` — `SimI2CBus`
99
+ - `board.spi(bus)` — `SimSPIBus`
100
+
101
+ ### Simulating a real board package
102
+
103
+ When you already depend on a `@typecad/board-*` package, use `createBoardFromDefinition()` to build a `SimBoard` whose pin layout, PWM/interrupt pins, ADC resolution/reference, and bus counts match the real board — instead of hardcoding them in `createSimBoard()`:
104
+
105
+ ```ts
106
+ import { ArduinoUno } from '@typecad/board-arduino-uno';
107
+ import { createBoardFromDefinition } from '@typecad/simulator';
108
+
109
+ const board = createBoardFromDefinition(ArduinoUno);
110
+
111
+ board.digital(13).asOutput().high(); // LED on PB5 (pin 13)
112
+ board.pwm(9).pwm(50); // PWM on PB1 (pin 9)
113
+ board.analog(0).injectVoltage(2.5); // A0, 10-bit ADC, 5V reference
114
+ ```
115
+
116
+ This is the recommended path for board-specific tests. The board package must be built so its `BoardDefinition` is importable at runtime. See the [Software-Defined Hardware docs](https://typecad.dev/docs/simulation/software-defined-hardware#simulating-a-real-board-package) for the full list of derived fields.
117
+
118
+ ### Verify state and reset
119
+
120
+ The simulated board supports reset and state inspection, making it useful for unit tests and firmware validation without hardware:
121
+
122
+ ```ts
123
+ board.digital(13).output();
124
+ board.digital(13).high();
125
+ expect(board.digital(13).getBitValue()).toBe(1);
126
+ board.reset();
127
+ expect(board.digital(13).getBitValue()).toBe(0);
128
+ ```
129
+
130
+ ### HAL compatibility
131
+
132
+ `@typecad/simulator` is built on the same hardware abstraction contract used by the rest of the ecosystem. The simulator package owns the runtime contract hierarchy (`BasePin`, `PWMPin`, `AnalogPin`, `InterruptPin`, `II2CBus`, `ISPIBus`, `ISerialPort`, and the `I2CStatus`/`SPIStatus`/`UARTStatus` enums) in its own `contracts.ts`, and re-exports it for consumers. Primitive and protocol-shape types (`PinMode`, `DigitalValue`, `AnalogValue`, `InterruptHandler`, `I2CAddress`, `SPIMode`, `SPIBitOrder`, `SPISettings`) are imported from `@typecad/hal`:
133
+
134
+ - `SimDigitalPin` implements `BasePin` and digital pin semantics.
135
+ - `SimAnalogPin` implements `AnalogPin` semantics.
136
+ - `SimPWMPin` implements `PWMPin` semantics (including `getPwmFrequency()` / `getPwmResolution()`).
137
+ - `SimInterruptPin` implements `InterruptPin` semantics.
138
+ - `SimSerialPort` implements `ISerialPort` / UART communication semantics.
139
+ - `SimI2CBus` and `SimSPIBus` expose the same bus-style APIs expected by TypeCAD HAL consumers.
140
+
141
+ ### How the simulator uses HAL concepts
142
+
143
+ - `PinMode` values are imported from `@typecad/hal` and used to track simulated pin direction and pull state.
144
+ - capability flags like `digitalInput`, `pwm`, and `interrupt` are modeled using the same type definitions that HAL packages expose for pin capabilities.
145
+ - serial, I2C, and SPI simulation classes rely on the shared TypeCAD bus interfaces to ensure host code can interact with them using the same method names and semantics as real hardware.
146
+ - `createSimBoard()` selects default PWM and interrupt pin sets by board type, matching the physical pin layout conventions used by TypeCAD board packages.
147
+
148
+ This makes the simulator a practical way to validate hardware logic and unit tests while staying aligned with TypeCAD's HAL abstraction layer.
@@ -0,0 +1,52 @@
1
+ import type { SimBoardConfig } from '../types.js';
2
+ import { SimDigitalPin } from '../gpio/digital-pin-sim.js';
3
+ import { SimAnalogPin } from '../gpio/analog-pin-sim.js';
4
+ import { SimPWMPin } from '../gpio/pwm-pin-sim.js';
5
+ import { SimInterruptPin } from '../gpio/interrupt-sim.js';
6
+ import { SimSerialPort } from '../bus/serial-sim.js';
7
+ import { SimI2CBus } from '../bus/i2c-sim.js';
8
+ import { SimSPIBus } from '../bus/spi-sim.js';
9
+ /**
10
+ * A fully simulated board with GPIO pins and bus peripherals.
11
+ *
12
+ * Tests create a `SimBoard` via `createSimBoard()`, then access pins
13
+ * and buses to inject data and verify behavior.
14
+ */
15
+ export declare class SimBoard {
16
+ readonly digitalPins: ReadonlyMap<number, SimDigitalPin>;
17
+ readonly analogPins: ReadonlyMap<number, SimAnalogPin>;
18
+ readonly pwmPins: ReadonlyMap<number, SimPWMPin>;
19
+ readonly interruptPins: ReadonlyMap<number, SimInterruptPin>;
20
+ readonly serialPorts: ReadonlyMap<number, SimSerialPort>;
21
+ readonly i2cBuses: ReadonlyMap<number, SimI2CBus>;
22
+ readonly spiBuses: ReadonlyMap<number, SimSPIBus>;
23
+ constructor(config: SimBoardConfig);
24
+ /** Get digital pin by number. Throws if out of range. */
25
+ digital(pin: number): SimDigitalPin;
26
+ /** Get analog pin by number (A0=0, A1=1, ...). Throws if out of range. */
27
+ analog(pin: number): SimAnalogPin;
28
+ /** Get PWM pin by number. Throws if not a PWM pin. */
29
+ pwm(pin: number): SimPWMPin;
30
+ /** Get interrupt pin by number. Throws if not an interrupt pin. */
31
+ interrupt(pin: number): SimInterruptPin;
32
+ /** Get serial port by number (0 = UART0). */
33
+ serial(port?: number): SimSerialPort;
34
+ /** Get I2C bus by number (0 = I2C0). */
35
+ i2c(bus?: number): SimI2CBus;
36
+ /** Get SPI bus by number (0 = SPI0). */
37
+ spi(bus?: number): SimSPIBus;
38
+ /** Reset all peripherals to initial state. */
39
+ reset(): void;
40
+ }
41
+ /**
42
+ * Create a simulated board with the given configuration.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const board = createSimBoard({ boardType: 'arduino-uno' });
47
+ * board.digital(13).output();
48
+ * board.digital(13).high();
49
+ * expect(board.digital(13).getBitValue()).toBe(1);
50
+ * ```
51
+ */
52
+ export declare function createSimBoard(config: SimBoardConfig): SimBoard;
@@ -0,0 +1,188 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/simulator — Simulated board factory
3
+ // ---------------------------------------------------------------------------
4
+ import { SimDigitalPin } from '../gpio/digital-pin-sim.js';
5
+ import { SimAnalogPin } from '../gpio/analog-pin-sim.js';
6
+ import { SimPWMPin } from '../gpio/pwm-pin-sim.js';
7
+ import { SimInterruptPin } from '../gpio/interrupt-sim.js';
8
+ import { SimSerialPort } from '../bus/serial-sim.js';
9
+ import { SimI2CBus } from '../bus/i2c-sim.js';
10
+ import { SimSPIBus } from '../bus/spi-sim.js';
11
+ // ---------------------------------------------------------------------------
12
+ // SimBoard
13
+ // ---------------------------------------------------------------------------
14
+ /**
15
+ * A fully simulated board with GPIO pins and bus peripherals.
16
+ *
17
+ * Tests create a `SimBoard` via `createSimBoard()`, then access pins
18
+ * and buses to inject data and verify behavior.
19
+ */
20
+ export class SimBoard {
21
+ constructor(config) {
22
+ const digitalCount = config.digitalPinCount ?? 14;
23
+ const analogCount = config.analogPinCount ?? 6;
24
+ const uartCount = config.uartCount ?? 1;
25
+ const i2cCount = config.i2cBusCount ?? 1;
26
+ const spiCount = config.spiBusCount ?? 1;
27
+ // Digital pins
28
+ const digitalMap = new Map();
29
+ for (let i = 0; i < digitalCount; i++) {
30
+ digitalMap.set(i, new SimDigitalPin(i));
31
+ }
32
+ this.digitalPins = digitalMap;
33
+ // Analog pins (A0, A1, ...)
34
+ const analogMap = new Map();
35
+ for (let i = 0; i < analogCount; i++) {
36
+ analogMap.set(i, new SimAnalogPin(i));
37
+ }
38
+ this.analogPins = analogMap;
39
+ // PWM pins (e.g. 3, 5, 6, 9, 10, 11 on Uno). Custom/unknown board types
40
+ // default to no PWM pins unless explicitly configured via `pwmPins`.
41
+ const pwmMap = new Map();
42
+ const pwmPinNumbers = config.pwmPins ?? getPwmPinsForBoard(config.boardType);
43
+ for (const pinNum of pwmPinNumbers) {
44
+ if (pinNum < digitalCount) {
45
+ pwmMap.set(pinNum, new SimPWMPin(pinNum));
46
+ }
47
+ }
48
+ this.pwmPins = pwmMap;
49
+ // Interrupt pins (e.g. 2, 3 on Uno). Custom/unknown board types default
50
+ // to no interrupt pins unless explicitly configured via `interruptPins`.
51
+ const interruptMap = new Map();
52
+ const intPinNumbers = config.interruptPins ?? getInterruptPinsForBoard(config.boardType);
53
+ for (const pinNum of intPinNumbers) {
54
+ if (pinNum < digitalCount) {
55
+ interruptMap.set(pinNum, new SimInterruptPin(pinNum));
56
+ }
57
+ }
58
+ this.interruptPins = interruptMap;
59
+ // Serial ports
60
+ const serialMap = new Map();
61
+ for (let i = 0; i < uartCount; i++) {
62
+ serialMap.set(i, new SimSerialPort(i, config.uartRxBufferSize, config.uartTxBufferSize));
63
+ }
64
+ this.serialPorts = serialMap;
65
+ // I2C buses
66
+ const i2cMap = new Map();
67
+ for (let i = 0; i < i2cCount; i++) {
68
+ i2cMap.set(i, new SimI2CBus(i));
69
+ }
70
+ this.i2cBuses = i2cMap;
71
+ // SPI buses
72
+ const spiMap = new Map();
73
+ for (let i = 0; i < spiCount; i++) {
74
+ spiMap.set(i, new SimSPIBus());
75
+ }
76
+ this.spiBuses = spiMap;
77
+ }
78
+ // --- Convenience accessors ---
79
+ /** Get digital pin by number. Throws if out of range. */
80
+ digital(pin) {
81
+ const p = this.digitalPins.get(pin);
82
+ if (!p)
83
+ throw new Error(`Digital pin ${pin} not available on this board`);
84
+ return p;
85
+ }
86
+ /** Get analog pin by number (A0=0, A1=1, ...). Throws if out of range. */
87
+ analog(pin) {
88
+ const p = this.analogPins.get(pin);
89
+ if (!p)
90
+ throw new Error(`Analog pin A${pin} not available on this board`);
91
+ return p;
92
+ }
93
+ /** Get PWM pin by number. Throws if not a PWM pin. */
94
+ pwm(pin) {
95
+ const p = this.pwmPins.get(pin);
96
+ if (!p)
97
+ throw new Error(`Pin ${pin} is not a PWM pin on this board`);
98
+ return p;
99
+ }
100
+ /** Get interrupt pin by number. Throws if not an interrupt pin. */
101
+ interrupt(pin) {
102
+ const p = this.interruptPins.get(pin);
103
+ if (!p)
104
+ throw new Error(`Pin ${pin} is not an interrupt pin on this board`);
105
+ return p;
106
+ }
107
+ /** Get serial port by number (0 = UART0). */
108
+ serial(port = 0) {
109
+ const s = this.serialPorts.get(port);
110
+ if (!s)
111
+ throw new Error(`Serial port UART${port} not available on this board`);
112
+ return s;
113
+ }
114
+ /** Get I2C bus by number (0 = I2C0). */
115
+ i2c(bus = 0) {
116
+ const b = this.i2cBuses.get(bus);
117
+ if (!b)
118
+ throw new Error(`I2C bus ${bus} not available on this board`);
119
+ return b;
120
+ }
121
+ /** Get SPI bus by number (0 = SPI0). */
122
+ spi(bus = 0) {
123
+ const b = this.spiBuses.get(bus);
124
+ if (!b)
125
+ throw new Error(`SPI bus ${bus} not available on this board`);
126
+ return b;
127
+ }
128
+ // --- Reset ---
129
+ /** Reset all peripherals to initial state. */
130
+ reset() {
131
+ for (const pin of this.digitalPins.values())
132
+ pin.reset();
133
+ for (const pin of this.analogPins.values())
134
+ pin.reset();
135
+ for (const pin of this.pwmPins.values())
136
+ pin.reset();
137
+ for (const pin of this.interruptPins.values())
138
+ pin.reset();
139
+ for (const port of this.serialPorts.values())
140
+ port.reset();
141
+ for (const bus of this.i2cBuses.values())
142
+ bus.reset();
143
+ for (const bus of this.spiBuses.values())
144
+ bus.reset();
145
+ }
146
+ }
147
+ // ---------------------------------------------------------------------------
148
+ // Factory
149
+ // ---------------------------------------------------------------------------
150
+ /**
151
+ * Create a simulated board with the given configuration.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * const board = createSimBoard({ boardType: 'arduino-uno' });
156
+ * board.digital(13).output();
157
+ * board.digital(13).high();
158
+ * expect(board.digital(13).getBitValue()).toBe(1);
159
+ * ```
160
+ */
161
+ export function createSimBoard(config) {
162
+ return new SimBoard(config);
163
+ }
164
+ // ---------------------------------------------------------------------------
165
+ // Board-specific pin maps
166
+ //
167
+ // Only known board types carry a default capability pin map. Custom and
168
+ // unrecognized board types return an empty set — use `pwmPins` /
169
+ // `interruptPins` on SimBoardConfig to declare capability pins for them.
170
+ // ---------------------------------------------------------------------------
171
+ function getPwmPinsForBoard(boardType) {
172
+ switch (boardType) {
173
+ case 'arduino-uno':
174
+ case 'arduino-nano':
175
+ return [3, 5, 6, 9, 10, 11];
176
+ default:
177
+ return [];
178
+ }
179
+ }
180
+ function getInterruptPinsForBoard(boardType) {
181
+ switch (boardType) {
182
+ case 'arduino-uno':
183
+ case 'arduino-nano':
184
+ return [2, 3];
185
+ default:
186
+ return [];
187
+ }
188
+ }
@@ -0,0 +1,43 @@
1
+ import type { BoardDefinition } from '@typecad/cuttlefish/api/schema';
2
+ import type { SimBoard } from './board-sim.js';
3
+ /**
4
+ * Build a simulated board from a board package's `BoardDefinition`.
5
+ *
6
+ * Every `@typecad/board-*` package exports a `BoardDefinition` whose `pins.all`
7
+ * entries carry real framework pin numbers and per-pin capability flags, and
8
+ * whose `peripherals` describe the ADC resolution/reference and bus counts.
9
+ * This helper reads that authoritative data and produces a `SimBoard` whose
10
+ * pin layout, PWM/interrupt pins, analog ADC config, and bus counts match the
11
+ * real board — without hardcoding them.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { ArduinoUno } from '@typecad/board-arduino-uno';
16
+ * import { createBoardFromDefinition } from '@typecad/simulator';
17
+ *
18
+ * const board = createBoardFromDefinition(ArduinoUno);
19
+ * board.digital(13).asOutput().high(); // LED on PB5
20
+ * board.analog(0).injectVoltage(2.5); // A0, 10-bit / 5V reference
21
+ * ```
22
+ *
23
+ * The board package must be built (its `dist/`) so the `BoardDefinition` is
24
+ * importable at runtime.
25
+ *
26
+ * **Derived from the `BoardDefinition`:**
27
+ *
28
+ * | SimBoard field | BoardDefinition source |
29
+ * |----------------|------------------------|
30
+ * | `digitalPinCount` | `pins.digital.length` |
31
+ * | `analogPinCount` | `pins.analog.length` |
32
+ * | `pwmPins` | `pins.all` filtered by `capabilities.pwm` |
33
+ * | `interruptPins` | `pins.all` filtered by `capabilities.interrupt` (excluding `unsafe`) |
34
+ * | `i2cBusCount` | `peripherals.i2c.length` |
35
+ * | `spiBusCount` | `peripherals.spi.length` |
36
+ * | `uartCount` | `peripherals.uart.length` |
37
+ * | ADC resolution | `peripherals.adc[0].resolution` |
38
+ * | ADC reference | `peripherals.adc[0].referenceVoltage` |
39
+ *
40
+ * @param def - The board package's `BoardDefinition` (e.g. `ArduinoUno`).
41
+ * @returns A `SimBoard` configured to match the board.
42
+ */
43
+ export declare function createBoardFromDefinition(def: BoardDefinition): SimBoard;
@@ -0,0 +1,82 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/simulator — Derive a SimBoard from a BoardDefinition
3
+ // ---------------------------------------------------------------------------
4
+ import { createSimBoard } from './board-sim.js';
5
+ /**
6
+ * Build a simulated board from a board package's `BoardDefinition`.
7
+ *
8
+ * Every `@typecad/board-*` package exports a `BoardDefinition` whose `pins.all`
9
+ * entries carry real framework pin numbers and per-pin capability flags, and
10
+ * whose `peripherals` describe the ADC resolution/reference and bus counts.
11
+ * This helper reads that authoritative data and produces a `SimBoard` whose
12
+ * pin layout, PWM/interrupt pins, analog ADC config, and bus counts match the
13
+ * real board — without hardcoding them.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { ArduinoUno } from '@typecad/board-arduino-uno';
18
+ * import { createBoardFromDefinition } from '@typecad/simulator';
19
+ *
20
+ * const board = createBoardFromDefinition(ArduinoUno);
21
+ * board.digital(13).asOutput().high(); // LED on PB5
22
+ * board.analog(0).injectVoltage(2.5); // A0, 10-bit / 5V reference
23
+ * ```
24
+ *
25
+ * The board package must be built (its `dist/`) so the `BoardDefinition` is
26
+ * importable at runtime.
27
+ *
28
+ * **Derived from the `BoardDefinition`:**
29
+ *
30
+ * | SimBoard field | BoardDefinition source |
31
+ * |----------------|------------------------|
32
+ * | `digitalPinCount` | `pins.digital.length` |
33
+ * | `analogPinCount` | `pins.analog.length` |
34
+ * | `pwmPins` | `pins.all` filtered by `capabilities.pwm` |
35
+ * | `interruptPins` | `pins.all` filtered by `capabilities.interrupt` (excluding `unsafe`) |
36
+ * | `i2cBusCount` | `peripherals.i2c.length` |
37
+ * | `spiBusCount` | `peripherals.spi.length` |
38
+ * | `uartCount` | `peripherals.uart.length` |
39
+ * | ADC resolution | `peripherals.adc[0].resolution` |
40
+ * | ADC reference | `peripherals.adc[0].referenceVoltage` |
41
+ *
42
+ * @param def - The board package's `BoardDefinition` (e.g. `ArduinoUno`).
43
+ * @returns A `SimBoard` configured to match the board.
44
+ */
45
+ export function createBoardFromDefinition(def) {
46
+ const all = def.pins.all;
47
+ const pwmPins = all
48
+ .filter(p => p.capabilities?.pwm)
49
+ .map(p => p.number);
50
+ // Interrupt-capable pins, excluding any marked unsafe (e.g. boot-strap pins).
51
+ // The `interrupt` capability flag is set per-pin in the board definition's
52
+ // `pins.all`; for ATmega328P only PD2 (INT0) and PD3 (INT1) carry it, so
53
+ // this yields [2, 3] on Uno. Override with `createSimBoard({ interruptPins })`
54
+ // if your board's flagging differs from the interrupts you want to simulate.
55
+ const interruptPins = all
56
+ .filter(p => p.capabilities?.interrupt && !p.unsafe)
57
+ .map(p => p.number);
58
+ const board = createSimBoard({
59
+ boardType: def.id,
60
+ digitalPinCount: def.pins.digital.length,
61
+ analogPinCount: def.pins.analog.length,
62
+ i2cBusCount: def.peripherals.i2c?.length ?? 1,
63
+ spiBusCount: def.peripherals.spi?.length ?? 1,
64
+ uartCount: def.peripherals.uart?.length ?? 1,
65
+ pwmPins,
66
+ interruptPins,
67
+ });
68
+ // Apply the board's ADC resolution and reference voltage to every analog
69
+ // pin so readVoltage() produces board-accurate values out of the box.
70
+ const adc = def.peripherals.adc?.[0];
71
+ if (adc) {
72
+ for (const pin of board.analogPins.values()) {
73
+ if (typeof adc.resolution === 'number') {
74
+ pin.setResolution(adc.resolution);
75
+ }
76
+ if (typeof adc.referenceVoltage === 'number') {
77
+ pin.setAnalogReference(adc.referenceVoltage);
78
+ }
79
+ }
80
+ }
81
+ return board;
82
+ }
@@ -0,0 +1,61 @@
1
+ import { I2CStatus } from '../contracts.js';
2
+ import type { II2CBus, II2CDeviceAccessor, ErrorPolicy } from '../contracts.js';
3
+ import type { I2CAddress } from '@typecad/hal';
4
+ import type { ISimI2CDevice } from '../types.js';
5
+ /**
6
+ * Simulated I2C bus for testing I2C communication.
7
+ *
8
+ * Tests register mock devices via `attachDevice()`. When sketch code reads
9
+ * or writes to an address, the corresponding mock device handles the operation.
10
+ */
11
+ export declare class SimI2CBus implements II2CBus {
12
+ readonly busNumber: number;
13
+ isEnabled: boolean;
14
+ errorPolicy: ErrorPolicy;
15
+ private _devices;
16
+ private _speed;
17
+ private _slaveAddress;
18
+ private _errorHandler;
19
+ /** Track all operations for test assertions. */
20
+ private _log;
21
+ constructor(busNumber?: number);
22
+ begin(address?: I2CAddress): void;
23
+ end(): void;
24
+ setClock(hz: number): void;
25
+ device(address: I2CAddress): II2CDeviceAccessor;
26
+ onError(handler: (status: I2CStatus, address: I2CAddress, operation: 'read' | 'write') => void): void;
27
+ recover(): boolean;
28
+ /**
29
+ * Attach a mock I2C device at the given address.
30
+ */
31
+ attachDevice(address: I2CAddress, device: ISimI2CDevice): void;
32
+ /**
33
+ * Remove a mock device.
34
+ */
35
+ detachDevice(address: I2CAddress): void;
36
+ /**
37
+ * Get the operation log for test assertions.
38
+ */
39
+ getLog(): readonly I2COperationLog[];
40
+ /**
41
+ * Clear the operation log.
42
+ */
43
+ clearLog(): void;
44
+ /**
45
+ * Reset all state.
46
+ */
47
+ reset(): void;
48
+ /** @internal */
49
+ _getDevice(address: I2CAddress): ISimI2CDevice | undefined;
50
+ /** @internal */
51
+ _logOperation(op: I2COperationLog): void;
52
+ /** @internal */
53
+ _fireError(status: I2CStatus, address: I2CAddress, operation: 'read' | 'write'): void;
54
+ }
55
+ export interface I2COperationLog {
56
+ operation: 'read' | 'write';
57
+ address: I2CAddress;
58
+ register: number;
59
+ data?: number[];
60
+ timestamp: number;
61
+ }