Skip to content

Commit 65fc3d6

Browse files
committed
Updates to WASM port to support new controller structure and async functionality.
1 parent 91383ca commit 65fc3d6

27 files changed

Lines changed: 4930 additions & 510 deletions

ports/wasm/common-hal/busio/I2C.c

Lines changed: 127 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "shared-bindings/microcontroller/Pin.h"
1212
#include "py/mperrno.h"
1313
#include "py/runtime.h"
14+
#include "proxy_c.h"
1415
#include <emscripten.h>
1516
#include <string.h>
1617

@@ -46,6 +47,11 @@ typedef struct {
4647
uint8_t last_read_addr;
4748
uint8_t last_read_data[I2C_BUFFER_SIZE];
4849
uint16_t last_read_len;
50+
51+
// Rich path: Optional JsProxy for events (NULL if no web app listeners)
52+
// When this exists, JS I2C bus object is the source of truth for transactions
53+
// C code syncs to it via store_attr(), triggering automatic onChange events
54+
mp_obj_jsproxy_t *js_bus;
4955
} i2c_bus_state_t;
5056

5157
// Global I2C bus state array
@@ -70,6 +76,7 @@ void busio_reset_i2c_state(void) {
7076
i2c_buses[i].locked = false;
7177
i2c_buses[i].last_write_len = 0;
7278
i2c_buses[i].last_read_len = 0;
79+
i2c_buses[i].js_bus = NULL; // No JsProxy initially
7380

7481
// Reset all devices on this bus
7582
for (int j = 0; j < 128; j++) {
@@ -79,6 +86,94 @@ void busio_reset_i2c_state(void) {
7986
}
8087
}
8188

89+
// ========== JsProxy Integration Functions ==========
90+
91+
// Create a JS I2C bus object and add it to the proxy system
92+
EM_JS(int, i2c_create_js_bus_proxy, (int bus_index), {
93+
const board = Module._circuitPythonBoard;
94+
if (!board || !board.i2c) {
95+
console.warn('[I2C] CircuitPythonBoard or I2C controller not initialized');
96+
return -1;
97+
}
98+
99+
// Get the I2C bus object (controller creates it if it doesn't exist)
100+
const bus = board.i2c.getBus(bus_index);
101+
102+
// Add to proxy system and return the reference
103+
return proxy_js_add_obj(bus);
104+
});
105+
106+
// Get current timestamp in milliseconds
107+
EM_JS(double, i2c_get_timestamp_ms, (void), {
108+
return Date.now();
109+
});
110+
111+
// Helper to sync transaction data to JS proxy
112+
static inline void i2c_sync_transaction_to_js(mp_obj_jsproxy_t *js_bus,
113+
uint8_t addr, const uint8_t *data, size_t len, bool is_write) {
114+
if (js_bus == NULL) {
115+
return;
116+
}
117+
118+
// Create transaction object as a Python dict
119+
mp_obj_t transaction_dict = mp_obj_new_dict(5);
120+
121+
// Add type field
122+
mp_obj_dict_store(transaction_dict,
123+
mp_obj_new_str("type", 4),
124+
mp_obj_new_str(is_write ? "write" : "read", is_write ? 5 : 4));
125+
126+
// Add address field
127+
mp_obj_dict_store(transaction_dict,
128+
mp_obj_new_str("addr", 4),
129+
mp_obj_new_int(addr));
130+
131+
// Add data field (as bytes)
132+
mp_obj_t data_bytes = mp_obj_new_bytes(data, len);
133+
mp_obj_dict_store(transaction_dict,
134+
mp_obj_new_str("data", 4),
135+
data_bytes);
136+
137+
// Add length field
138+
mp_obj_dict_store(transaction_dict,
139+
mp_obj_new_str("length", 6),
140+
mp_obj_new_int(len));
141+
142+
// Add timestamp field
143+
double timestamp = i2c_get_timestamp_ms();
144+
mp_obj_dict_store(transaction_dict,
145+
mp_obj_new_str("timestamp", 9),
146+
mp_obj_new_float(timestamp));
147+
148+
// Convert to JS and set as property on bus object
149+
uint32_t value_out[PVN];
150+
proxy_convert_mp_to_js_obj_cside(transaction_dict, value_out);
151+
store_attr(js_bus->ref, "lastTransaction", value_out);
152+
}
153+
154+
// Helper to sync device probe to JS proxy
155+
static inline void i2c_sync_probe_to_js(mp_obj_jsproxy_t *js_bus, uint8_t addr, bool found) {
156+
if (js_bus == NULL) {
157+
return;
158+
}
159+
160+
// Create probe result object
161+
mp_obj_t probe_dict = mp_obj_new_dict(2);
162+
163+
mp_obj_dict_store(probe_dict,
164+
mp_obj_new_str("addr", 4),
165+
mp_obj_new_int(addr));
166+
167+
mp_obj_dict_store(probe_dict,
168+
mp_obj_new_str("found", 5),
169+
mp_obj_new_bool(found));
170+
171+
// Convert to JS and set as property
172+
uint32_t value_out[PVN];
173+
proxy_convert_mp_to_js_obj_cside(probe_dict, value_out);
174+
store_attr(js_bus->ref, "lastProbe", value_out);
175+
}
176+
82177
// Find an I2C bus by pin pair, or return -1 if not found
83178
static int8_t find_i2c_bus(uint8_t scl_pin, uint8_t sda_pin) {
84179
for (int i = 0; i < MAX_I2C_BUSES; i++) {
@@ -128,6 +223,14 @@ void common_hal_busio_i2c_construct(busio_i2c_obj_t *self,
128223
i2c_buses[bus_idx].enabled = true;
129224
i2c_buses[bus_idx].locked = false;
130225
i2c_buses[bus_idx].never_reset = false;
226+
227+
// Create JsProxy for this bus if not already created
228+
if (i2c_buses[bus_idx].js_bus == NULL) {
229+
int jsref = i2c_create_js_bus_proxy(bus_idx);
230+
if (jsref >= 0) {
231+
i2c_buses[bus_idx].js_bus = mp_obj_new_jsproxy(jsref);
232+
}
233+
}
131234
}
132235
}
133236

@@ -197,8 +300,13 @@ bool common_hal_busio_i2c_probe(busio_i2c_obj_t *self, uint8_t addr) {
197300
return false;
198301
}
199302

200-
// Check if device is active
201-
return i2c_buses[bus_idx].devices[addr].active;
303+
// Fast path: Check if device is active
304+
bool found = i2c_buses[bus_idx].devices[addr].active;
305+
306+
// Rich path: Sync probe result to JsProxy (triggers automatic probe events)
307+
i2c_sync_probe_to_js(i2c_buses[bus_idx].js_bus, addr, found);
308+
309+
return found;
202310
}
203311

204312
uint8_t common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t address,
@@ -223,7 +331,7 @@ uint8_t common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t address,
223331
len = I2C_BUFFER_SIZE;
224332
}
225333

