Skip to content

Commit 8c2bf4d

Browse files
committed
pbio/sys/telemetry: Add module.
Adds a first version for hub telemetry. We start off with just motor angles, generalizing the motor angles emitted via a socket in the virtual hub.
1 parent 4a5f77d commit 8c2bf4d

File tree

9 files changed

+171
-65
lines changed

9 files changed

+171
-65
lines changed

bricks/_common/sources.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\
251251
sys/status.c \
252252
sys/storage_settings.c \
253253
sys/storage.c \
254+
sys/telemetry.c \
254255
)
255256

256257
# LEGO specification library

lib/pbio/drv/bluetooth/bluetooth_simulation.c

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <stdio.h>
1111
#include <stdlib.h>
1212
#include <unistd.h>
13+
#include <arpa/inet.h>
1314

1415
#include "bluetooth.h"
1516
#include <pbdrv/bluetooth.h>
@@ -50,17 +51,31 @@ bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t connection) {
5051
return false;
5152
}
5253

54+
// Socket used to send data to Python animation.
55+
static int data_socket = -1;
56+
static struct sockaddr_in serv_addr;
5357

5458
pbio_error_t pbdrv_bluetooth_send_pybricks_value_notification(pbio_os_state_t *state, const uint8_t *data, uint16_t size) {
5559
PBIO_OS_ASYNC_BEGIN(state);
5660

57-
// Only care about stdout for now.
58-
if (size < 1 || data[0] != PBIO_PYBRICKS_EVENT_WRITE_STDOUT) {
61+
// Stdout also goes to native stdout.
62+
if (size > 1 && data[0] == PBIO_PYBRICKS_EVENT_WRITE_STDOUT) {
63+
int ret = write(STDOUT_FILENO, data + 1, size - 1);
64+
(void)ret;
65+
}
66+
67+
// No output configured, so done.
68+
if (data_socket < 0) {
5969
return PBIO_SUCCESS;
6070
}
6171

62-
int ret = write(STDOUT_FILENO, data + 1, size - 1);
63-
(void)ret;
72+
// Send the value notification via socket.
73+
ssize_t sent = sendto(data_socket, data, size, 0, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
74+
if (sent < 0) {
75+
printf("send() failed");
76+
close(data_socket);
77+
data_socket = -1;
78+
}
6479

6580
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
6681
}
@@ -180,6 +195,24 @@ void pbdrv_bluetooth_init_hci(void) {
180195
bluetooth_thread_err = PBIO_ERROR_AGAIN;
181196
bluetooth_thread_state = 0;
182197
pbio_os_process_start(&pbdrv_bluetooth_simulation_process, pbdrv_bluetooth_simulation_process_thread, NULL);
198+
199+
// Configure socket if specified.
200+
if (getenv("PBIO_TEST_CONNECT_SOCKET")) {
201+
data_socket = socket(AF_INET, SOCK_DGRAM, 0);
202+
if (data_socket < 0) {
203+
printf("socket() failed");
204+
return;
205+
}
206+
serv_addr.sin_family = AF_INET;
207+
serv_addr.sin_port = htons(5002);
208+
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
209+
printf("inet_pton() failed");
210+
close(data_socket);
211+
data_socket = -1;
212+
return;
213+
}
214+
}
215+
183216
}
184217

185218
#endif // PBDRV_CONFIG_BLUETOOTH_SIMULATION

lib/pbio/drv/motor_driver/motor_driver_virtual_simulation.c

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,6 @@
55

66
#if PBDRV_CONFIG_MOTOR_DRIVER_VIRTUAL_SIMULATION
77

8-
#include <arpa/inet.h>
9-
#include <stdint.h>
10-
#include <stdio.h>
11-
#include <stdlib.h>
12-
#include <string.h>
13-
#include <sys/wait.h>
14-
#include <unistd.h>
15-
#include <unistd.h>
16-
178
#include <contiki.h>
189

1910
#include <pbdrv/clock.h>
@@ -185,27 +176,6 @@ pbio_error_t pbdrv_motor_driver_set_duty_cycle(pbdrv_motor_driver_dev_t *driver,
185176
return PBIO_SUCCESS;
186177
}
187178

188-
static int data_socket = -1;
189-
static struct sockaddr_in serv_addr;
190-
191-
static void animation_socket_connect(void) {
192-
193-
data_socket = socket(AF_INET, SOCK_DGRAM, 0);
194-
if (data_socket < 0) {
195-
perror("socket() failed");
196-
return;
197-
}
198-
199-
serv_addr.sin_family = AF_INET;
200-
serv_addr.sin_port = htons(5002);
201-
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
202-
perror("inet_pton() failed");
203-
close(data_socket);
204-
data_socket = -1;
205-
return;
206-
}
207-
}
208-
209179
PROCESS(pbdrv_motor_driver_virtual_simulation_process, "pbdrv_motor_driver_virtual_simulation");
210180

