@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.
- package/LICENSE +21 -0
- package/README.md +148 -0
- package/dist/board/board-sim.d.ts +52 -0
- package/dist/board/board-sim.js +188 -0
- package/dist/board/from-definition.d.ts +43 -0
- package/dist/board/from-definition.js +82 -0
- package/dist/bus/i2c-sim.d.ts +61 -0
- package/dist/bus/i2c-sim.js +155 -0
- package/dist/bus/serial-sim.d.ts +71 -0
- package/dist/bus/serial-sim.js +241 -0
- package/dist/bus/spi-sim.d.ts +65 -0
- package/dist/bus/spi-sim.js +202 -0
- package/dist/contracts.d.ts +223 -0
- package/dist/contracts.js +87 -0
- package/dist/gpio/analog-pin-sim.d.ts +40 -0
- package/dist/gpio/analog-pin-sim.js +75 -0
- package/dist/gpio/digital-pin-sim.d.ts +68 -0
- package/dist/gpio/digital-pin-sim.js +172 -0
- package/dist/gpio/interrupt-sim.d.ts +69 -0
- package/dist/gpio/interrupt-sim.js +118 -0
- package/dist/gpio/pwm-pin-sim.d.ts +41 -0
- package/dist/gpio/pwm-pin-sim.js +85 -0
- package/dist/helpers.d.ts +40 -0
- package/dist/helpers.js +57 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +21 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.js +4 -0
- package/package.json +62 -0
- package/src/board/board-sim.ts +208 -0
- package/src/board/from-definition.ts +91 -0
- package/src/bus/i2c-sim.ts +212 -0
- package/src/bus/serial-sim.ts +280 -0
- package/src/bus/spi-sim.ts +269 -0
- package/src/contracts.ts +330 -0
- package/src/gpio/analog-pin-sim.ts +91 -0
- package/src/gpio/digital-pin-sim.ts +234 -0
- package/src/gpio/interrupt-sim.ts +151 -0
- package/src/gpio/pwm-pin-sim.ts +103 -0
- package/src/helpers.ts +100 -0
- package/src/index.ts +59 -0
- 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
|
+
}
|