226-
// Store last write for JavaScript access
334+
// Fast path: Store last write for JavaScript access
227335
i2c_buses[bus_idx].last_write_addr = address;
228336
memcpy(i2c_buses[bus_idx].last_write_data, data, len);
229337
i2c_buses[bus_idx].last_write_len = len;
@@ -235,6 +343,9 @@ uint8_t common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t address,
235343
&data[1], len - 1);
236344
}
237345

346+
// Rich path: Sync to JsProxy (triggers automatic transaction events)
347+
i2c_sync_transaction_to_js(i2c_buses[bus_idx].js_bus, address, data, len, true);
348+
238349
return 0; // Success
239350
}
240351

@@ -260,14 +371,17 @@ uint8_t common_hal_busio_i2c_read(busio_i2c_obj_t *self, uint16_t address,
260371
len = I2C_BUFFER_SIZE;
261372
}
262373

263-
// Read from device registers (starting from register 0)
374+
// Fast path: Read from device registers (starting from register 0)
264375
memcpy(data, i2c_buses[bus_idx].devices[address].registers, len);
265376

266377
// Store last read for JavaScript access
267378
i2c_buses[bus_idx].last_read_addr = address;
268379
memcpy(i2c_buses[bus_idx].last_read_data, data, len);
269380
i2c_buses[bus_idx].last_read_len = len;
270381

382+
// Rich path: Sync to JsProxy (triggers automatic transaction events)
383+
i2c_sync_transaction_to_js(i2c_buses[bus_idx].js_bus, address, data, len, false);
384+
271385
return 0; // Success
272386
}
273387

@@ -296,7 +410,7 @@ uint8_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint16_t address,
296410
in_len = I2C_BUFFER_SIZE;
297411
}
298412