211181
PROCESS_THREAD(pbdrv_motor_driver_virtual_simulation_process, ev, data) {
@@ -223,25 +193,6 @@ PROCESS_THREAD(pbdrv_motor_driver_virtual_simulation_process, ev, data) {
223193
for (;;) {
224194
PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER && etimer_expired(&tick_timer));
225195

226-
// If data parser pipe is connected, output the motor angles.
227-
if (data_socket >= 0 && timer_expired(&frame_timer)) {
228-
timer_reset(&frame_timer);
229-
uint8_t buf[sizeof(uint32_t) * PBDRV_CONFIG_MOTOR_DRIVER_NUM_DEV + 1] = { 3 };
230-
// Output motor angles on one line.
231-
for (dev_index = 0; dev_index < PBDRV_CONFIG_MOTOR_DRIVER_NUM_DEV; dev_index++) {
232-
driver = &motor_driver_devs[dev_index];
233-
pbio_set_uint32_le(&buf[dev_index * sizeof(uint32_t) + 1], (uint32_t)(driver->angle / 1000));
234-
}
235-
236-
// Send the constructed message
237-
ssize_t sent = sendto(data_socket, buf, sizeof(buf), 0, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
238-
if (sent < 0) {
239-
perror("send() failed");
240-
close(data_socket);
241-
data_socket = -1;
242-
}
243-
}
244-
245196
for (dev_index = 0; dev_index < PBDRV_CONFIG_MOTOR_DRIVER_NUM_DEV; dev_index++) {
246197
driver = &motor_driver_devs[dev_index];
247198

@@ -317,11 +268,6 @@ void pbdrv_counter_init(void) {
317268
void pbdrv_motor_driver_init(void) {
318269
simulation_init();
319270

320-
// Skip if no data parser is given.
321-
if (getenv("PBIO_TEST_CONNECT_SOCKET")) {
322-
animation_socket_connect();
323-
}
324-
325271
if (simulation_enabled) {
326272
process_start(&pbdrv_motor_driver_virtual_simulation_process);
327273
}

lib/pbio/include/pbio/protocol.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,27 @@ typedef enum {
252252
PBIO_PYBRICKS_EVENT_WRITE_STDOUT = 1,
253253

254254
/**
255-
* App data sent from the hub to the host. This is similar to stdout, but
256-
* typically used for data that should not be shown in the user terminal,
257-
* such as sensor telemetry.
255+
* User data sent from the hub to the host app. This is similar to stdout,
256+
* but typically used for data that should not be shown in the user
257+
* terminal.
258258
*
259259
* The payload is a variable number of bytes that was written to app data.
260260
*
261261
* @since Pybricks Profile v1.4.0
262262
*/
263263
PBIO_PYBRICKS_EVENT_WRITE_APP_DATA = 2,
264+
265+
/**
266+
* Telemetry data sent from the hub to the host.
267+
*
268+
* The payload is a variable number of bytes that was written to app data.
269+
*
270+
* Pybricks profile version defines exact encoding of this data.
271+
*
272+
* @since Unreleased. Should not be considered final.
273+
*/
274+
PBIO_PYBRICKS_EVENT_WRITE_TELEMETRY = 3,
275+
264276
/**
265277
* The total number of events that can be queued and sent.
266278
*/

lib/pbio/platform/virtual_hub/animation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,8 @@ def load_and_scale_image(filename, scale=0.25):
5959

6060

6161
def update_state():
62-
global angles
63-
6462
while not data_queue.empty():
6563
data = data_queue.get_nowait()
66-
print(data)
6764
if not isinstance(data, bytes) or len(data) < 2:
6865
continue
6966
event = data[0]
@@ -75,7 +72,10 @@ def update_state():
7572
except UnicodeDecodeError:
7673
print(payload)
7774
elif event == PBIO_PYBRICKS_EVENT_WRITE_PORT_VIEW:
78-
angles = struct.unpack("<iiiiii", payload)
75+
if len(payload) == 6:
76+
type_id, index, angle = struct.unpack("<bbi", payload[0:6])
77+
if type_id == 96:
78+
angles[index] = angle
7979

8080

8181
def main():

lib/pbio/platform/virtual_hub/pbsysconfig.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@
2424
#define PBSYS_CONFIG_STATUS_LIGHT_BLUETOOTH (0)
2525
#define PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS (1)
2626
#define PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS_HUE (240)
27+
#define PBSYS_CONFIG_TELEMETRY (1)
2728
#define PBSYS_CONFIG_USER_PROGRAM (0)
2829
#define PBSYS_CONFIG_PROGRAM_STOP (1)

lib/pbio/sys/core.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "light.h"
2020
#include "storage.h"
2121
#include "program_stop.h"
22+
#include "telemetry.h"
2223

2324
static pbio_os_process_t pbsys_system_poll_process;
2425

@@ -74,6 +75,7 @@ void pbsys_init(void) {
7475
pbsys_hmi_init();
7576
pbsys_host_init();
7677
pbsys_status_light_init();
78+
pbsys_telemetry_init();
7779

7880
pbio_os_process_start(&pbsys_system_poll_process, pbsys_system_poll_process_thread, NULL);
7981

lib/pbio/sys/telemetry.c

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2024 The Pybricks Authors
3+
4+
#include <pbsys/config.h>
5+
6+
#if PBSYS_CONFIG_TELEMETRY
7+
8+
#include <stdio.h>
9+
10+
#include <pbio/os.h>
11+
#include <pbio/port_interface.h>
12+
#include <pbio/util.h>
13+
14+
#include <pbsys/host.h>
15+
16+
typedef struct {
17+
lego_device_type_id_t type_id;
18+
int32_t value;
19+
} pbsys_telemetry_port_data_t;
20+
21+
static pbsys_telemetry_port_data_t last_data[PBIO_CONFIG_PORT_NUM_DEV];
22+
23+
// Revisit: Come up with a data encoding protocol. Right now it just sends
24+
// six motor positions to drive the existing motor animation.
25+
static uint8_t update_port_data(uint8_t index, uint8_t *buf) {
26+
27+
// Get type and angle.
28+
int32_t degrees = 0;
29+
pbio_angle_t angle;
30+
lego_device_type_id_t type_id = LEGO_DEVICE_TYPE_ID_NONE;
31+
pbio_port_t *port = pbio_port_by_index(index);
32+
pbio_error_t err = pbio_port_get_angle(port, &angle);
33+
if (err == PBIO_SUCCESS) {
34+
type_id = LEGO_DEVICE_TYPE_ID_ANY_ENCODED_MOTOR;
35+
degrees = pbio_angle_to_low_res(&angle, 1000);
36+
}
37+
38+
pbsys_telemetry_port_data_t *data = &last_data[index];
39+
40+
if (data->type_id == type_id && data->value == degrees) {
41+
return 0;
42+
}
43+
44+
data->type_id = type_id;
45+
data->value = degrees;
46+
47+
buf[0] = type_id;
48+
buf[1] = index;
49+
pbio_set_uint32_le(&buf[2], degrees);
50+
return 6;
51+
}
52+
53+
/**
54+
* Hub, motor, and sensor telemetry to host.
55+
*/
56+
static pbio_error_t pbsys_telemetry_process_thread(pbio_os_state_t *state, void *context) {
57+
58+
static pbio_os_timer_t timer;
59+
static pbio_os_state_t sub;
60+
static uint8_t buf[20];
61+
static uint8_t size;
62+
static uint32_t i = 0;
63+
64+
PBIO_OS_ASYNC_BEGIN(state);
65+
66+
for (;;) {
67+
PBIO_OS_AWAIT_MS(state, &timer, 40);
68+
69+
// Revisit, could concatenate packages for more efficient BLE sending.
70+
for (i = 0; i < PBIO_CONFIG_PORT_NUM_DEV; i++) {
71+
size = update_port_data(i, buf);
72+
if (size) {
73+
PBIO_OS_AWAIT(state, &sub, pbsys_host_send_event(&sub, PBIO_PYBRICKS_EVENT_WRITE_TELEMETRY, buf, size));
74+
}
75+
}
76+
}
77+
78+
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
79+
}
80+
81+
/**
82+
*
83+
* Starts telemetry process.
84+
*/
85+
void pbsys_telemetry_init(void) {
86+
static pbio_os_process_t pbsys_telemetry_process;
87+
pbio_os_process_start(&pbsys_telemetry_process, pbsys_telemetry_process_thread, NULL);
88+
}
89+
90+
#endif // PBSYS_CONFIG_TELEMETRY

lib/pbio/sys/telemetry.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2024 The Pybricks Authors
3+
4+
#ifndef _PBSYS_SYS_TELEMETRY_H_
5+
#define _PBSYS_SYS_TELEMETRY_H_
6+
7+
#include <pbsys/config.h>
8+
9+
10+
#if PBSYS_CONFIG_TELEMETRY
11+
12+
void pbsys_telemetry_init(void);
13+
14+
#else
15+
16+
static inline void pbsys_telemetry_init(void) {
17+
}
18+
19+
#endif // PBSYS_CONFIG_TELEMETRY
20+
21+
#endif // _PBSYS_SYS_TELEMETRY_H_

0 commit comments

Comments
 (0)