@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
@@ -0,0 +1,155 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/simulator — I2C bus simulation
3
+ // ---------------------------------------------------------------------------
4
+ import { I2CStatus } from '../contracts.js';
5
+ // ---------------------------------------------------------------------------
6
+ // SimI2CBus
7
+ // ---------------------------------------------------------------------------
8
+ /**
9
+ * Simulated I2C bus for testing I2C communication.
10
+ *
11
+ * Tests register mock devices via `attachDevice()`. When sketch code reads
12
+ * or writes to an address, the corresponding mock device handles the operation.
13
+ */
14
+ export class SimI2CBus {
15
+ constructor(busNumber = 0) {
16
+ this.isEnabled = false;
17
+ this.errorPolicy = 'callback';
18
+ this._devices = new Map();
19
+ this._speed = 100000;
20
+ this._slaveAddress = null;
21
+ this._errorHandler = null;
22
+ /** Track all operations for test assertions. */
23
+ this._log = [];
24
+ this.busNumber = busNumber;
25
+ }
26
+ // --- II2CBus methods ---
27
+ begin(address) {
28
+ if (address !== undefined) {
29
+ this._slaveAddress = address;
30
+ }
31
+ this.isEnabled = true;
32
+ }
33
+ end() {
34
+ // In simulation, ending the bus disables it without releasing mock devices.
35
+ this.isEnabled = false;
36
+ }
37
+ setClock(hz) {
38
+ this._speed = hz;
39
+ }
40
+ device(address) {
41
+ return createDeviceAccessor(this, address);
42
+ }
43
+ onError(handler) {
44
+ this._errorHandler = handler;
45
+ }
46
+ recover() {
47
+ return true;
48
+ }
49
+ // --- Simulation helpers ---
50
+ /**
51
+ * Attach a mock I2C device at the given address.
52
+ */
53
+ attachDevice(address, device) {
54
+ this._devices.set(address, device);
55
+ }
56
+ /**
57
+ * Remove a mock device.
58
+ */
59
+ detachDevice(address) {
60
+ this._devices.delete(address);
61
+ }
62
+ /**
63
+ * Get the operation log for test assertions.
64
+ */
65
+ getLog() {
66
+ return this._log;
67
+ }
68
+ /**
69
+ * Clear the operation log.
70
+ */
71
+ clearLog() {
72
+ this._log = [];
73
+ }
74
+ /**
75
+ * Reset all state.
76
+ */
77
+ reset() {
78
+ this._devices.clear();
79
+ this._log = [];
80
+ this._errorHandler = null;
81
+ this.isEnabled = false;
82
+ this._speed = 100000;
83
+ this._slaveAddress = null;
84
+ }
85
+ // --- Internal ---
86
+ /** @internal */
87
+ _getDevice(address) {
88
+ return this._devices.get(address);
89
+ }
90
+ /** @internal */
91
+ _logOperation(op) {
92
+ this._log.push(op);
93
+ }
94
+ /** @internal */
95
+ _fireError(status, address, operation) {
96
+ if (this._errorHandler) {
97
+ this._errorHandler(status, address, operation);
98
+ }
99
+ }
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // Device accessor factory
103
+ // ---------------------------------------------------------------------------
104
+ function createDeviceAccessor(bus, address) {
105
+ return {
106
+ address,
107
+ readByte(register) {
108
+ const device = bus._getDevice(address);
109
+ const timestamp = Date.now();
110
+ if (!device) {
111
+ bus._logOperation({ operation: 'read', address, register, timestamp });
112
+ bus._fireError(I2CStatus.NACK_ON_ADDRESS, address, 'read');
113
+ return 0;
114
+ }
115
+ const data = device.read(register, 1);
116
+ bus._logOperation({ operation: 'read', address, register, data, timestamp });
117
+ return data[0] ?? 0;
118
+ },
119
+ readBytes(register, count) {
120
+ const device = bus._getDevice(address);
121
+ const timestamp = Date.now();
122
+ if (!device) {
123
+ bus._logOperation({ operation: 'read', address, register, timestamp });
124
+ bus._fireError(I2CStatus.NACK_ON_ADDRESS, address, 'read');
125
+ return new Uint8Array(0);
126
+ }
127
+ const data = device.read(register, count);
128
+ bus._logOperation({ operation: 'read', address, register, data, timestamp });
129
+ return new Uint8Array(data);
130
+ },
131
+ writeByte(register, value) {
132
+ const device = bus._getDevice(address);
133
+ const timestamp = Date.now();
134
+ if (!device) {
135
+ bus._logOperation({ operation: 'write', address, register, data: [value], timestamp });
136
+ bus._fireError(I2CStatus.NACK_ON_ADDRESS, address, 'write');
137
+ return;
138
+ }
139
+ device.write(register, [value]);
140
+ bus._logOperation({ operation: 'write', address, register, data: [value], timestamp });
141
+ },
142
+ writeBytes(register, data) {
143
+ const device = bus._getDevice(address);
144
+ const dataArray = Array.isArray(data) ? data : Array.from(data);
145
+ const timestamp = Date.now();
146
+ if (!device) {
147
+ bus._logOperation({ operation: 'write', address, register, data: dataArray, timestamp });
148
+ bus._fireError(I2CStatus.NACK_ON_ADDRESS, address, 'write');
149
+ return;
150
+ }
151
+ device.write(register, dataArray);
152
+ bus._logOperation({ operation: 'write', address, register, data: dataArray, timestamp });
153
+ },
154
+ };
155
+ }
@@ -0,0 +1,71 @@
1
+ import { UARTStatus } from '../contracts.js';
2
+ import type { ISerialPort, UARTStatusInfo, ErrorPolicy } from '../contracts.js';
3
+ /**
4
+ * Simulated serial port for testing UART communication.
5
+ *
6
+ * Tests can inject bytes into the RX buffer and inspect the TX buffer
7
+ * to verify that sketch code sends the expected data.
8
+ */
9
+ export declare class SimSerialPort implements ISerialPort {
10
+ readonly uartNumber: number;
11
+ baudRate: number;
12
+ isEnabled: boolean;
13
+ errorPolicy: ErrorPolicy;
14
+ private _rxBuffer;
15
+ private _txBuffer;
16
+ private _rxBufferSize;
17
+ private _txBufferSize;
18
+ private _overrunError;
19
+ private _parityError;
20
+ private _framingError;
21
+ private _breakDetected;
22
+ private _onReceiveCallback;
23
+ private _onTransmitCompleteCallback;
24
+ private _onErrorCallback;
25
+ constructor(uartNumber?: number, rxBufferSize?: number, txBufferSize?: number);
26
+ begin(baud: number): void;
27
+ end(): void;
28
+ read(): number;
29
+ peek(): number;
30
+ readLine(): string;
31
+ readBytes(count: number): Uint8Array;
32
+ readString(): string;
33
+ available(): number;
34
+ write(data: number | Uint8Array | string): number;
35
+ flush(): void;
36
+ getStatus(): UARTStatusInfo;
37
+ clearErrors(): void;
38
+ onReceive(callback: (bytesAvailable: number) => void): void;
39
+ onTransmitComplete(callback: () => void): void;
40
+ onError(callback: (status: UARTStatus) => void): void;
41
+ print(...args: unknown[]): void;
42
+ println(...args: unknown[]): void;
43
+ printf(format: string, ...args: unknown[]): void;
44
+ isConnected(): boolean;
45
+ waitForConnection(_timeout?: number): Promise<void>;
46
+ /**
47
+ * Inject bytes into the RX buffer (simulating received data).
48
+ * Triggers onReceive callback if registered.
49
+ */
50
+ injectRx(data: Uint8Array | number[] | string): void;
51
+ /**
52
+ * Get all bytes written to the TX buffer since the last call.
53
+ * Clears the TX buffer.
54
+ */
55
+ flushTx(): number[];
56
+ /**
57
+ * Get the TX buffer contents without clearing.
58
+ */
59
+ peekTx(): readonly number[];
60
+ /**
61
+ * Get the TX buffer contents as a string.
62
+ */
63
+ peekTxAsString(): string;
64
+ /**
65
+ * Reset all buffers and state.
66
+ */
67
+ reset(): void;
68
+ private _pushTxBytes;
69
+ private _encodeString;
70
+ private _formatString;
71
+ }
@@ -0,0 +1,241 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/simulator — Serial port simulation
3
+ // ---------------------------------------------------------------------------
4
+ // ---------------------------------------------------------------------------
5
+ // SimSerialPort
6
+ // ---------------------------------------------------------------------------
7
+ /**
8
+ * Simulated serial port for testing UART communication.
9
+ *
10
+ * Tests can inject bytes into the RX buffer and inspect the TX buffer
11
+ * to verify that sketch code sends the expected data.
12
+ */
13
+ export class SimSerialPort {
14
+ constructor(uartNumber = 0, rxBufferSize = 256, txBufferSize = 256) {
15
+ this.baudRate = 0;
16
+ this.isEnabled = false;
17
+ this.errorPolicy = 'callback';
18
+ this._rxBuffer = [];
19
+ this._txBuffer = [];
20
+ this._overrunError = false;
21
+ this._parityError = false;
22
+ this._framingError = false;
23
+ this._breakDetected = false;
24
+ this._onReceiveCallback = null;
25
+ this._onTransmitCompleteCallback = null;
26
+ this._onErrorCallback = null;
27
+ this.uartNumber = uartNumber;
28
+ this._rxBufferSize = rxBufferSize;
29
+ this._txBufferSize = txBufferSize;
30
+ }
31
+ // --- IUARTBus methods ---
32
+ begin(baud) {
33
+ this.baudRate = baud;
34
+ this.isEnabled = true;
35
+ }
36
+ end() {
37
+ // In simulation, ending the port disables it without destroying buffers.
38
+ this.isEnabled = false;
39
+ }
40
+ read() {
41
+ if (this._rxBuffer.length === 0)
42
+ return -1;
43
+ return this._rxBuffer.shift();
44
+ }
45
+ peek() {
46
+ if (this._rxBuffer.length === 0)
47
+ return -1;
48
+ return this._rxBuffer[0];
49
+ }
50
+ readLine() {
51
+ const idx = this._rxBuffer.findIndex(b => b === 0x0A);
52
+ if (idx === -1)
53
+ return '';
54
+ const lineBytes = this._rxBuffer.splice(0, idx + 1);
55
+ return new TextDecoder().decode(new Uint8Array(lineBytes)).trimEnd();
56
+ }
57
+ readBytes(count) {
58
+ if (this._rxBuffer.length < count) {
59
+ const available = this._rxBuffer.splice(0, this._rxBuffer.length);
60
+ return new Uint8Array(available);
61
+ }
62
+ const result = this._rxBuffer.splice(0, count);
63
+ return new Uint8Array(result);
64
+ }
65
+ readString() {
66
+ const result = new Uint8Array(this._rxBuffer);
67
+ this._rxBuffer = [];
68
+ return new TextDecoder().decode(result);
69
+ }
70
+ available() {
71
+ return this._rxBuffer.length;
72
+ }
73
+ write(data) {
74
+ const bytes = typeof data === 'number'
75
+ ? [data & 0xFF]
76
+ : typeof data === 'string'
77
+ ? this._encodeString(data)
78
+ : Array.from(data);
79
+ return this._pushTxBytes(bytes);
80
+ }
81
+ flush() {
82
+ // In simulation, data is "sent" immediately when written to TX buffer.
83
+ // Flush triggers the transmit complete callback.
84
+ if (this._onTransmitCompleteCallback) {
85
+ this._onTransmitCompleteCallback();
86
+ }
87
+ }
88
+ getStatus() {
89
+ return {
90
+ available: this._rxBuffer.length,
91
+ writeAvailable: this._txBufferSize - this._txBuffer.length,
92
+ overrunError: this._overrunError,
93
+ parityError: this._parityError,
94
+ framingError: this._framingError,
95
+ breakDetected: this._breakDetected,
96
+ };
97
+ }
98
+ clearErrors() {
99
+ this._overrunError = false;
100
+ this._parityError = false;
101
+ this._framingError = false;
102
+ this._breakDetected = false;
103
+ }
104
+ onReceive(callback) {
105
+ this._onReceiveCallback = callback;
106
+ }
107
+ onTransmitComplete(callback) {
108
+ this._onTransmitCompleteCallback = callback;
109
+ }
110
+ onError(callback) {
111
+ this._onErrorCallback = callback;
112
+ }
113
+ // --- ISerialPort methods ---
114
+ print(...args) {
115
+ const text = args.map(a => String(a)).join('');
116
+ this.write(text);
117
+ }
118
+ println(...args) {
119
+ const text = args.map(a => String(a)).join('');
120
+ this.write(text + '\r\n');
121
+ }
122
+ printf(format, ...args) {
123
+ const text = this._formatString(format, args);
124
+ this.write(text);
125
+ }
126
+ isConnected() {
127
+ return this.isEnabled;
128
+ }
129
+ async waitForConnection(_timeout) {
130
+ // In simulation, just resolve immediately
131
+ }
132
+ // --- Simulation helpers ---
133
+ /**
134
+ * Inject bytes into the RX buffer (simulating received data).
135
+ * Triggers onReceive callback if registered.
136
+ */
137
+ injectRx(data) {
138
+ const bytes = typeof data === 'string'
139
+ ? this._encodeString(data)
140
+ : Array.isArray(data)
141
+ ? data
142
+ : Array.from(data);
143
+ for (const b of bytes) {
144
+ if (this._rxBuffer.length >= this._rxBufferSize) {
145
+ this._overrunError = true;
146
+ break;
147
+ }
148
+ this._rxBuffer.push(b & 0xFF);
149
+ }
150
+ if (this._onReceiveCallback) {
151
+ this._onReceiveCallback(this._rxBuffer.length);
152
+ }
153
+ }
154
+ /**
155
+ * Get all bytes written to the TX buffer since the last call.
156
+ * Clears the TX buffer.
157
+ */
158
+ flushTx() {
159
+ const result = this._txBuffer.slice();
160
+ this._txBuffer = [];
161
+ if (this._onTransmitCompleteCallback) {
162
+ this._onTransmitCompleteCallback();
163
+ }
164
+ return result;
165
+ }
166
+ /**
167
+ * Get the TX buffer contents without clearing.
168
+ */
169
+ peekTx() {
170
+ return this._txBuffer;
171
+ }
172
+ /**
173
+ * Get the TX buffer contents as a string.
174
+ */
175
+ peekTxAsString() {
176
+ return new TextDecoder().decode(new Uint8Array(this._txBuffer));
177
+ }
178
+ /**
179
+ * Reset all buffers and state.
180
+ */
181
+ reset() {
182
+ this._rxBuffer = [];
183
+ this._txBuffer = [];
184
+ this._overrunError = false;
185
+ this._parityError = false;
186
+ this._framingError = false;
187
+ this._breakDetected = false;
188
+ this.isEnabled = false;
189
+ this.baudRate = 0;
190
+ }
191
+ // --- Private helpers ---
192
+ _pushTxBytes(bytes) {
193
+ let written = 0;
194
+ for (const b of bytes) {
195
+ if (this._txBuffer.length >= this._txBufferSize)
196
+ break;
197
+ this._txBuffer.push(b & 0xFF);
198
+ written++;
199
+ }
200
+ return written;
201
+ }
202
+ _encodeString(text) {
203
+ return Array.from(new TextEncoder().encode(text));
204
+ }
205
+ _formatString(fmt, args) {
206
+ let result = '';
207
+ let argIdx = 0;
208
+ for (let i = 0; i < fmt.length; i++) {
209
+ if (fmt[i] === '{' && fmt[i + 1] === '}') {
210
+ result += String(args[argIdx++] ?? '');
211
+ i++;
212
+ }
213
+ else if (fmt[i] === '%' && i + 1 < fmt.length) {
214
+ const spec = fmt[i + 1];
215
+ if (spec === 'd' || spec === 'i') {
216
+ result += String(Number(args[argIdx++]) || 0);
217
+ i++;
218
+ }
219
+ else if (spec === 's') {
220
+ result += String(args[argIdx++] ?? '');
221
+ i++;
222
+ }
223
+ else if (spec === 'f') {
224
+ result += String(Number(args[argIdx++]) || 0.0);
225
+ i++;
226
+ }
227
+ else if (spec === '%') {
228
+ result += '%';
229
+ i++;
230
+ }
231
+ else {
232
+ result += fmt[i];
233
+ }
234
+ }
235
+ else {
236
+ result += fmt[i];
237
+ }
238
+ }
239
+ return result;
240
+ }
241
+ }
@@ -0,0 +1,65 @@
1
+ import { SPIStatus } from '../contracts.js';
2
+ import type { ISPIBus, ISPIDevice, BasePin, ErrorPolicy } from '../contracts.js';
3
+ import type { SPIMode, SPIBitOrder, SPISettings } from '@typecad/hal';
4
+ import type { ISimSPIDevice } from '../types.js';
5
+ /**
6
+ * Simulated SPI bus for testing SPI communication.
7
+ *
8
+ * Tests register mock devices via `attachDevice()`. When sketch code performs
9
+ * transfers, the corresponding mock device handles the operation.
10
+ */
11
+ export declare class SimSPIBus implements ISPIBus {
12
+ isEnabled: boolean;
13
+ errorPolicy: ErrorPolicy;
14
+ private _frequency;
15
+ private _mode;
16
+ private _bitOrder;
17
+ private _devices;
18
+ private _inTransaction;
19
+ /** Track all operations for test assertions. */
20
+ private _log;
21
+ begin(): void;
22
+ end(): void;
23
+ setMode(mode: SPIMode): void;
24
+ setBitOrder(order: SPIBitOrder): void;
25
+ setFrequency(hz: number): void;
26
+ beginTransaction(_settings: SPISettings): void;
27
+ endTransaction(): void;
28
+ transfer(data: number | Uint8Array): Uint8Array;
29
+ write(data: number | Uint8Array): void;
30
+ write16(value: number): void;
31
+ device(chipSelect: BasePin): ISPIDevice;
32
+ /**
33
+ * Attach a mock SPI device for the given chip-select pin.
34
+ */
35
+ attachDevice(csPin: BasePin, device: ISimSPIDevice): void;
36
+ /**
37
+ * Remove a mock device.
38
+ */
39
+ detachDevice(csPin: BasePin): void;
40
+ /**
41
+ * Get the operation log for test assertions.
42
+ */
43
+ getLog(): readonly SPIOperationLog[];
44
+ /**
45
+ * Clear the operation log.
46
+ */
47
+ clearLog(): void;
48
+ onError(_handler: (status: SPIStatus, operation: 'transfer' | 'read' | 'write') => void): void;
49
+ /**
50
+ * Reset all state.
51
+ */
52
+ reset(): void;
53
+ /** @internal */
54
+ _getDevice(csKey: string): ISimSPIDevice | undefined;
55
+ /** @internal */
56
+ _logOperation(op: SPIOperationLog): void;
57
+ }
58
+ export interface SPIOperationLog {
59
+ operation: 'read' | 'write' | 'transfer';
60
+ register?: number;
61
+ count?: number;
62
+ data?: number[];
63
+ response?: number[];
64
+ timestamp: number;
65
+ }