299-
// Read from specified register
413+
// Fast path: Read from specified register
300414
memcpy(in_data, &i2c_buses[bus_idx].devices[address].registers[reg_addr], in_len);
301415

302416
// Store transaction info
@@ -310,6 +424,14 @@ uint8_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint16_t address,
310424
memcpy(i2c_buses[bus_idx].last_read_data, in_data, in_len);
311425
i2c_buses[bus_idx].last_read_len = in_len;
312426

427+
// Rich path: Sync write and read to JsProxy (triggers automatic transaction events)
428+
// First sync the write part (register address)
429+
if (out_len > 0) {
430+
i2c_sync_transaction_to_js(i2c_buses[bus_idx].js_bus, address, out_data, out_len, true);
431+
}
432+
// Then sync the read part (data read from register)
433+
i2c_sync_transaction_to_js(i2c_buses[bus_idx].js_bus, address, in_data, in_len, false);
434+
313435
return 0; // Success
314436
}
315437

ports/wasm/common-hal/busio/SPI.c

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "shared-bindings/microcontroller/Pin.h"
1212
#include "py/mperrno.h"
1313
#include "py/runtime.h"
14+
#include "proxy_c.h"
1415
#include <emscripten.h>
1516
#include <string.h>
1617

@@ -36,6 +37,11 @@ typedef struct {
3637
uint8_t last_read_data[SPI_BUFFER_SIZE];
3738
uint16_t last_write_len;
3839
uint16_t last_read_len;
40+
41+
// Rich path: Optional JsProxy for events (NULL if no web app listeners)
42+
// When this exists, JS SPI bus object is the source of truth for transactions
43+
// C code syncs to it via store_attr(), triggering automatic onChange events
44+
mp_obj_jsproxy_t *js_spi;
3945
} spi_bus_state_t;
4046

4147
// State array for up to 4 SPI buses
@@ -80,9 +86,83 @@ void busio_reset_spi_state(void) {
8086
spi_buses[i].half_duplex = false;
8187
spi_buses[i].last_write_len = 0;
8288
spi_buses[i].last_read_len = 0;
89+
spi_buses[i].js_spi = NULL; // No JsProxy initially
8390
}
8491
}
8592

93+
// ========== JsProxy Integration Functions ==========
94+
95+
// Create a JS SPI bus object and add it to the proxy system
96+
EM_JS(int, spi_create_js_bus_proxy, (int bus_index), {
97+
const board = Module._circuitPythonBoard;
98+
if (!board || !board.spi) {
99+
console.warn('[SPI] CircuitPythonBoard or SPI controller not initialized');
100+
return -1;
101+
}
102+
103+
// Get the SPI bus object (controller creates it if it doesn't exist)
104+
const bus = board.spi.getBus(bus_index);
105+
106+
// Add to proxy system and return the reference
107+
return proxy_js_add_obj(bus);
108+
});
109+
110+
// Get current timestamp in milliseconds
111+
EM_JS(double, spi_get_timestamp_ms, (void), {
112+
return Date.now();
113+
});
114+
115+
// Helper to sync transaction data to JS proxy
116+
static inline void spi_sync_transaction_to_js(mp_obj_jsproxy_t *js_spi,
117+
const uint8_t *write_data, size_t write_len,
118+
const uint8_t *read_data, size_t read_len,
119+
const char *type) {
120+
if (js_spi == NULL) {
121+
return;
122+
}
123+
124+
// Create transaction object as a Python dict
125+
mp_obj_t transaction_dict = mp_obj_new_dict(5);
126+
127+
// Add type field ("write", "read", or "transfer")
128+
mp_obj_dict_store(transaction_dict,
129+
mp_obj_new_str("type", 4),
130+
mp_obj_new_str(type, strlen(type)));
131+
132+
// Add write data if present
133+
if (write_data != NULL && write_len > 0) {
134+
mp_obj_t write_bytes = mp_obj_new_bytes(write_data, write_len);
135+
mp_obj_dict_store(transaction_dict,
136+
mp_obj_new_str("writeData", 9),
137+
write_bytes);
138+
mp_obj_dict_store(transaction_dict,
139+
mp_obj_new_str("writeLen", 8),
140+
mp_obj_new_int(write_len));
141+
}
142+
143+
// Add read data if present
144+
if (read_data != NULL && read_len > 0) {
145+
mp_obj_t read_bytes = mp_obj_new_bytes(read_data, read_len);
146+
mp_obj_dict_store(transaction_dict,
147+
mp_obj_new_str("readData", 8),
148+
read_bytes);
149+
mp_obj_dict_store(transaction_dict,
150+
mp_obj_new_str("readLen", 7),
151+
mp_obj_new_int(read_len));
152+
}
153+
154+
// Add timestamp field
155+
double timestamp = spi_get_timestamp_ms();
156+
mp_obj_dict_store(transaction_dict,
157+
mp_obj_new_str("timestamp", 9),
158+
mp_obj_new_float(timestamp));
159+
160+
// Convert to JS and set as property on bus object
161+
uint32_t value_out[PVN];
162+
proxy_convert_mp_to_js_obj_cside(transaction_dict, value_out);
163+
store_attr(js_spi->ref, "lastTransaction", value_out);
164+
}
165+
86166
void common_hal_busio_spi_construct(busio_spi_obj_t *self,
87167
const mcu_pin_obj_t *clock, const mcu_pin_obj_t *mosi,
88168
const mcu_pin_obj_t *miso, bool half_duplex) {
@@ -133,6 +213,14 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self,
133213
spi_buses[bus_idx].locked = false;
134214
spi_buses[bus_idx].never_reset = false;
135215
spi_buses[bus_idx].half_duplex = half_duplex;
216+
217+
// Create JsProxy for this bus if not already created
218+
if (spi_buses[bus_idx].js_spi == NULL) {
219+
int jsref = spi_create_js_bus_proxy(bus_idx);
220+
if (jsref >= 0) {
221+
spi_buses[bus_idx].js_spi = mp_obj_new_jsproxy(jsref);
222+
}
223+
}
136224
}
137225

