1515#include <pybricks/common.h>
1616#include <pybricks/iodevices/iodevices.h>
1717#include <pybricks/parameters.h>
18+ #include <pybricks/tools/pb_type_async.h>
1819
1920#include <pybricks/util_mp/pb_kwarg_helper.h>
2021#include <pybricks/util_mp/pb_obj_helper.h>
2122#include <pybricks/util_pb/pb_error.h>
2223
23- // Object representing a pybricks.iodevices.I2CDevice instance.
24+ /**
25+ * Object representing a pybricks.iodevices.I2CDevice instance.
26+ *
27+ * Also used by sensor classes for I2C Devices.
28+ */
2429typedef struct {
2530 mp_obj_base_t base ;
31+ /**
32+ * Object that owns this I2C device, such as an Ultrasonic Sensor instance.
33+ * Gets passed to all return mappings.
34+ * Equals MP_OBJ_NULL when this is the standalone I2CDevice class instance.
35+ */
36+ mp_obj_t sensor_obj ;
37+ /**
38+ * Generic reusable awaitable operation.
39+ */
40+ pb_type_async_t * iter ;
2641 /**
2742 * The following are buffered parameters for one ongoing I2C operation, See
2843 * ::pbdrv_i2c_write_then_read for details on each parameter. We need to
@@ -31,17 +46,19 @@ typedef struct {
3146 * immediately copied to the driver on the first call to the protothread.
3247 */
3348 pbdrv_i2c_dev_t * i2c_dev ;
34- pbio_os_state_t state ;
3549 uint8_t address ;
3650 bool nxt_quirk ;
37- pb_type_i2c_device_return_map_t return_map ;
3851 size_t write_len ;
3952 size_t read_len ;
4053 uint8_t * read_buf ;
54+ /**
55+ * Maps bytes read to the user return object.
56+ */
57+ pb_type_i2c_device_return_map_t return_map ;
4158} device_obj_t ;
4259
4360// pybricks.iodevices.I2CDevice.__init__
44- mp_obj_t pb_type_i2c_device_make_new (mp_obj_t port_in , uint8_t address , bool custom , bool powered , bool nxt_quirk ) {
61+ mp_obj_t pb_type_i2c_device_make_new (mp_obj_t sensor_obj , mp_obj_t port_in , uint8_t address , bool custom , bool powered , bool nxt_quirk ) {
4562
4663 pb_module_tools_assert_blocking ();
4764
@@ -68,6 +85,8 @@ mp_obj_t pb_type_i2c_device_make_new(mp_obj_t port_in, uint8_t address, bool cus
6885 device -> i2c_dev = i2c_dev ;
6986 device -> address = address ;
7087 device -> nxt_quirk = nxt_quirk ;
88+ device -> sensor_obj = sensor_obj ;
89+ device -> iter = NULL ;
7190 if (powered ) {
7291 pbio_port_p1p2_set_power (port , PBIO_PORT_POWER_REQUIREMENTS_BATTERY_VOLTAGE_P1_POS );
7392 }
@@ -85,29 +104,25 @@ static mp_obj_t make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw,
85104 PB_ARG_DEFAULT_FALSE (nxt_quirk )
86105 );
87106
88- return pb_type_i2c_device_make_new (port_in , mp_obj_get_int (address_in ), mp_obj_is_true (custom_in ), mp_obj_is_true (powered_in ), mp_obj_is_true (nxt_quirk_in ));
107+ return pb_type_i2c_device_make_new (
108+ MP_OBJ_NULL ,
109+ port_in ,
110+ mp_obj_get_int (address_in ),
111+ mp_obj_is_true (custom_in ),
112+ mp_obj_is_true (powered_in ),
113+ mp_obj_is_true (nxt_quirk_in )
114+ );
89115}
90116
91- // Object representing the iterable that is returned when calling an I2C
92- // method. This object can then be awaited (iterated). It has a reference to
93- // the device from which it was created. Only one operation can be active at
94- // one time.
95- typedef struct {
96- mp_obj_base_t base ;
97- mp_obj_t device_obj ;
98- } operation_obj_t ;
99-
100- static mp_obj_t operation_close (mp_obj_t op_in ) {
101- // Close is not implemented but needs to exist.
102- operation_obj_t * op = MP_OBJ_TO_PTR (op_in );
103- (void )op ;
104- return mp_const_none ;
105- }
106- static MP_DEFINE_CONST_FUN_OBJ_1 (operation_close_obj , operation_close ) ;
117+ /**
118+ * This keeps calling the I2C protothread with cached parameters until completion.
119+ */
120+ static pbio_error_t pb_type_i2c_device_iterate_once (pbio_os_state_t * state , mp_obj_t i2c_device_obj ) {
121+
122+ device_obj_t * device = MP_OBJ_TO_PTR (i2c_device_obj );
107123
108- static pbio_error_t operation_iterate_once (device_obj_t * device ) {
109124 return pbdrv_i2c_write_then_read (
110- & device -> state , device -> i2c_dev ,
125+ state , device -> i2c_dev ,
111126 device -> address ,
112127 NULL , // Already memcpy'd on initial iteration. No need to provide here.
113128 device -> write_len ,
@@ -117,40 +132,23 @@ static pbio_error_t operation_iterate_once(device_obj_t *device) {
117132 );
118133}
119134
120- static mp_obj_t operation_iternext (mp_obj_t op_in ) {
121- operation_obj_t * op = MP_OBJ_TO_PTR (op_in );
122- device_obj_t * device = MP_OBJ_TO_PTR (op -> device_obj );
123-
124- pbio_error_t err = operation_iterate_once (device );
125-
126- // Yielded, keep going.
127- if (err == PBIO_ERROR_AGAIN ) {
128- return mp_const_none ;
129- }
130-
131- // Raises on Timeout and other I/O errors. Proceeds on success.
132- pb_assert (err );
135+ /**
136+ * This is the callable form required by the shared awaitable code.
137+ *
138+ * For classes that have an I2C class instance such as the Ultrasonic Sensor,
139+ * the I2C object is not of interest, but rather the sensor object. So this
140+ * wrapper essentially passes the containing object to the return map.
141+ */
142+ static mp_obj_t pb_type_i2c_device_return_generic (mp_obj_t i2c_device_obj ) {
143+ device_obj_t * device = MP_OBJ_TO_PTR (i2c_device_obj );
133144
134- // For no return map, return basic stop iteration, which results None.
135145 if (!device -> return_map ) {
136- return MP_OBJ_STOP_ITERATION ;
146+ return mp_const_none ;
137147 }
138148
139- // Set return value via stop iteration.
140- return mp_make_stop_iteration (device -> return_map (device -> read_buf , device -> read_len ));
149+ return device -> return_map (device -> sensor_obj , device -> read_buf , device -> read_len );
141150}
142151
143- static const mp_rom_map_elem_t operation_locals_dict_table [] = {
144- { MP_ROM_QSTR (MP_QSTR_close ), MP_ROM_PTR (& operation_close_obj ) },
145- };
146- MP_DEFINE_CONST_DICT (operation_locals_dict , operation_locals_dict_table );
147-
148- MP_DEFINE_CONST_OBJ_TYPE (operation_type ,
149- MP_QSTR_I2COperation ,
150- MP_TYPE_FLAG_ITER_IS_ITERNEXT ,
151- iter , operation_iternext ,
152- locals_dict , & operation_locals_dict );
153-
154152mp_obj_t pb_type_i2c_device_start_operation (mp_obj_t i2c_device_obj , const uint8_t * write_data , size_t write_len , size_t read_len , pb_type_i2c_device_return_map_t return_map ) {
155153
156154 pb_assert_type (i2c_device_obj , & pb_type_i2c_device );
@@ -173,32 +171,23 @@ mp_obj_t pb_type_i2c_device_start_operation(mp_obj_t i2c_device_obj, const uint8
173171 }
174172
175173 // The initial operation above can fail if an I2C transaction is already in
176- // progress. If so, we don't want to reset it state or allow the return
174+ // progress. If so, we don't want to reset its state or allow the return
177175 // result to be garbage collected. Now that the first iteration succeeded,
178- // save the state and assign the new result buffer .
176+ // save the state.
179177 device -> read_len = read_len ;
180178 device -> write_len = write_len ;
181- device -> state = state ;
182179 device -> read_buf = NULL ;
183180 device -> return_map = return_map ;
184181
185- // If runloop active, return an awaitable object.
186- if (pb_module_tools_run_loop_is_active ()) {
187- operation_obj_t * operation = mp_obj_malloc (operation_obj_t , & operation_type );
188- operation -> device_obj = MP_OBJ_FROM_PTR (device );
189- return MP_OBJ_FROM_PTR (operation );
190- }
191-
192- // Otherwise block and wait for the result here.
193- while ((err = operation_iterate_once (device )) == PBIO_ERROR_AGAIN ) {
194- MICROPY_EVENT_POLL_HOOK ;
195- }
196- pb_assert (err );
197-
198- if (!device -> return_map ) {
199- return mp_const_none ;
200- }
201- return device -> return_map (device -> read_buf , device -> read_len );
182+ pb_type_async_t config = {
183+ .parent_obj = i2c_device_obj ,
184+ .iter_once = pb_type_i2c_device_iterate_once ,
185+ .state = state ,
186+ .return_map = return_map ? pb_type_i2c_device_return_generic : NULL ,
187+ };
188+ // New operation always wins; ongoing sound awaitable is cancelled.
189+ pb_type_async_schedule_cancel (device -> iter );
190+ return pb_type_async_wait_or_await (& config , & device -> iter );
202191}
203192
204193/**
@@ -207,19 +196,24 @@ mp_obj_t pb_type_i2c_device_start_operation(mp_obj_t i2c_device_obj, const uint8
207196 * string is not found.
208197 */
209198void pb_type_i2c_device_assert_string_at_register (mp_obj_t i2c_device_obj , uint8_t reg , const char * string ) {
199+
200+ device_obj_t * device = MP_OBJ_TO_PTR (i2c_device_obj );
201+
210202 pb_module_tools_assert_blocking ();
211203
204+ size_t read_len = strlen (string );
212205 const uint8_t write_data [] = { reg };
213- mp_obj_t result = pb_type_i2c_device_start_operation (i2c_device_obj , write_data , MP_ARRAY_SIZE (write_data ), strlen (string ) - 1 , mp_obj_new_bytes );
214-
215- size_t result_len ;
216- const char * result_data = mp_obj_str_get_data (result , & result_len );
206+ pb_type_i2c_device_start_operation (i2c_device_obj , write_data , MP_ARRAY_SIZE (write_data ), read_len , NULL );
217207
218- if (memcmp (string , result_data , strlen ( string ) - 1 )) {
208+ if (memcmp (string , device -> read_buf , read_len )) {
219209 pb_assert (PBIO_ERROR_NO_DEV );
220210 }
221211}
222212
213+ static mp_obj_t pb_type_i2c_device_return_bytes (mp_obj_t self_in , const uint8_t * data , size_t len ) {
214+ return mp_obj_new_bytes (data , len );
215+ }
216+
223217// pybricks.iodevices.I2CDevice.read
224218static mp_obj_t read (size_t n_args , const mp_obj_t * pos_args , mp_map_t * kw_args ) {
225219 PB_PARSE_ARGS_METHOD (n_args , pos_args , kw_args ,
@@ -235,7 +229,13 @@ static mp_obj_t read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
235229 & (uint8_t ) { mp_obj_get_int (reg_in ) };
236230 size_t write_len = reg_in == mp_const_none ? 0 : 1 ;
237231
238- return pb_type_i2c_device_start_operation (MP_OBJ_FROM_PTR (device ), write_data , write_len , pb_obj_get_positive_int (length_in ), mp_obj_new_bytes );
232+ return pb_type_i2c_device_start_operation (
233+ MP_OBJ_FROM_PTR (device ),
234+ write_data ,
235+ write_len ,
236+ pb_obj_get_positive_int (length_in ),
237+ pb_type_i2c_device_return_bytes
238+ );
239239}
240240static MP_DEFINE_CONST_FUN_OBJ_KW (read_obj , 0 , read ) ;
241241
0 commit comments