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
83178static 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
204312uint8_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
0 commit comments