138226
// Store configuration in object
@@ -258,11 +346,14 @@ bool common_hal_busio_spi_write(busio_spi_obj_t *self, const uint8_t *data, size
258346
return false;
259347
}
260348

261-
// Store write data for JavaScript access (up to buffer size)
349+
// Fast path: Store write data for JavaScript access (up to buffer size)
262350
size_t copy_len = (len > SPI_BUFFER_SIZE) ? SPI_BUFFER_SIZE : len;
263351
memcpy(spi_buses[bus_idx].last_write_data, data, copy_len);
264352
spi_buses[bus_idx].last_write_len = copy_len;
265353

354+
// Rich path: Sync to JsProxy (triggers automatic transaction events)
355+
spi_sync_transaction_to_js(spi_buses[bus_idx].js_spi, data, copy_len, NULL, 0, "write");
356+
266357
return true;
267358
}
268359

@@ -280,12 +371,15 @@ bool common_hal_busio_spi_read(busio_spi_obj_t *self, uint8_t *data, size_t len,
280371
return false;
281372
}
282373

283-
// For WASM simulation, JavaScript can write to last_read_data
374+
// Fast path: For WASM simulation, JavaScript can write to last_read_data
284375
// to simulate peripheral responses
285376
size_t copy_len = (len > SPI_BUFFER_SIZE) ? SPI_BUFFER_SIZE : len;
286377
memcpy(data, spi_buses[bus_idx].last_read_data, copy_len);
287378
spi_buses[bus_idx].last_read_len = copy_len;
288379

380+
// Rich path: Sync to JsProxy (triggers automatic transaction events)
381+
spi_sync_transaction_to_js(spi_buses[bus_idx].js_spi, NULL, 0, data, copy_len, "read");
382+
289383
return true;
290384
}
291385

@@ -303,13 +397,16 @@ bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, const uint8_t *data_ou
303397
return false;
304398
}
305399

306-
// Store write data and read simulated response
400+
// Fast path: Store write data and read simulated response
307401
size_t copy_len = (len > SPI_BUFFER_SIZE) ? SPI_BUFFER_SIZE : len;
308402
memcpy(spi_buses[bus_idx].last_write_data, data_out, copy_len);
309403
memcpy(data_in, spi_buses[bus_idx].last_read_data, copy_len);
310404
spi_buses[bus_idx].last_write_len = copy_len;
311405
spi_buses[bus_idx].last_read_len = copy_len;
312406

407+
// Rich path: Sync to JsProxy (triggers automatic transaction events)
408+
spi_sync_transaction_to_js(spi_buses[bus_idx].js_spi, data_out, copy_len, data_in, copy_len, "transfer");
409+
313410
return true;
314411
}
315412

0 commit comments

Comments
 (0)