diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b4c45201..17989d7f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ ## [Unreleased] +### Added +- Added support for Powered Up touch sensors that are supported according to + the specification, but were never released. Users can make their own switch + inputs ([pybricks-micropython#454]). + +[pybricks-micropython#454]: https://github.com/pybricks/pybricks-micropython/pull/454 + ## [4.0.0b4] - 2026-01-22 ### Added diff --git a/lib/pbio/src/port_dcm_pup.c b/lib/pbio/src/port_dcm_pup.c index 767c8aa6c..182d63edf 100644 --- a/lib/pbio/src/port_dcm_pup.c +++ b/lib/pbio/src/port_dcm_pup.c @@ -43,6 +43,8 @@ struct _pbio_port_dcm_t { dev_id_group_t dev_id1_group; uint8_t gpio_value; uint8_t prev_gpio_value; + /** Touch sensor data. */ + uint32_t sensor_data; }; 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 PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // ID1 is inverse of touch sensor value - // TODO: save this value to sensor dcm - // sensor_data = !pbdrv_gpio_input(&pins->p5); + dcm->sensor_data = !pbdrv_gpio_input(&pins->p5); } // if ID2 changed from low to high 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 case LEGO_DEVICE_TYPE_ID_LPF2_MMOTOR: case LEGO_DEVICE_TYPE_ID_LPF2_TRAIN: case LEGO_DEVICE_TYPE_ID_LPF2_LIGHT: - // On Powered Up, the only known existing passive devices are DC + // On Powered Up, the only known official passive devices are DC // devices. Pass if requesting a specific match or any DC device. if (*type_id == LEGO_DEVICE_TYPE_ID_ANY_DC_MOTOR || *type_id == dcm->prev_type_id) { *type_id = dcm->prev_type_id; return PBIO_SUCCESS; } return PBIO_ERROR_NO_DEV; + case LEGO_DEVICE_TYPE_ID_LPF2_TOUCH: + // The Powered Up protocol appears to have been designed to support + // basic switches as touch sensors. None of such sensors were ever + // released, but we can still detect matching custom devices here. + if (*type_id == LEGO_DEVICE_TYPE_ID_LPF2_TOUCH) { + return PBIO_SUCCESS; + } + return PBIO_ERROR_NO_DEV; case LEGO_DEVICE_TYPE_ID_LPF2_UNKNOWN_UART: if (*type_id == LEGO_DEVICE_TYPE_ID_ANY_LUMP_UART) { return PBIO_SUCCESS; @@ -338,7 +347,9 @@ pbio_error_t pbio_port_dcm_assert_type_id(pbio_port_dcm_t *dcm, lego_device_type } uint32_t pbio_port_dcm_get_analog_value(pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins, bool active) { - return 0; + // This platform does not have any analog sensors, but we can use this to + // return the state of a passive touch sensor. + return dcm->sensor_data; } pbio_error_t pbio_port_dcm_get_analog_rgba(pbio_port_dcm_t *dcm, pbio_port_dcm_analog_rgba_t *rgba) { diff --git a/pybricks/iodevices/pb_type_iodevices_pupdevice.c b/pybricks/iodevices/pb_type_iodevices_pupdevice.c index f0da7c452..2d69374ca 100644 --- a/pybricks/iodevices/pb_type_iodevices_pupdevice.c +++ b/pybricks/iodevices/pb_type_iodevices_pupdevice.c @@ -32,35 +32,36 @@ typedef struct _iodevices_PUPDevice_obj_t { uint8_t last_mode; // ID of a passive device, if any. lego_device_type_id_t passive_id; + // Device port. + pbio_port_t *port; } iodevices_PUPDevice_obj_t; /** * Tests if the given device is a passive device and stores ID. * * @param [in] self The PUP device. - * @param [in] port_in The port. * @return True if passive device, false otherwise. */ -static bool init_passive_pup_device(iodevices_PUPDevice_obj_t *self, mp_obj_t port_in) { - pb_module_tools_assert_blocking(); +static bool init_passive_pup_device(iodevices_PUPDevice_obj_t *self) { - pbio_port_id_t port_id = pb_type_enum_get_value(port_in, &pb_enum_type_Port); - - // Get the port instance. - pbio_port_t *port; - pb_assert(pbio_port_get_port(port_id, &port)); + // Check for custom devices that follow the Powered Up spec for simple + // switches as touch sensors. + uint32_t value; + pbio_error_t err = pbio_port_get_analog_value(self->port, LEGO_DEVICE_TYPE_ID_LPF2_TOUCH, false, &value); + if (err == PBIO_SUCCESS) { + self->passive_id = LEGO_DEVICE_TYPE_ID_LPF2_TOUCH; + return true; + } + // Check for DC motor or light. lego_device_type_id_t type_id = LEGO_DEVICE_TYPE_ID_ANY_DC_MOTOR; pbio_dcmotor_t *dcmotor; - pbio_error_t err = pbio_port_get_dcmotor(port, &type_id, &dcmotor); - + err = pbio_port_get_dcmotor(self->port, &type_id, &dcmotor); if (err == PBIO_SUCCESS) { self->passive_id = type_id; return true; } return false; - - self->passive_id = type_id; } // 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 iodevices_PUPDevice_obj_t *self = mp_obj_malloc(iodevices_PUPDevice_obj_t, type); + pb_module_tools_assert_blocking(); + + // Get the port instance. + pbio_port_id_t port_id = pb_type_enum_get_value(port_in, &pb_enum_type_Port); + pb_assert(pbio_port_get_port(port_id, &self->port)); + // For backwards compatibility, allow class to be used with passive devices. - if (init_passive_pup_device(self, port_in)) { + if (init_passive_pup_device(self)) { return MP_OBJ_FROM_PTR(self); } @@ -164,7 +171,16 @@ static mp_obj_t iodevices_PUPDevice_read(size_t n_args, const mp_obj_t *pos_args iodevices_PUPDevice_obj_t, self, PB_ARG_REQUIRED(mode)); - // Passive devices don't support reading. + // Allow reading from passive touch sensors as per the Powered Up spec. + // These do not have modes. For this special case, alwyas return a bool, + // even in async mode when other reads would return awaitables. + if (self->passive_id == LEGO_DEVICE_TYPE_ID_LPF2_TOUCH) { + uint32_t value; + pb_assert(pbio_port_get_analog_value(self->port, self->passive_id, false, &value)); + return mp_obj_new_bool(value); + } + + // Other passive devices don't support reading. if (self->passive_id != LEGO_DEVICE_TYPE_ID_LPF2_UNKNOWN_UART) { pb_assert(PBIO_ERROR_INVALID_OP); }