Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 15 additions & 4 deletions lib/pbio/src/port_dcm_pup.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down
44 changes: 30 additions & 14 deletions pybricks/iodevices/pb_type_iodevices_pupdevice.c
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}
Expand Down