Skip to content

Commit 1641df5

Browse files
authored
pybricks.iodevices: Add LWP3Device class
Class to communicate with a device that supports the LEGO Wireless Protocol 3.0.00.
1 parent b5aa3e6 commit 1641df5

File tree

6 files changed

+231
-0
lines changed

6 files changed

+231
-0
lines changed

AUTHORS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@
88
**Maintainers:**
99
- Laurens Valk (@laurensvalk)
1010
- David Lechner (@dlech)
11+
12+
**Contributors:**
13+
- Nard Strijbosch (@NStrijbosch)

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
new contributors to experiment with Pybricks using C code.
1313
- Added ability to import the `main.mpy` that is embedded in the firmware from
1414
a download and run program ([support#408]).
15+
- Add `pybricks.iodevices.LWP3Device` to communicate with a device that supports the LEGO Wireless Protocol 3.0.00 ([support#262])
1516

1617
### Changed
1718
- Move Hub Bluetooth optimizations to reduce firmware size ([issue#49]).

bricks/stm32/stm32.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ PYBRICKS_PYBRICKS_SRC_C = $(addprefix pybricks/,\
216216
iodevices/pb_type_iodevices_ev3devsensor.c \
217217
iodevices/pb_type_iodevices_i2cdevice.c \
218218
iodevices/pb_type_iodevices_lumpdevice.c \
219+
iodevices/pb_type_iodevices_lwp3device.c \
219220
iodevices/pb_type_iodevices_pupdevice.c \
220221
iodevices/pb_type_iodevices_uartdevice.c \
221222
media/pb_module_media.c \

pybricks/iodevices.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010

1111
#include "py/obj.h"
1212

13+
#include <pybricks/util_pb/pb_device.h>
14+
1315
extern const mp_obj_module_t pb_module_iodevices;
1416

1517
#if PYBRICKS_PY_PUPDEVICES
1618

19+
extern const mp_obj_type_t pb_type_iodevices_LWP3Device;
1720
extern const mp_obj_type_t pb_type_iodevices_PUPDevice;
1821

1922
#endif // PYBRICKS_PY_PUPDEVICES

pybricks/iodevices/pb_module_iodevices.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ STATIC const mp_rom_map_elem_t iodevices_globals_table[] = {
1212
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_iodevices) },
1313
#if PYBRICKS_PY_PUPDEVICES
1414
{ MP_ROM_QSTR(MP_QSTR_PUPDevice), MP_ROM_PTR(&pb_type_iodevices_PUPDevice) },
15+
{ MP_ROM_QSTR(MP_QSTR_LWP3Device), MP_ROM_PTR(&pb_type_iodevices_LWP3Device) },
1516
#endif
1617
#if PYBRICKS_PY_EV3DEVICES
1718
{ MP_ROM_QSTR(MP_QSTR_LUMPDevice), MP_ROM_PTR(&pb_type_iodevices_LUMPDevice) },
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2021 The Pybricks Authors
3+
4+
#include "py/mpconfig.h"
5+
6+
#if PYBRICKS_PY_IODEVICES && PYBRICKS_PY_PUPDEVICES
7+
8+
#include <stdbool.h>
9+
#include <stdint.h>
10+
11+
#include <lego_lwp3.h>
12+
13+
#include <pbdrv/bluetooth.h>
14+
#include <pbio/error.h>
15+
#include <pbio/task.h>
16+
17+
#include <pybricks/common.h>
18+
#include <pybricks/parameters.h>
19+
#include <pybricks/util_mp/pb_kwarg_helper.h>
20+
#include <pybricks/util_mp/pb_obj_helper.h>
21+
#include <pybricks/util_pb/pb_error.h>
22+
#include <pybricks/util_pb/pb_task.h>
23+
24+
#include "py/mphal.h"
25+
#include "py/runtime.h"
26+
#include "py/obj.h"
27+
#include "py/mperrno.h"
28+
29+
// TODO: most of the functions in this class can be shared with the Remote class
30+
31+
#define LWP3_HEADER_SIZE 3
32+
33+
// MTU is assumed to be 23, not the actual negotiated MTU.
34+
// A overhead of 3 yields a max message size of 20 (=23-3)
35+
#define LWP3_MAX_MESSAGE_SIZE 20
36+
37+
typedef struct {
38+
pbio_task_t task;
39+
uint8_t buffer[LWP3_MAX_MESSAGE_SIZE];
40+
bool notification_received;
41+
pbdrv_bluetooth_scan_and_connect_context_t context;
42+
} pb_lwp3device_t;
43+
44+
STATIC pb_lwp3device_t pb_lwp3device_singleton;
45+
46+
// Handles LEGO Wireless protocol messages from the LWP3 Device
47+
STATIC void handle_notification(pbdrv_bluetooth_connection_t connection, const uint8_t *value, uint8_t size) {
48+
pb_lwp3device_t *lwp3device = &pb_lwp3device_singleton;
49+
50+
// Each message overwrites the previous received messages
51+
// Messages will be lost if they are not read fast enough
52+
memcpy(lwp3device->buffer, &value[0], (size < LWP3_MAX_MESSAGE_SIZE) ? size : LWP3_MAX_MESSAGE_SIZE);
53+
54+
lwp3device->notification_received = true;
55+
}
56+
57+
STATIC void lwp3device_connect(const uint8_t hub_kind, const char *name, mp_int_t timeout) {
58+
pb_lwp3device_t *lwp3device = &pb_lwp3device_singleton;
59+
60+
// REVISIT: for now, we only allow a single connection to a LWP3 device.
61+
if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PERIPHERAL_LWP3)) {
62+
pb_assert(PBIO_ERROR_BUSY);
63+
}
64+
65+
// clear memory after reconnect to empty buffer
66+
// we are using static memory
67+
memset(lwp3device, 0, sizeof(*lwp3device));
68+
69+
lwp3device->context.hub_kind = hub_kind;
70+
71+
if (name) {
72+
strncpy(lwp3device->context.name, name, sizeof(lwp3device->context.name));
73+
}
74+
75+
pbdrv_bluetooth_set_notification_handler(handle_notification);
76+
pbdrv_bluetooth_scan_and_connect(&lwp3device->task, &lwp3device->context);
77+
pb_wait_task(&lwp3device->task, timeout);
78+
}
79+
80+
STATIC void lwp3device_assert_connected(void) {
81+
if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PERIPHERAL_LWP3)) {
82+
mp_raise_OSError(MP_ENODEV);
83+
}
84+
}
85+
86+
typedef struct _pb_type_iodevices_LWP3Device_obj_t {
87+
mp_obj_base_t base;
88+
} pb_type_iodevices_LWP3Device_obj_t;
89+
90+
STATIC mp_obj_t pb_type_iodevices_LWP3Device_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
91+
PB_PARSE_ARGS_CLASS(n_args, n_kw, args,
92+
PB_ARG_REQUIRED(hub_kind),
93+
PB_ARG_DEFAULT_NONE(name),
94+
PB_ARG_DEFAULT_INT(timeout, 10000));
95+
96+
pb_type_iodevices_LWP3Device_obj_t *self = m_new_obj(pb_type_iodevices_LWP3Device_obj_t);
97+
self->base.type = (mp_obj_type_t *)type;
98+
99+
uint8_t hub_kind = pb_obj_get_positive_int(hub_kind_in);
100+
101+
const char *name = name_in == mp_const_none ? NULL : mp_obj_str_get_str(name_in);
102+
mp_int_t timeout = timeout_in == mp_const_none ? -1 : pb_obj_get_positive_int(timeout_in);
103+
lwp3device_connect(hub_kind, name, timeout);
104+
105+
return MP_OBJ_FROM_PTR(self);
106+
}
107+
108+
STATIC mp_obj_t lwp3device_name(size_t n_args, const mp_obj_t *args) {
109+
pb_lwp3device_t *lwp3device = &pb_lwp3device_singleton;
110+
111+
lwp3device_assert_connected();
112+
113+
if (n_args == 2) {
114+
size_t len;
115+
const char *name = mp_obj_str_get_data(args[1], &len);
116+
117+
if (len == 0 || len > LWP3_MAX_MESSAGE_SIZE) {
118+
mp_raise_ValueError(MP_ERROR_TEXT("bad name length"));
119+
}
120+
121+
struct {
122+
pbdrv_bluetooth_value_t value;
123+
uint8_t length;
124+
uint8_t hub;
125+
uint8_t type;
126+
uint8_t property;
127+
uint8_t operation;
128+
char payload[LWP3_MAX_HUB_PROPERTY_NAME_SIZE];
129+
} __attribute__((packed)) msg;
130+
131+
msg.value.size = msg.length = len + 5;
132+
msg.hub = 0;
133+
msg.type = LWP3_MSG_TYPE_HUB_PROPERTIES;
134+
msg.property = LWP3_HUB_PROPERTY_NAME;
135+
msg.operation = LWP3_HUB_PROPERTY_OP_SET;
136+
memcpy(msg.payload, name, len);
137+
138+
// NB: operation is not cancelable, so timeout is not used
139+
pbdrv_bluetooth_write_remote(&lwp3device->task, &msg.value);
140+
pb_wait_task(&lwp3device->task, -1);
141+
142+
// assuming write was successful instead of reading back from the handset
143+
memcpy(lwp3device->context.name, name, len);
144+
lwp3device->context.name[len] = 0;
145+
146+
return mp_const_none;
147+
}
148+
149+
return mp_obj_new_str(lwp3device->context.name, strlen(lwp3device->context.name));
150+
}
151+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(lwp3device_name_obj, 1, 2, lwp3device_name);
152+
153+
STATIC mp_obj_t lwp3device_write(mp_obj_t self_in, mp_obj_t buf_in) {
154+
pb_lwp3device_t *lwp3device = &pb_lwp3device_singleton;
155+
156+
lwp3device_assert_connected();
157+
158+
mp_buffer_info_t bufinfo;
159+
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ);
160+
161+
if (bufinfo.len < LWP3_HEADER_SIZE || bufinfo.len > LWP3_MAX_MESSAGE_SIZE) {
162+
mp_raise_ValueError(MP_ERROR_TEXT("bad length"));
163+
}
164+
if (((uint8_t *)bufinfo.buf)[0] != bufinfo.len) {
165+
mp_raise_ValueError(MP_ERROR_TEXT("length in header wrong"));
166+
}
167+
168+
struct {
169+
pbdrv_bluetooth_value_t value;
170+
char payload[LWP3_MAX_MESSAGE_SIZE];
171+
} __attribute__((packed)) msg = {
172+
.value.size = bufinfo.len,
173+
};
174+
memcpy(msg.payload, bufinfo.buf, bufinfo.len);
175+
176+
pbdrv_bluetooth_write_remote(&lwp3device->task, &msg.value);
177+
pb_wait_task(&lwp3device->task, -1);
178+
179+
return mp_const_none;
180+
}
181+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(lwp3device_write_obj, lwp3device_write);
182+
183+
STATIC mp_obj_t lwp3device_read(mp_obj_t self_in) {
184+
pb_lwp3device_t *lwp3device = &pb_lwp3device_singleton;
185+
186+
// wait until a notification is received
187+
for (;;) {
188+
lwp3device_assert_connected();
189+
190+
if (lwp3device->notification_received) {
191+
lwp3device->notification_received = false;
192+
break;
193+
}
194+
195+
MICROPY_EVENT_POLL_HOOK
196+
}
197+
198+
size_t len = lwp3device->buffer[0];
199+
200+
if (len < LWP3_HEADER_SIZE || len > LWP3_MAX_MESSAGE_SIZE) {
201+
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("bad data"));
202+
}
203+
204+
return mp_obj_new_bytes(lwp3device->buffer, len);
205+
}
206+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(lwp3device_read_obj, lwp3device_read);
207+
208+
STATIC const mp_rom_map_elem_t pb_type_iodevices_LWP3Device_locals_dict_table[] = {
209+
{ MP_ROM_QSTR(MP_QSTR_name), MP_ROM_PTR(&lwp3device_name_obj) },
210+
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&lwp3device_write_obj) },
211+
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&lwp3device_read_obj) },
212+
};
213+
STATIC MP_DEFINE_CONST_DICT(pb_type_iodevices_LWP3Device_locals_dict, pb_type_iodevices_LWP3Device_locals_dict_table);
214+
215+
const mp_obj_type_t pb_type_iodevices_LWP3Device = {
216+
{ &mp_type_type },
217+
.name = MP_QSTR_LWP3Device,
218+
.make_new = pb_type_iodevices_LWP3Device_make_new,
219+
.locals_dict = (mp_obj_dict_t *)&pb_type_iodevices_LWP3Device_locals_dict,
220+
};
221+
222+
#endif // PYBRICKS_PY_IODEVICES && PYBRICKS_PY_PUPDEVICES

0 commit comments

Comments
 (0)