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