Skip to content

Commit 9deac01

Browse files
committed
pybricks.iodevices.I2CDevices: Use new awaitable.
Also generalize return map to pass in the sensor object. This allows the return map to make use of the sensor state.
1 parent 3d4c5a3 commit 9deac01

File tree

5 files changed

+89
-86
lines changed

5 files changed

+89
-86
lines changed

pybricks/iodevices/iodevices.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
#include "py/obj.h"
1212

13+
#include <pybricks/tools/pb_type_async.h>
14+
1315
extern const mp_obj_type_t pb_type_iodevices_PUPDevice;
1416
extern const mp_obj_type_t pb_type_uart_device;
1517

@@ -21,12 +23,14 @@ extern const mp_obj_type_t pb_type_i2c_device;
2123
* of a desired form. For example, it could map two bytes to a single floating
2224
* point value representing temperature.
2325
*
26+
* @param [in] sensor_obj Instance of sensor that owns this device or MP_OBJ_NULL for the standalone I2CDevice class.
2427
* @param [in] data The data read.
2528
* @param [in] len The data length.
29+
* @return Resulting object to return to user.
2630
*/
27-
typedef mp_obj_t (*pb_type_i2c_device_return_map_t)(const uint8_t *data, size_t len);
31+
typedef mp_obj_t (*pb_type_i2c_device_return_map_t)(mp_obj_t sensor_obj, const uint8_t *data, size_t len);
2832

29-
mp_obj_t pb_type_i2c_device_make_new(mp_obj_t port_in, uint8_t address, bool custom, bool powered, bool nxt_quirk);
33+
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);
3034
mp_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);
3135
void pb_type_i2c_device_assert_string_at_register(mp_obj_t i2c_device_obj, uint8_t reg, const char *string);
3236

pybricks/iodevices/pb_type_i2c_device.c

Lines changed: 77 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,29 @@
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+
*/
2429
typedef 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-
154152
mp_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
*/
209198
void 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
224218
static 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
}
240240
static MP_DEFINE_CONST_FUN_OBJ_KW(read_obj, 0, read);
241241

pybricks/nxtdevices/pb_type_nxtdevices_temperaturesensor.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// pybricks.nxtdevices.TemperatureSensor class object
2222
typedef struct _nxtdevices_TemperatureSensor_obj_t {
2323
mp_obj_base_t base;
24-
mp_obj_t *i2c_device_obj;
24+
mp_obj_t i2c_device_obj;
2525
} nxtdevices_TemperatureSensor_obj_t;
2626

2727
// pybricks.nxtdevices.TemperatureSensor.__init__
@@ -30,7 +30,7 @@ static mp_obj_t nxtdevices_TemperatureSensor_make_new(const mp_obj_type_t *type,
3030
PB_ARG_REQUIRED(port));
3131

3232
nxtdevices_TemperatureSensor_obj_t *self = mp_obj_malloc(nxtdevices_TemperatureSensor_obj_t, type);
33-
self->i2c_device_obj = pb_type_i2c_device_make_new(port_in, 0x4C, true, false, false);
33+
self->i2c_device_obj = pb_type_i2c_device_make_new(MP_OBJ_FROM_PTR(self), port_in, 0x4C, true, false, false);
3434

3535
// Set resolution to 0.125 degrees as a fair balance between speed and accuracy.
3636
const uint8_t write_data[] = { 0x01, (1 << 6) | (0 << 5) };
@@ -39,7 +39,7 @@ static mp_obj_t nxtdevices_TemperatureSensor_make_new(const mp_obj_type_t *type,
3939
return MP_OBJ_FROM_PTR(self);
4040
}
4141

42-
static mp_obj_t map_temperature(const uint8_t *data, size_t len) {
42+
static mp_obj_t map_temperature(mp_obj_t self_in, const uint8_t *data, size_t len) {
4343
int16_t combined = ((uint16_t)data[0] << 8) | data[1];
4444
return mp_obj_new_float_from_f((combined >> 4) / 16.0f);
4545
}

pybricks/nxtdevices/pb_type_nxtdevices_ultrasonicsensor.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// pybricks.nxtdevices.UltrasonicSensor class object
2222
typedef struct _nxtdevices_UltrasonicSensor_obj_t {
2323
mp_obj_base_t base;
24-
mp_obj_t *i2c_device_obj;
24+
mp_obj_t i2c_device_obj;
2525
} nxtdevices_UltrasonicSensor_obj_t;
2626

2727
// pybricks.nxtdevices.UltrasonicSensor.__init__
@@ -30,7 +30,7 @@ static mp_obj_t nxtdevices_UltrasonicSensor_make_new(const mp_obj_type_t *type,
3030
PB_ARG_REQUIRED(port));
3131

3232
nxtdevices_UltrasonicSensor_obj_t *self = mp_obj_malloc(nxtdevices_UltrasonicSensor_obj_t, type);
33-
self->i2c_device_obj = pb_type_i2c_device_make_new(port_in, 0x01, false, true, true);
33+
self->i2c_device_obj = pb_type_i2c_device_make_new(MP_OBJ_FROM_PTR(self), port_in, 0x01, false, true, true);
3434

3535
// NXT Ultrasonic Sensor appears to need some time after initializing I2C pins before it can receive data.
3636
mp_hal_delay_ms(100);
@@ -41,7 +41,7 @@ static mp_obj_t nxtdevices_UltrasonicSensor_make_new(const mp_obj_type_t *type,
4141
return MP_OBJ_FROM_PTR(self);
4242
}
4343

44-
static mp_obj_t map_distance(const uint8_t *data, size_t len) {
44+
static mp_obj_t map_distance(mp_obj_t self_in, const uint8_t *data, size_t len) {
4545
return mp_obj_new_int(data[0] * 10);
4646
}
4747

pybricks/tools/pb_type_app_data.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
#include "py/objstr.h"
1515

1616
#include <pybricks/tools.h>
17-
#include <pybricks/tools/pb_type_awaitable.h>
1817

1918
#include <pybricks/util_mp/pb_kwarg_helper.h>
2019
#include <pybricks/util_mp/pb_obj_helper.h>

0 commit comments

Comments
 (0)