Skip to content

Commit 9cd734b

Browse files
dlechlaurensvalk
andcommitted
pbio/port_dcm_pup: Detect and use switch inputs.
Co-authored-by: Laurens Valk <laurens@pybricks.com>
1 parent 4849642 commit 9cd734b

File tree

3 files changed

+76
-18
lines changed

3 files changed

+76
-18
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44

55
## [Unreleased]
66

7+
### Added
8+
- Added support for Powered Up touch sensors that are supported according to
9+
the specification, but were never released. Users can make their own switch
10+
inputs ([pybricks-micropython#454]).
11+
12+
[pybricks-micropython#454]: https://github.com/pybricks/pybricks-micropython/pull/454
13+
714
## [4.0.0b5] - 2026-01-30
815

916
### Added

lib/pbio/src/port_dcm_pup.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ struct _pbio_port_dcm_t {
4343
dev_id_group_t dev_id1_group;
4444
uint8_t gpio_value;
4545
uint8_t prev_gpio_value;
46+
/** Touch sensor data. */
47+
uint32_t sensor_data;
4648
};
4749

4850
pbio_port_dcm_t dcm_state[PBIO_CONFIG_PORT_DCM_NUM_DEV];
@@ -127,8 +129,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer
127129
PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS);
128130

129131
// ID1 is inverse of touch sensor value
130-
// TODO: save this value to sensor dcm
131-
// sensor_data = !pbdrv_gpio_input(&pins->p5);
132+
dcm->sensor_data = !pbdrv_gpio_input(&pins->p5);
132133
}
133134
// if ID2 changed from low to high
134135
else if (dcm->prev_gpio_value == 0 && dcm->gpio_value == 1) {
@@ -320,13 +321,21 @@ pbio_error_t pbio_port_dcm_assert_type_id(pbio_port_dcm_t *dcm, lego_device_type
320321
case LEGO_DEVICE_TYPE_ID_LPF2_MMOTOR:
321322
case LEGO_DEVICE_TYPE_ID_LPF2_TRAIN:
322323
case LEGO_DEVICE_TYPE_ID_LPF2_LIGHT:
323-
// On Powered Up, the only known existing passive devices are DC
324+
// On Powered Up, the only known official passive devices are DC
324325
// devices. Pass if requesting a specific match or any DC device.
325326
if (*type_id == LEGO_DEVICE_TYPE_ID_ANY_DC_MOTOR || *type_id == dcm->prev_type_id) {
326327
*type_id = dcm->prev_type_id;
327328
return PBIO_SUCCESS;
328329
}
329330
return PBIO_ERROR_NO_DEV;
331+
case LEGO_DEVICE_TYPE_ID_LPF2_TOUCH:
332+
// The Powered Up protocol appears to have been designed to support
333+
// basic switches as touch sensors. None of such sensors were ever
334+
// released, but we can still detect matching custom devices here.
335+
if (*type_id == LEGO_DEVICE_TYPE_ID_LPF2_TOUCH) {
336+
return PBIO_SUCCESS;
337+
}
338+
return PBIO_ERROR_NO_DEV;
330339
case LEGO_DEVICE_TYPE_ID_LPF2_UNKNOWN_UART:
331340
if (*type_id == LEGO_DEVICE_TYPE_ID_ANY_LUMP_UART) {
332341
return PBIO_SUCCESS;
@@ -338,7 +347,9 @@ pbio_error_t pbio_port_dcm_assert_type_id(pbio_port_dcm_t *dcm, lego_device_type
338347
}
339348

340349
uint32_t pbio_port_dcm_get_analog_value(pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins, bool active) {
341-
return 0;
350+
// This platform does not have any analog sensors, but we can use this to
351+
// return the state of a passive touch sensor.
352+
return dcm->sensor_data;
342353
}
343354

344355
pbio_error_t pbio_port_dcm_get_analog_rgba(pbio_port_dcm_t *dcm, pbio_port_dcm_analog_rgba_t *rgba) {

pybricks/iodevices/pb_type_iodevices_pupdevice.c

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,35 +32,36 @@ typedef struct _iodevices_PUPDevice_obj_t {
3232
uint8_t last_mode;
3333
// ID of a passive device, if any.
3434
lego_device_type_id_t passive_id;
35+
// Device port.
36+
pbio_port_t *port;
3537
} iodevices_PUPDevice_obj_t;
3638

3739
/**
3840
* Tests if the given device is a passive device and stores ID.
3941
*
4042
* @param [in] self The PUP device.
41-
* @param [in] port_in The port.
4243
* @return True if passive device, false otherwise.
4344
*/
44-
static bool init_passive_pup_device(iodevices_PUPDevice_obj_t *self, mp_obj_t port_in) {
45-
pb_module_tools_assert_blocking();
46-
47-
pbio_port_id_t port_id = pb_type_enum_get_value(port_in, &pb_enum_type_Port);
45+
static bool init_passive_pup_device(iodevices_PUPDevice_obj_t *self) {
4846

49-
// Get the port instance.
50-
pbio_port_t *port;
51-
pb_assert(pbio_port_get_port(port_id, &port));
47+
// Check for custom devices that follow the Powered Up spec for simple
48+
// switches as touch sensors.
49+
uint32_t value;
50+
pbio_error_t err = pbio_port_get_analog_value(self->port, LEGO_DEVICE_TYPE_ID_LPF2_TOUCH, false, &value);
51+
if (err == PBIO_SUCCESS) {
52+
self->passive_id = LEGO_DEVICE_TYPE_ID_LPF2_TOUCH;
53+
return true;
54+
}
5255

56+
// Check for DC motor or light.
5357
lego_device_type_id_t type_id = LEGO_DEVICE_TYPE_ID_ANY_DC_MOTOR;
5458
pbio_dcmotor_t *dcmotor;
55-
pbio_error_t err = pbio_port_get_dcmotor(port, &type_id, &dcmotor);
56-
59+
err = pbio_port_get_dcmotor(self->port, &type_id, &dcmotor);
5760
if (err == PBIO_SUCCESS) {
5861
self->passive_id = type_id;
5962
return true;
6063
}
6164
return false;
62-
63-
self->passive_id = type_id;
6465
}
6566

6667
// pybricks.iodevices.PUPDevice.__init__
@@ -70,8 +71,14 @@ static mp_obj_t iodevices_PUPDevice_make_new(const mp_obj_type_t *type, size_t n
7071

7172
iodevices_PUPDevice_obj_t *self = mp_obj_malloc(iodevices_PUPDevice_obj_t, type);
7273

74+
pb_module_tools_assert_blocking();
75+
76+
// Get the port instance.
77+
pbio_port_id_t port_id = pb_type_enum_get_value(port_in, &pb_enum_type_Port);
78+
pb_assert(pbio_port_get_port(port_id, &self->port));
79+
7380
// For backwards compatibility, allow class to be used with passive devices.
74-
if (init_passive_pup_device(self, port_in)) {
81+
if (init_passive_pup_device(self)) {
7582
return MP_OBJ_FROM_PTR(self);
7683
}
7784

@@ -158,13 +165,46 @@ static mp_obj_t get_pup_data_tuple(mp_obj_t self_in) {
158165
return mp_obj_new_tuple(mode_info[current_mode].num_values, values);
159166
}
160167

168+
static mp_obj_t iodevices_PUPDevice_touch_sensor_true(mp_obj_t self_in) {
169+
return mp_const_true;
170+
}
171+
172+
static mp_obj_t iodevices_PUPDevice_touch_sensor_false(mp_obj_t self_in) {
173+
return mp_const_false;
174+
}
175+
176+
static pbio_error_t iodevices_PUPDevice_touch_sensor_iter_once(pbio_os_state_t *state, mp_obj_t self_in) {
177+
return PBIO_SUCCESS;
178+
}
179+
161180
// pybricks.iodevices.PUPDevice.read
162181
static mp_obj_t iodevices_PUPDevice_read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
163182
PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
164183
iodevices_PUPDevice_obj_t, self,
165184
PB_ARG_REQUIRED(mode));
166185

167-
// Passive devices don't support reading.
186+
// Allow reading from passive touch sensors as per the Powered Up spec.
187+
// These do not have modes. For this special case, alwyas return a bool,
188+
// even in async mode when other reads would return awaitables.
189+
if (self->passive_id == LEGO_DEVICE_TYPE_ID_LPF2_TOUCH) {
190+
uint32_t value;
191+
pb_assert(pbio_port_get_analog_value(self->port, self->passive_id, false, &value));
192+
193+
if (!pb_module_tools_run_loop_is_active()) {
194+
return mp_obj_new_bool(value);
195+
}
196+
197+
// REVISIT: we could probably make something more efficient here.
198+
pb_type_async_t config = {
199+
.parent_obj = MP_OBJ_FROM_PTR(self),
200+
.iter_once = iodevices_PUPDevice_touch_sensor_iter_once,
201+
.return_map = value ? iodevices_PUPDevice_touch_sensor_true : iodevices_PUPDevice_touch_sensor_false,
202+
};
203+
204+
return pb_type_async_wait_or_await(&config, &self->device_base.last_awaitable, false);
205+
}
206+
207+
// Other passive devices don't support reading.
168208
if (self->passive_id != LEGO_DEVICE_TYPE_ID_LPF2_UNKNOWN_UART) {
169209
pb_assert(PBIO_ERROR_INVALID_OP);
170210
}

0 commit comments

Comments
 (0)