Skip to content

Commit 5e489ff

Browse files
committed
pybricks.iodevices.I2CDevices: Allow user callback.
Writing both the def and async def for each user method is cumbersome. This allows the user to make a read call and specify how to map the bytes to a return value. This method may then be called as async or sync without further wrappers.
1 parent 9deac01 commit 5e489ff

File tree

1 file changed

+25
-4
lines changed

1 file changed

+25
-4
lines changed

pybricks/iodevices/pb_type_i2c_device.c

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ typedef struct {
3131
/**
3232
* Object that owns this I2C device, such as an Ultrasonic Sensor instance.
3333
* Gets passed to all return mappings.
34-
* Equals MP_OBJ_NULL when this is the standalone I2CDevice class instance.
34+
*
35+
* In case of the standalone I2CDevice class instance, this value is instead
36+
* used to store an optional user callable to map bytes to a return object.
3537
*/
3638
mp_obj_t sensor_obj;
3739
/**
@@ -105,7 +107,7 @@ static mp_obj_t make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw,
105107
);
106108

107109
return pb_type_i2c_device_make_new(
108-
MP_OBJ_NULL,
110+
MP_OBJ_NULL, // Not associated with any particular sensor instance.
109111
port_in,
110112
mp_obj_get_int(address_in),
111113
mp_obj_is_true(custom_in),
@@ -210,16 +212,29 @@ void pb_type_i2c_device_assert_string_at_register(mp_obj_t i2c_device_obj, uint8
210212
}
211213
}
212214

215+
/**
216+
* I2C result mapping that just returns a bytes object.
217+
*/
213218
static mp_obj_t pb_type_i2c_device_return_bytes(mp_obj_t self_in, const uint8_t *data, size_t len) {
214219
return mp_obj_new_bytes(data, len);
215220
}
216221

222+
/**
223+
* I2C result mapping that calls user provided callback with self and bytes as argument.
224+
*/
225+
static mp_obj_t pb_type_i2c_device_return_user_map(mp_obj_t callable_obj, const uint8_t *data, size_t len) {
226+
// If user provides bound method, MicroPython takes care of providing
227+
// self as the first argument. We just need to pass in data arg.
228+
return mp_call_function_1(callable_obj, mp_obj_new_bytes(data, len));
229+
}
230+
217231
// pybricks.iodevices.I2CDevice.read
218232
static mp_obj_t read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
219233
PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
220234
device_obj_t, device,
221235
PB_ARG_DEFAULT_NONE(reg),
222-
PB_ARG_DEFAULT_INT(length, 1)
236+
PB_ARG_DEFAULT_INT(length, 1),
237+
PB_ARG_DEFAULT_NONE(map)
223238
);
224239

225240
// Write payload is one byte representing the register we want to read,
@@ -229,12 +244,18 @@ static mp_obj_t read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
229244
&(uint8_t) { mp_obj_get_int(reg_in) };
230245
size_t write_len = reg_in == mp_const_none ? 0 : 1;
231246

247+
// Optional user provided callback method of the form def my_method(self, data)
248+
// We can use sensor_obj for this since it isn't used by I2CDevice instances,
249+
// and we are already passing this to the mapping anyway, so we can conviently
250+
// use it to pass the callable object in this case.
251+
device->sensor_obj = mp_obj_is_callable(map_in) ? map_in : MP_OBJ_NULL;
252+
232253
return pb_type_i2c_device_start_operation(
233254
MP_OBJ_FROM_PTR(device),
234255
write_data,
235256
write_len,
236257
pb_obj_get_positive_int(length_in),
237-
pb_type_i2c_device_return_bytes
258+
mp_obj_is_callable(map_in) ? pb_type_i2c_device_return_user_map : pb_type_i2c_device_return_bytes
238259
);
239260
}
240261
static MP_DEFINE_CONST_FUN_OBJ_KW(read_obj, 0, read);

0 commit comments

Comments
 (0)