@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
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// @typecad/simulator — SPI bus simulation
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// SimSPIBus
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
/**
|
|
8
|
+
* Simulated SPI bus for testing SPI communication.
|
|
9
|
+
*
|
|
10
|
+
* Tests register mock devices via `attachDevice()`. When sketch code performs
|
|
11
|
+
* transfers, the corresponding mock device handles the operation.
|
|
12
|
+
*/
|
|
13
|
+
export class SimSPIBus {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.isEnabled = false;
|
|
16
|
+
this.errorPolicy = 'callback';
|
|
17
|
+
this._frequency = 4000000;
|
|
18
|
+
this._mode = 0;
|
|
19
|
+
this._bitOrder = 'msb';
|
|
20
|
+
this._devices = new Map();
|
|
21
|
+
this._inTransaction = false;
|
|
22
|
+
/** Track all operations for test assertions. */
|
|
23
|
+
this._log = [];
|
|
24
|
+
}
|
|
25
|
+
// --- ISPIBus methods ---
|
|
26
|
+
begin() {
|
|
27
|
+
this.isEnabled = true;
|
|
28
|
+
}
|
|
29
|
+
end() {
|
|
30
|
+
// In simulation, ending the bus disables it without releasing mock devices.
|
|
31
|
+
this.isEnabled = false;
|
|
32
|
+
}
|
|
33
|
+
setMode(mode) {
|
|
34
|
+
this._mode = mode;
|
|
35
|
+
}
|
|
36
|
+
setBitOrder(order) {
|
|
37
|
+
this._bitOrder = order;
|
|
38
|
+
}
|
|
39
|
+
setFrequency(hz) {
|
|
40
|
+
this._frequency = hz;
|
|
41
|
+
}
|
|
42
|
+
beginTransaction(_settings) {
|
|
43
|
+
this._inTransaction = true;
|
|
44
|
+
}
|
|
45
|
+
endTransaction() {
|
|
46
|
+
this._inTransaction = false;
|
|
47
|
+
}
|
|
48
|
+
transfer(data) {
|
|
49
|
+
const mosiData = typeof data === 'number' ? [data] : Array.from(data);
|
|
50
|
+
const timestamp = Date.now();
|
|
51
|
+
// Bus-level transfer (no CS pin) — log but no device interaction
|
|
52
|
+
this._log.push({ operation: 'transfer', data: mosiData, timestamp });
|
|
53
|
+
return new Uint8Array(mosiData.length);
|
|
54
|
+
}
|
|
55
|
+
write(data) {
|
|
56
|
+
const mosiData = typeof data === 'number' ? [data] : Array.from(data);
|
|
57
|
+
const timestamp = Date.now();
|
|
58
|
+
this._log.push({ operation: 'write', data: mosiData, timestamp });
|
|
59
|
+
}
|
|
60
|
+
write16(value) {
|
|
61
|
+
const msb = (value >> 8) & 0xFF;
|
|
62
|
+
const lsb = value & 0xFF;
|
|
63
|
+
this.write(new Uint8Array([msb, lsb]));
|
|
64
|
+
}
|
|
65
|
+
device(chipSelect) {
|
|
66
|
+
return new SimSPIDevice(this, chipSelect);
|
|
67
|
+
}
|
|
68
|
+
// --- Simulation helpers ---
|
|
69
|
+
/**
|
|
70
|
+
* Attach a mock SPI device for the given chip-select pin.
|
|
71
|
+
*/
|
|
72
|
+
attachDevice(csPin, device) {
|
|
73
|
+
this._devices.set(String(csPin.number), device);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Remove a mock device.
|
|
77
|
+
*/
|
|
78
|
+
detachDevice(csPin) {
|
|
79
|
+
this._devices.delete(String(csPin.number));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the operation log for test assertions.
|
|
83
|
+
*/
|
|
84
|
+
getLog() {
|
|
85
|
+
return this._log;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Clear the operation log.
|
|
89
|
+
*/
|
|
90
|
+
clearLog() {
|
|
91
|
+
this._log = [];
|
|
92
|
+
}
|
|
93
|
+
onError(_handler) {
|
|
94
|
+
// No-op in simulator
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Reset all state.
|
|
98
|
+
*/
|
|
99
|
+
reset() {
|
|
100
|
+
this._devices.clear();
|
|
101
|
+
this._log = [];
|
|
102
|
+
this.isEnabled = false;
|
|
103
|
+
this._inTransaction = false;
|
|
104
|
+
this._frequency = 4000000;
|
|
105
|
+
this._mode = 0;
|
|
106
|
+
this._bitOrder = 'msb';
|
|
107
|
+
}
|
|
108
|
+
// --- Internal ---
|
|
109
|
+
/** @internal */
|
|
110
|
+
_getDevice(csKey) {
|
|
111
|
+
return this._devices.get(csKey);
|
|
112
|
+
}
|
|
113
|
+
/** @internal */
|
|
114
|
+
_logOperation(op) {
|
|
115
|
+
this._log.push(op);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// SimSPIDevice — implements ISPIDevice
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
class SimSPIDevice {
|
|
122
|
+
constructor(bus, chipSelect) {
|
|
123
|
+
this.bus = bus;
|
|
124
|
+
this._simBus = bus;
|
|
125
|
+
this.chipSelect = chipSelect;
|
|
126
|
+
}
|
|
127
|
+
transfer(data) {
|
|
128
|
+
const csKey = String(this.chipSelect.number);
|
|
129
|
+
const mosiData = typeof data === 'number' ? [data] : Array.from(data);
|
|
130
|
+
const timestamp = Date.now();
|
|
131
|
+
const device = this._simBus._getDevice(csKey);
|
|
132
|
+
if (!device) {
|
|
133
|
+
this._simBus._logOperation({ operation: 'transfer', data: mosiData, timestamp });
|
|
134
|
+
return new Uint8Array(0);
|
|
135
|
+
}
|
|
136
|
+
const misoData = device.transfer(mosiData);
|
|
137
|
+
this._simBus._logOperation({ operation: 'transfer', data: mosiData, response: misoData, timestamp });
|
|
138
|
+
return new Uint8Array(misoData);
|
|
139
|
+
}
|
|
140
|
+
write(data) {
|
|
141
|
+
const csKey = String(this.chipSelect.number);
|
|
142
|
+
const dataArray = typeof data === 'number' ? [data] : Array.from(data);
|
|
143
|
+
const timestamp = Date.now();
|
|
144
|
+
const device = this._simBus._getDevice(csKey);
|
|
145
|
+
if (!device) {
|
|
146
|
+
this._simBus._logOperation({ operation: 'write', data: dataArray, timestamp });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (device.write) {
|
|
150
|
+
// Write without register — use register 0 as placeholder
|
|
151
|
+
device.write(0, dataArray);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
device.transfer(dataArray);
|
|
155
|
+
}
|
|
156
|
+
this._simBus._logOperation({ operation: 'write', data: dataArray, timestamp });
|
|
157
|
+
}
|
|
158
|
+
read(count) {
|
|
159
|
+
const csKey = String(this.chipSelect.number);
|
|
160
|
+
const timestamp = Date.now();
|
|
161
|
+
const device = this._simBus._getDevice(csKey);
|
|
162
|
+
if (!device) {
|
|
163
|
+
this._simBus._logOperation({ operation: 'read', count, timestamp });
|
|
164
|
+
return new Uint8Array(0);
|
|
165
|
+
}
|
|
166
|
+
// Send dummy bytes to read
|
|
167
|
+
const misoData = device.transfer(new Array(count).fill(0));
|
|
168
|
+
this._simBus._logOperation({ operation: 'read', count, data: misoData, timestamp });
|
|
169
|
+
return new Uint8Array(misoData);
|
|
170
|
+
}
|
|
171
|
+
writeRegister(register, data) {
|
|
172
|
+
const csKey = String(this.chipSelect.number);
|
|
173
|
+
const dataArray = typeof data === 'number' ? [data] : Array.from(data);
|
|
174
|
+
const timestamp = Date.now();
|
|
175
|
+
const device = this._simBus._getDevice(csKey);
|
|
176
|
+
if (!device) {
|
|
177
|
+
this._simBus._logOperation({ operation: 'write', register, data: dataArray, timestamp });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (device.write) {
|
|
181
|
+
device.write(register, dataArray);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
device.transfer([register, ...dataArray]);
|
|
185
|
+
}
|
|
186
|
+
this._simBus._logOperation({ operation: 'write', register, data: dataArray, timestamp });
|
|
187
|
+
}
|
|
188
|
+
readRegister(register, count) {
|
|
189
|
+
const csKey = String(this.chipSelect.number);
|
|
190
|
+
const timestamp = Date.now();
|
|
191
|
+
const device = this._simBus._getDevice(csKey);
|
|
192
|
+
if (!device) {
|
|
193
|
+
this._simBus._logOperation({ operation: 'read', register, count, timestamp });
|
|
194
|
+
return new Uint8Array(0);
|
|
195
|
+
}
|
|
196
|
+
const data = device.readRegister
|
|
197
|
+
? device.readRegister(register, count)
|
|
198
|
+
: device.transfer([register, ...new Array(count).fill(0)]).slice(1);
|
|
199
|
+
this._simBus._logOperation({ operation: 'read', register, count, data, timestamp });
|
|
200
|
+
return new Uint8Array(data);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type { DigitalValue, AnalogValue, InterruptHandler, I2CAddress } from '@typecad/hal';
|
|
2
|
+
import type { SPIMode, SPIBitOrder, SPISettings } from '@typecad/hal';
|
|
3
|
+
export interface PinCapabilityFlags {
|
|
4
|
+
digitalInput: boolean;
|
|
5
|
+
digitalOutput: boolean;
|
|
6
|
+
analogInput: boolean;
|
|
7
|
+
/** DAC output */
|
|
8
|
+
analogOutput: boolean;
|
|
9
|
+
pwm: boolean;
|
|
10
|
+
interrupt: boolean;
|
|
11
|
+
pullUp: boolean;
|
|
12
|
+
pullDown: boolean;
|
|
13
|
+
touch: boolean;
|
|
14
|
+
openDrain: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface IToneAttachment {
|
|
17
|
+
for(duration: number): void;
|
|
18
|
+
}
|
|
19
|
+
export interface InterruptOptions {
|
|
20
|
+
debounce?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface BasePin {
|
|
23
|
+
readonly number: number;
|
|
24
|
+
readonly gpio: number;
|
|
25
|
+
readonly capabilities: PinCapabilityFlags;
|
|
26
|
+
read(): DigitalValue;
|
|
27
|
+
isHigh(): boolean;
|
|
28
|
+
isLow(): boolean;
|
|
29
|
+
write(value: DigitalValue): void;
|
|
30
|
+
high(): void;
|
|
31
|
+
low(): void;
|
|
32
|
+
toggle(): void;
|
|
33
|
+
pulse(duration: number): void;
|
|
34
|
+
tone(frequency: number): IToneAttachment;
|
|
35
|
+
noTone(): void;
|
|
36
|
+
inputPullUp(): void;
|
|
37
|
+
inputPullDown?(): void;
|
|
38
|
+
outputOpenDrain(initial?: DigitalValue): void;
|
|
39
|
+
asOutput(initial?: DigitalValue): IOutputModePin;
|
|
40
|
+
asInput(): IInputModePin;
|
|
41
|
+
asInputPullUp(): IInputModePin;
|
|
42
|
+
pwm?(percent: number): void;
|
|
43
|
+
getPwmFrequency?(): number;
|
|
44
|
+
getPwmResolution?(): number;
|
|
45
|
+
readAnalog?(): AnalogValue;
|
|
46
|
+
readVoltage?(): number;
|
|
47
|
+
setAnalogReference?(voltage: number): void;
|
|
48
|
+
getAnalogResolution?(): number;
|
|
49
|
+
onRising?(handler: InterruptHandler, options?: InterruptOptions): void;
|
|
50
|
+
onFalling?(handler: InterruptHandler, options?: InterruptOptions): void;
|
|
51
|
+
onChange?(handler: InterruptHandler, options?: InterruptOptions): void;
|
|
52
|
+
offInterrupts?(): void;
|
|
53
|
+
waitForRising(timeout?: number): Promise<void>;
|
|
54
|
+
waitForFalling(timeout?: number): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
export interface IOutputModePin {
|
|
57
|
+
readonly number: number;
|
|
58
|
+
readonly gpio: number;
|
|
59
|
+
readonly capabilities: PinCapabilityFlags;
|
|
60
|
+
write(value: DigitalValue): void;
|
|
61
|
+
high(): void;
|
|
62
|
+
low(): void;
|
|
63
|
+
toggle(): void;
|
|
64
|
+
pulse(duration: number): void;
|
|
65
|
+
tone(frequency: number): IToneAttachment;
|
|
66
|
+
noTone(): void;
|
|
67
|
+
pwm?(percent: number): void;
|
|
68
|
+
getPwmFrequency?(): number;
|
|
69
|
+
getPwmResolution?(): number;
|
|
70
|
+
asOutput(initial?: DigitalValue): IOutputModePin;
|
|
71
|
+
asInput(): IInputModePin;
|
|
72
|
+
asInputPullUp(): IInputModePin;
|
|
73
|
+
inputPullUp(): void;
|
|
74
|
+
inputPullDown?(): void;
|
|
75
|
+
outputOpenDrain(initial?: DigitalValue): void;
|
|
76
|
+
}
|
|
77
|
+
export interface IInputModePin {
|
|
78
|
+
readonly number: number;
|
|
79
|
+
readonly gpio: number;
|
|
80
|
+
readonly capabilities: PinCapabilityFlags;
|
|
81
|
+
read(): DigitalValue;
|
|
82
|
+
isHigh(): boolean;
|
|
83
|
+
isLow(): boolean;
|
|
84
|
+
readAnalog?(): AnalogValue;
|
|
85
|
+
readVoltage?(): number;
|
|
86
|
+
setAnalogReference?(voltage: number): void;
|
|
87
|
+
getAnalogResolution?(): number;
|
|
88
|
+
onRising?(handler: InterruptHandler, options?: InterruptOptions): void;
|
|
89
|
+
onFalling?(handler: InterruptHandler, options?: InterruptOptions): void;
|
|
90
|
+
onChange?(handler: InterruptHandler, options?: InterruptOptions): void;
|
|
91
|
+
offInterrupts?(): void;
|
|
92
|
+
waitForRising(timeout?: number): Promise<void>;
|
|
93
|
+
waitForFalling(timeout?: number): Promise<void>;
|
|
94
|
+
asOutput(initial?: DigitalValue): IOutputModePin;
|
|
95
|
+
asInput(): IInputModePin;
|
|
96
|
+
asInputPullUp(): IInputModePin;
|
|
97
|
+
inputPullUp(): void;
|
|
98
|
+
inputPullDown?(): void;
|
|
99
|
+
outputOpenDrain(initial?: DigitalValue): void;
|
|
100
|
+
}
|
|
101
|
+
/** Pin with PWM output capability. */
|
|
102
|
+
export type PWMPin = BasePin & {
|
|
103
|
+
pwm: NonNullable<BasePin['pwm']>;
|
|
104
|
+
};
|
|
105
|
+
/** Pin with analog input capability. */
|
|
106
|
+
export type AnalogPin = BasePin & {
|
|
107
|
+
readAnalog: NonNullable<BasePin['readAnalog']>;
|
|
108
|
+
};
|
|
109
|
+
/** Pin with interrupt capability. */
|
|
110
|
+
export type InterruptPin = BasePin & {
|
|
111
|
+
onRising: NonNullable<BasePin['onRising']>;
|
|
112
|
+
};
|
|
113
|
+
export declare function hasPWM(pin: unknown): pin is PWMPin;
|
|
114
|
+
export declare function hasAnalogInput(pin: unknown): pin is AnalogPin;
|
|
115
|
+
export declare function hasInterrupt(pin: unknown): pin is InterruptPin;
|
|
116
|
+
export declare function assertPWM(pin: BasePin, message?: string): asserts pin is PWMPin;
|
|
117
|
+
export declare function assertAnalog(pin: BasePin, message?: string): asserts pin is AnalogPin;
|
|
118
|
+
export declare function assertInterrupt(pin: BasePin, message?: string): asserts pin is InterruptPin;
|
|
119
|
+
export type ErrorPolicy = 'throw' | 'callback' | 'silent';
|
|
120
|
+
export declare enum I2CStatus {
|
|
121
|
+
SUCCESS = 0,
|
|
122
|
+
DATA_TOO_LONG = 1,
|
|
123
|
+
NACK_ON_ADDRESS = 2,
|
|
124
|
+
NACK_ON_DATA = 3,
|
|
125
|
+
OTHER_ERROR = 4,
|
|
126
|
+
PARTIAL_READ = 5
|
|
127
|
+
}
|
|
128
|
+
export interface II2CDeviceAccessor {
|
|
129
|
+
readonly address: I2CAddress;
|
|
130
|
+
readByte(register: number): number;
|
|
131
|
+
readBytes(register: number, count: number): Uint8Array;
|
|
132
|
+
writeByte(register: number, value: number): void;
|
|
133
|
+
writeBytes(register: number, data: Uint8Array | number[]): void;
|
|
134
|
+
}
|
|
135
|
+
export interface II2CBus {
|
|
136
|
+
readonly busNumber: number;
|
|
137
|
+
readonly isEnabled: boolean;
|
|
138
|
+
begin(): void;
|
|
139
|
+
begin(address: I2CAddress): void;
|
|
140
|
+
end(): void;
|
|
141
|
+
setClock(hz: number): void;
|
|
142
|
+
device(address: I2CAddress): II2CDeviceAccessor;
|
|
143
|
+
onError(handler: (status: I2CStatus, address: I2CAddress, operation: 'read' | 'write') => void): void;
|
|
144
|
+
errorPolicy: ErrorPolicy;
|
|
145
|
+
recover(): boolean;
|
|
146
|
+
}
|
|
147
|
+
export declare enum SPIStatus {
|
|
148
|
+
SUCCESS = 0,
|
|
149
|
+
NOT_INITIALIZED = 1,
|
|
150
|
+
TRANSFER_FAILED = 2,
|
|
151
|
+
INVALID_CONFIG = 3,
|
|
152
|
+
TIMEOUT = 4,
|
|
153
|
+
DEVICE_ERROR = 5
|
|
154
|
+
}
|
|
155
|
+
export interface ISPIDevice {
|
|
156
|
+
readonly chipSelect: BasePin;
|
|
157
|
+
transfer(data: number | Uint8Array): Uint8Array;
|
|
158
|
+
write(data: number | Uint8Array): void;
|
|
159
|
+
read(count: number): Uint8Array;
|
|
160
|
+
writeRegister(register: number, data: number | Uint8Array): void;
|
|
161
|
+
readRegister(register: number, count: number): Uint8Array;
|
|
162
|
+
}
|
|
163
|
+
export interface ISPIBus {
|
|
164
|
+
readonly isEnabled: boolean;
|
|
165
|
+
begin(): void;
|
|
166
|
+
end(): void;
|
|
167
|
+
setMode(mode: SPIMode): void;
|
|
168
|
+
setBitOrder(order: SPIBitOrder): void;
|
|
169
|
+
setFrequency(hz: number): void;
|
|
170
|
+
beginTransaction(settings: SPISettings): void;
|
|
171
|
+
endTransaction(): void;
|
|
172
|
+
device(chipSelect: BasePin): ISPIDevice;
|
|
173
|
+
onError(handler: (status: SPIStatus, operation: 'transfer' | 'read' | 'write') => void): void;
|
|
174
|
+
errorPolicy: ErrorPolicy;
|
|
175
|
+
}
|
|
176
|
+
export declare enum UARTStatus {
|
|
177
|
+
SUCCESS = 0,
|
|
178
|
+
NOT_INITIALIZED = 1,
|
|
179
|
+
TIMEOUT = 2,
|
|
180
|
+
BUFFER_OVERFLOW = 3,
|
|
181
|
+
OVERRUN_ERROR = 4,
|
|
182
|
+
PARITY_ERROR = 5,
|
|
183
|
+
FRAMING_ERROR = 6,
|
|
184
|
+
BREAK_DETECTED = 7,
|
|
185
|
+
WRITE_FAILED = 8,
|
|
186
|
+
READ_FAILED = 9
|
|
187
|
+
}
|
|
188
|
+
export interface UARTStatusInfo {
|
|
189
|
+
available: number;
|
|
190
|
+
writeAvailable: number;
|
|
191
|
+
overrunError: boolean;
|
|
192
|
+
parityError: boolean;
|
|
193
|
+
framingError: boolean;
|
|
194
|
+
breakDetected: boolean;
|
|
195
|
+
}
|
|
196
|
+
export interface IUARTBus {
|
|
197
|
+
readonly uartNumber: number;
|
|
198
|
+
readonly baudRate: number;
|
|
199
|
+
readonly isEnabled: boolean;
|
|
200
|
+
begin(baud: number): void;
|
|
201
|
+
end(): void;
|
|
202
|
+
read(): number;
|
|
203
|
+
peek(): number;
|
|
204
|
+
readLine(): string;
|
|
205
|
+
readBytes(count: number): Uint8Array;
|
|
206
|
+
readString(): string;
|
|
207
|
+
available(): number;
|
|
208
|
+
write(data: number | Uint8Array | string): number;
|
|
209
|
+
flush(): void;
|
|
210
|
+
getStatus(): UARTStatusInfo;
|
|
211
|
+
clearErrors(): void;
|
|
212
|
+
onReceive(callback: (bytesAvailable: number) => void): void;
|
|
213
|
+
onTransmitComplete(callback: () => void): void;
|
|
214
|
+
onError(callback: (status: UARTStatus) => void): void;
|
|
215
|
+
errorPolicy: ErrorPolicy;
|
|
216
|
+
}
|
|
217
|
+
export interface ISerialPort extends IUARTBus {
|
|
218
|
+
print(...args: unknown[]): void;
|
|
219
|
+
println(...args: unknown[]): void;
|
|
220
|
+
printf(format: string, ...args: unknown[]): void;
|
|
221
|
+
isConnected(): boolean;
|
|
222
|
+
waitForConnection(timeout?: number): Promise<void>;
|
|
223
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// @typecad/simulator — Runtime contract interfaces
|
|
3
|
+
//
|
|
4
|
+
// These types define the runtime contracts that @typecad/simulator's simulated
|
|
5
|
+
// peripheral classes implement (SimDigitalPin, SimI2CBus, SimSPIBus,
|
|
6
|
+
// SimSerialPort, ...). They were relocated here from @typecad/hal so that HAL
|
|
7
|
+
// contains only its transpiler-shim surface, and the simulator owns the
|
|
8
|
+
// hierarchy that matches its job (real behavioral objects, not IR emitters).
|
|
9
|
+
//
|
|
10
|
+
// Primitive aliases that the contracts still reference (DigitalValue,
|
|
11
|
+
// AnalogValue, InterruptHandler) and the protocol-shape types (I2CAddress,
|
|
12
|
+
// SPIMode, SPIBitOrder, SPISettings) remain in @typecad/hal and are imported
|
|
13
|
+
// below. The dependency direction is simulator → hal (already established).
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Capability type guards and assertions
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
function isBasePin(value) {
|
|
19
|
+
return (typeof value === 'object' &&
|
|
20
|
+
value !== null &&
|
|
21
|
+
'number' in value &&
|
|
22
|
+
'gpio' in value);
|
|
23
|
+
}
|
|
24
|
+
export function hasPWM(pin) {
|
|
25
|
+
return isBasePin(pin) && 'pwm' in pin && typeof pin.pwm === 'function';
|
|
26
|
+
}
|
|
27
|
+
export function hasAnalogInput(pin) {
|
|
28
|
+
return isBasePin(pin) && 'readAnalog' in pin && typeof pin.readAnalog === 'function';
|
|
29
|
+
}
|
|
30
|
+
export function hasInterrupt(pin) {
|
|
31
|
+
return isBasePin(pin) && 'onRising' in pin && typeof pin.onRising === 'function';
|
|
32
|
+
}
|
|
33
|
+
export function assertPWM(pin, message) {
|
|
34
|
+
if (!hasPWM(pin)) {
|
|
35
|
+
throw new Error(message ?? 'Pin does not support PWM');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function assertAnalog(pin, message) {
|
|
39
|
+
if (!hasAnalogInput(pin)) {
|
|
40
|
+
throw new Error(message ?? 'Pin does not support analog input');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function assertInterrupt(pin, message) {
|
|
44
|
+
if (!hasInterrupt(pin)) {
|
|
45
|
+
throw new Error(message ?? 'Pin does not support interrupts');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// I2C types
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
export var I2CStatus;
|
|
52
|
+
(function (I2CStatus) {
|
|
53
|
+
I2CStatus[I2CStatus["SUCCESS"] = 0] = "SUCCESS";
|
|
54
|
+
I2CStatus[I2CStatus["DATA_TOO_LONG"] = 1] = "DATA_TOO_LONG";
|
|
55
|
+
I2CStatus[I2CStatus["NACK_ON_ADDRESS"] = 2] = "NACK_ON_ADDRESS";
|
|
56
|
+
I2CStatus[I2CStatus["NACK_ON_DATA"] = 3] = "NACK_ON_DATA";
|
|
57
|
+
I2CStatus[I2CStatus["OTHER_ERROR"] = 4] = "OTHER_ERROR";
|
|
58
|
+
I2CStatus[I2CStatus["PARTIAL_READ"] = 5] = "PARTIAL_READ";
|
|
59
|
+
})(I2CStatus || (I2CStatus = {}));
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// SPI types
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
export var SPIStatus;
|
|
64
|
+
(function (SPIStatus) {
|
|
65
|
+
SPIStatus[SPIStatus["SUCCESS"] = 0] = "SUCCESS";
|
|
66
|
+
SPIStatus[SPIStatus["NOT_INITIALIZED"] = 1] = "NOT_INITIALIZED";
|
|
67
|
+
SPIStatus[SPIStatus["TRANSFER_FAILED"] = 2] = "TRANSFER_FAILED";
|
|
68
|
+
SPIStatus[SPIStatus["INVALID_CONFIG"] = 3] = "INVALID_CONFIG";
|
|
69
|
+
SPIStatus[SPIStatus["TIMEOUT"] = 4] = "TIMEOUT";
|
|
70
|
+
SPIStatus[SPIStatus["DEVICE_ERROR"] = 5] = "DEVICE_ERROR";
|
|
71
|
+
})(SPIStatus || (SPIStatus = {}));
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// UART types
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
export var UARTStatus;
|
|
76
|
+
(function (UARTStatus) {
|
|
77
|
+
UARTStatus[UARTStatus["SUCCESS"] = 0] = "SUCCESS";
|
|
78
|
+
UARTStatus[UARTStatus["NOT_INITIALIZED"] = 1] = "NOT_INITIALIZED";
|
|
79
|
+
UARTStatus[UARTStatus["TIMEOUT"] = 2] = "TIMEOUT";
|
|
80
|
+
UARTStatus[UARTStatus["BUFFER_OVERFLOW"] = 3] = "BUFFER_OVERFLOW";
|
|
81
|
+
UARTStatus[UARTStatus["OVERRUN_ERROR"] = 4] = "OVERRUN_ERROR";
|
|
82
|
+
UARTStatus[UARTStatus["PARITY_ERROR"] = 5] = "PARITY_ERROR";
|
|
83
|
+
UARTStatus[UARTStatus["FRAMING_ERROR"] = 6] = "FRAMING_ERROR";
|
|
84
|
+
UARTStatus[UARTStatus["BREAK_DETECTED"] = 7] = "BREAK_DETECTED";
|
|
85
|
+
UARTStatus[UARTStatus["WRITE_FAILED"] = 8] = "WRITE_FAILED";
|
|
86
|
+
UARTStatus[UARTStatus["READ_FAILED"] = 9] = "READ_FAILED";
|
|
87
|
+
})(UARTStatus || (UARTStatus = {}));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AnalogPin } from '../contracts.js';
|
|
2
|
+
import type { AnalogValue } from '@typecad/hal';
|
|
3
|
+
import { SimDigitalPin } from './digital-pin-sim.js';
|
|
4
|
+
/**
|
|
5
|
+
* Simulated analog input pin.
|
|
6
|
+
*
|
|
7
|
+
* Extends SimDigitalPin with analog read capabilities.
|
|
8
|
+
* Tests can inject values via `injectValue()` and verify readings.
|
|
9
|
+
*
|
|
10
|
+
* - `read()` returns a digital boolean (HIGH if above half-scale).
|
|
11
|
+
* - `readAnalog()` returns the raw ADC value.
|
|
12
|
+
*/
|
|
13
|
+
export declare class SimAnalogPin extends SimDigitalPin implements AnalogPin {
|
|
14
|
+
private _analogValue;
|
|
15
|
+
private _voltage;
|
|
16
|
+
private _reference;
|
|
17
|
+
private _resolution;
|
|
18
|
+
constructor(pinNum: number);
|
|
19
|
+
/** Convenience: set pin to analog input mode. */
|
|
20
|
+
analog(): void;
|
|
21
|
+
readAnalog(): AnalogValue;
|
|
22
|
+
readVoltage(): number;
|
|
23
|
+
setAnalogReference(voltage: number): void;
|
|
24
|
+
getAnalogResolution(): number;
|
|
25
|
+
/**
|
|
26
|
+
* Inject an analog value (e.g., sensor reading).
|
|
27
|
+
* Also updates the digital read state based on threshold.
|
|
28
|
+
* @param value - Raw ADC value (0 to 2^resolution - 1)
|
|
29
|
+
*/
|
|
30
|
+
injectValue(value: AnalogValue): void;
|
|
31
|
+
/**
|
|
32
|
+
* Inject a voltage directly. Converts to ADC value based on resolution.
|
|
33
|
+
*/
|
|
34
|
+
injectVoltage(voltage: number): void;
|
|
35
|
+
/**
|
|
36
|
+
* Set the ADC resolution in bits.
|
|
37
|
+
*/
|
|
38
|
+
setResolution(bits: number): void;
|
|
39
|
+
reset(): void;
|
|
40
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// @typecad/simulator — Simulated analog input pin
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
import { SimDigitalPin } from './digital-pin-sim.js';
|
|
5
|
+
/**
|
|
6
|
+
* Simulated analog input pin.
|
|
7
|
+
*
|
|
8
|
+
* Extends SimDigitalPin with analog read capabilities.
|
|
9
|
+
* Tests can inject values via `injectValue()` and verify readings.
|
|
10
|
+
*
|
|
11
|
+
* - `read()` returns a digital boolean (HIGH if above half-scale).
|
|
12
|
+
* - `readAnalog()` returns the raw ADC value.
|
|
13
|
+
*/
|
|
14
|
+
export class SimAnalogPin extends SimDigitalPin {
|
|
15
|
+
constructor(pinNum) {
|
|
16
|
+
super(pinNum, { analogInput: true });
|
|
17
|
+
this._analogValue = 0;
|
|
18
|
+
this._voltage = 0;
|
|
19
|
+
this._reference = 5.0; // Default 5V reference
|
|
20
|
+
this._resolution = 10; // Default 10-bit (0-1023)
|
|
21
|
+
}
|
|
22
|
+
// --- AnalogPin capability methods ---
|
|
23
|
+
/** Convenience: set pin to analog input mode. */
|
|
24
|
+
analog() {
|
|
25
|
+
this.asInput();
|
|
26
|
+
}
|
|
27
|
+
readAnalog() {
|
|
28
|
+
return this._analogValue;
|
|
29
|
+
}
|
|
30
|
+
readVoltage() {
|
|
31
|
+
return this._voltage;
|
|
32
|
+
}
|
|
33
|
+
setAnalogReference(voltage) {
|
|
34
|
+
this._reference = voltage;
|
|
35
|
+
}
|
|
36
|
+
getAnalogResolution() {
|
|
37
|
+
return this._resolution;
|
|
38
|
+
}
|
|
39
|
+
// --- Simulation helpers ---
|
|
40
|
+
/**
|
|
41
|
+
* Inject an analog value (e.g., sensor reading).
|
|
42
|
+
* Also updates the digital read state based on threshold.
|
|
43
|
+
* @param value - Raw ADC value (0 to 2^resolution - 1)
|
|
44
|
+
*/
|
|
45
|
+
injectValue(value) {
|
|
46
|
+
this._analogValue = value;
|
|
47
|
+
// Calculate voltage from ADC value
|
|
48
|
+
const maxAdc = (1 << this._resolution) - 1;
|
|
49
|
+
this._voltage = (value / maxAdc) * this._reference;
|
|
50
|
+
// Update digital state: HIGH if above half-scale
|
|
51
|
+
super.injectValue(value > (maxAdc / 2) ? 1 : 0);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Inject a voltage directly. Converts to ADC value based on resolution.
|
|
55
|
+
*/
|
|
56
|
+
injectVoltage(voltage) {
|
|
57
|
+
this._voltage = voltage;
|
|
58
|
+
const maxAdc = (1 << this._resolution) - 1;
|
|
59
|
+
this._analogValue = Math.round((voltage / this._reference) * maxAdc);
|
|
60
|
+
super.injectValue(this._analogValue > (maxAdc / 2) ? 1 : 0);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Set the ADC resolution in bits.
|
|
64
|
+
*/
|
|
65
|
+
setResolution(bits) {
|
|
66
|
+
this._resolution = bits;
|
|
67
|
+
}
|
|
68
|
+
reset() {
|
|
69
|
+
super.reset();
|
|
70
|
+
this._analogValue = 0;
|
|
71
|
+
this._voltage = 0;
|
|
72
|
+
this._reference = 5.0;
|
|
73
|
+
this._resolution = 10;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { BasePin, IToneAttachment, PinCapabilityFlags } from '../contracts.js';
|
|
2
|
+
import { PinMode } from '@typecad/hal';
|
|
3
|
+
import type { DigitalValue } from '@typecad/hal';
|
|
4
|
+
/**
|
|
5
|
+
* Tracks the history of pin state changes for test assertions.
|
|
6
|
+
*/
|
|
7
|
+
export interface PinStateChange {
|
|
8
|
+
/** Timestamp (ms since simulation start) */
|
|
9
|
+
timestamp: number;
|
|
10
|
+
/** Previous value (0 or 1) */
|
|
11
|
+
from: number;
|
|
12
|
+
/** New value (0 or 1) */
|
|
13
|
+
to: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class SimDigitalPin implements BasePin {
|
|
16
|
+
readonly number: number;
|
|
17
|
+
readonly gpio: number;
|
|
18
|
+
readonly capabilities: PinCapabilityFlags;
|
|
19
|
+
private _value;
|
|
20
|
+
private _pinMode;
|
|
21
|
+
private _history;
|
|
22
|
+
protected _startTime: number;
|
|
23
|
+
constructor(pinNum: number, caps?: Partial<PinCapabilityFlags>);
|
|
24
|
+
getMode(): PinMode;
|
|
25
|
+
setMode(mode: PinMode): void;
|
|
26
|
+
has(capability: keyof PinCapabilityFlags): boolean;
|
|
27
|
+
inputPullUp(): void;
|
|
28
|
+
inputPullDown(): void;
|
|
29
|
+
outputOpenDrain(initial?: DigitalValue): void;
|
|
30
|
+
asOutput(initial?: DigitalValue): any;
|
|
31
|
+
asInput(): any;
|
|
32
|
+
asInputPullUp(): any;
|
|
33
|
+
read(): DigitalValue;
|
|
34
|
+
isHigh(): boolean;
|
|
35
|
+
isLow(): boolean;
|
|
36
|
+
waitForRising(_timeout?: number): Promise<void>;
|
|
37
|
+
waitForFalling(_timeout?: number): Promise<void>;
|
|
38
|
+
write(value: DigitalValue): void;
|
|
39
|
+
high(): void;
|
|
40
|
+
low(): void;
|
|
41
|
+
toggle(): void;
|
|
42
|
+
pulse(_duration: number): void;
|
|
43
|
+
tone(_frequency: number): IToneAttachment;
|
|
44
|
+
noTone(): void;
|
|
45
|
+
stop(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Inject a value into this pin (simulates external signal).
|
|
48
|
+
* Use this in tests to simulate button presses, sensor triggers, etc.
|
|
49
|
+
*/
|
|
50
|
+
injectValue(value: number): void;
|
|
51
|
+
/**
|
|
52
|
+
* Get the history of all state changes on this pin.
|
|
53
|
+
*/
|
|
54
|
+
getHistory(): readonly PinStateChange[];
|
|
55
|
+
/**
|
|
56
|
+
* Clear the state change history.
|
|
57
|
+
*/
|
|
58
|
+
clearHistory(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Get the current value as a plain number (0 or 1).
|
|
61
|
+
*/
|
|
62
|
+
getBitValue(): number;
|
|
63
|
+
/**
|
|
64
|
+
* Reset the pin to its initial state.
|
|
65
|
+
*/
|
|
66
|
+
reset(): void;
|
|
67
|
+
private _setBitValue;
|
|
68
|
+
}
|