• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pybricks / pybricks-micropython / 6675885095

28 Oct 2023 08:38AM UTC coverage: 56.053% (+10.0%) from 46.074%
6675885095

push

github

laurensvalk
pybricks.hubs.MoveHub: Use standard hub init.

The Move Hub cannot have true vector axes, but we can still use this API to initialize the hub using numeric indices.

This ensures we can use the custom orientation not just in tilt, but also in acceleration like we do on other hubs.

3616 of 6451 relevant lines covered (56.05%)

20895680.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.34
/lib/pbio/sys/bluetooth.c
1
// SPDX-License-Identifier: MIT
2
// Copyright (c) 2020-2023 The Pybricks Authors
3

4
#include <pbsys/config.h>
5

6
#if PBSYS_CONFIG_BLUETOOTH
7

8
#include <assert.h>
9
#include <stdbool.h>
10
#include <stdint.h>
11

12
#include <contiki-lib.h>
13
#include <contiki.h>
14
#include <lwrb/lwrb.h>
15

16
#include <pbdrv/bluetooth.h>
17
#include <pbio/error.h>
18
#include <pbio/event.h>
19
#include <pbio/protocol.h>
20
#include <pbio/util.h>
21
#include <pbsys/bluetooth.h>
22
#include <pbsys/command.h>
23
#include <pbsys/status.h>
24

25
// REVISIT: this can be the negotiated MTU - 3 to allow for better throughput
26
#define MAX_CHAR_SIZE 20
27

28
// REVISIT: this needs to be moved to a common place where it can be shared with USB
29
static pbsys_bluetooth_stdin_event_callback_t stdin_event_callback;
30
static lwrb_t stdout_ring_buf;
31
static lwrb_t stdin_ring_buf;
32

33
typedef struct {
34
    list_t queue;
35
    pbdrv_bluetooth_send_context_t context;
36
    bool is_queued;
37
    uint8_t payload[MAX_CHAR_SIZE];
38
} send_msg_t;
39

40
static send_msg_t stdout_msg;
41
LIST(send_queue);
42
static bool send_busy;
43

44
PROCESS(pbsys_bluetooth_process, "Bluetooth");
45

46
// Internal API
47

48
/** Initializes Bluetooth. */
49
void pbsys_bluetooth_init(void) {
1✔
50
    // enough for two packets, one currently being sent and one to be ready
51
    // as soon as the previous one completes + 1 byte for ring buf pointer
52
    static uint8_t stdout_buf[MAX_CHAR_SIZE * 2 + 1];
53
    // enough for one packet received + 1 byte for ring buf pointer
54
    static uint8_t stdin_buf[PBDRV_BLUETOOTH_MAX_MTU_SIZE - 3 + 1];
55

56
    lwrb_init(&stdout_ring_buf, stdout_buf, PBIO_ARRAY_SIZE(stdout_buf));
1✔
57
    lwrb_init(&stdin_ring_buf, stdin_buf, PBIO_ARRAY_SIZE(stdin_buf));
1✔
58
    process_start(&pbsys_bluetooth_process);
1✔
59
}
1✔
60

61
/**
62
 * Gets the number of bytes currently free for writing in stdin.
63
 * @return              The number of bytes.
64
 */
65
uint32_t pbsys_bluetooth_rx_get_free(void) {
1✔
66
    return lwrb_get_free(&stdin_ring_buf);
1✔
67
}
68

69
/**
70
 * Writes data to the stdin buffer.
71
 *
72
 * This does not currently return the number of bytes written, so first call
73
 * pbsys_bluetooth_rx_get_free() to ensure enough free space.
74
 *
75
 * @param [in]  data    The data to write to the stdin buffer.
76
 * @param [in]  size    The size of @p data in bytes.
77
 */
78
void pbsys_bluetooth_rx_write(const uint8_t *data, uint32_t size) {
1✔
79
    if (stdin_event_callback) {
1✔
80
        // If there is a callback hook, we have to process things one byte at
81
        // a time.
82
        for (uint32_t i = 0; i < size; i++) {
×
83
            if (!stdin_event_callback(data[i])) {
×
84
                lwrb_write(&stdin_ring_buf, &data[i], 1);
×
85
            }
86
        }
87
    } else {
88
        lwrb_write(&stdin_ring_buf, data, size);
1✔
89
    }
90
}
1✔
91

92
// Public API
93

94
/**
95
 * Sets the UART Rx callback function.
96
 * @param callback  [in]    The callback or NULL.
97
 */
98
void pbsys_bluetooth_rx_set_callback(pbsys_bluetooth_stdin_event_callback_t callback) {
×
99
    stdin_event_callback = callback;
×
100
}
×
101

102
/**
103
 * Gets the number of bytes currently available to be read from the UART Rx
104
 * characteristic.
105
 * @return              The number of bytes.
106
 */
107
uint32_t pbsys_bluetooth_rx_get_available(void) {
×
108
    return lwrb_get_full(&stdin_ring_buf);
×
109
}
110

111
/**
112
 * Reads data from the stdin buffer.
113
 * @param data  [in]        A buffer to receive a copy of the data.
114
 * @param size  [in, out]   The number of bytes to read (@p data must be at least
115
 *                          this big). After return @p size contains the number
116
 *                          of bytes actually read.
117
 * @return                  ::PBIO_SUCCESS if @p data was read, ::PBIO_ERROR_AGAIN
118
 *                          if @p data could not be read at this time (i.e. buffer
119
 *                          is empty), ::PBIO_ERROR_INVALID_OP if there is not an
120
 *                          active Bluetooth connection or ::PBIO_ERROR_NOT_SUPPORTED
121
 *                          if this platform does not support Bluetooth.
122
 */
123
pbio_error_t pbsys_bluetooth_rx(uint8_t *data, uint32_t *size) {
2✔
124
    // make sure we have a Bluetooth connection
125
    if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) {
2✔
126
        return PBIO_ERROR_INVALID_OP;
×
127
    }
128

129
    if ((*size = lwrb_read(&stdin_ring_buf, data, *size)) == 0) {
2✔
130
        return PBIO_ERROR_AGAIN;
1✔
131
    }
132

133
    return PBIO_SUCCESS;
1✔
134
}
135

136
/**
137
 * Flushes data from the UART Rx characteristic so that ::pbsys_bluetooth_rx
138
 * can be used to wait for new data.
139
 */
140
void pbsys_bluetooth_rx_flush(void) {
×
141
    lwrb_reset(&stdin_ring_buf);
×
142
}
×
143

144
/**
145
 * Queues data to be transmitted via Bluetooth serial port.
146
 * @param data  [in]        The data to be sent.
147
 * @param size  [in, out]   The size of @p data in bytes. After return, @p size
148
 *                          contains the number of bytes actually written.
149
 * @return                  ::PBIO_SUCCESS if @p data was queued, ::PBIO_ERROR_AGAIN
150
 *                          if @p data could not be queued at this time (e.g. buffer
151
 *                          is full), ::PBIO_ERROR_INVALID_OP if there is not an
152
 *                          active Bluetooth connection or ::PBIO_ERROR_NOT_SUPPORTED
153
 *                          if this platform does not support Bluetooth.
154
 */
155
pbio_error_t pbsys_bluetooth_tx(const uint8_t *data, uint32_t *size) {
3✔
156
    // make sure we have a Bluetooth connection
157
    if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) {
3✔
158
        return PBIO_ERROR_INVALID_OP;
1✔
159
    }
160

161
    // only allow one UART Tx message in the queue at a time
162
    if (!stdout_msg.is_queued) {
2✔
163
        // Setting data and size are deferred until we actually send the message.
164
        // This way, if the caller is only writing one byte at a time, we can
165
        // still buffer data to send it more efficiently.
166
        stdout_msg.context.connection = PBDRV_BLUETOOTH_CONNECTION_PYBRICKS;
2✔
167

168
        list_add(send_queue, &stdout_msg);
2✔
169
        stdout_msg.is_queued = true;
2✔
170
    }
171

172
    if ((*size = lwrb_write(&stdout_ring_buf, data, *size)) == 0) {
2✔
173
        return PBIO_ERROR_AGAIN;
×
174
    }
175

176
    // poke the process to start tx soon-ish. This way, we can accumulate up to
177
    // MAX_CHAR_SIZE bytes before actually transmitting
178
    process_poll(&pbsys_bluetooth_process);
2✔
179

180
    return PBIO_SUCCESS;
2✔
181
}
182

183
/**
184
 * Tests if the Tx queue is empty and all data has been sent over the air.
185
 *
186
 * If there is no connection, this will always return @c true.
187
 *
188
 * @returns @c true if the condition is met, otherwise @c false.
189
 */
190
bool pbsys_bluetooth_tx_is_idle(void) {
×
191
    if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) {
×
192
        return true;
×
193
    }
194

195
    return !send_busy && lwrb_get_full(&stdout_ring_buf) == 0;
×
196
}
197

198
// Contiki process
199

200
static void on_event(void) {
148✔
201
    process_poll(&pbsys_bluetooth_process);
148✔
202
}
148✔
203

204
static pbio_pybricks_error_t handle_receive(pbdrv_bluetooth_connection_t connection, const uint8_t *data, uint32_t size) {
1✔
205
    if (connection == PBDRV_BLUETOOTH_CONNECTION_PYBRICKS) {
1✔
206
        return pbsys_command(data, size);
1✔
207
    }
208

209
    if (connection == PBDRV_BLUETOOTH_CONNECTION_UART) {
×
210
        // TODO: need a new buffer for NUS
211
        // lwrb_write(&uart_rx_ring, data, size);
212
        return PBIO_PYBRICKS_ERROR_OK;
×
213
    }
214

215
    return PBIO_PYBRICKS_ERROR_INVALID_HANDLE;
×
216
}
217

218
static void send_done(void) {
4✔
219
    send_msg_t *msg = list_pop(send_queue);
4✔
220

221
    if (msg == &stdout_msg && lwrb_get_full(&stdout_ring_buf)) {
4✔
222
        // If there is more buffered data to send, put the message back in the queue
223
        list_add(send_queue, msg);
×
224
    } else {
225
        msg->is_queued = false;
4✔
226
    }
227

228
    send_busy = false;
4✔
229
    process_poll(&pbsys_bluetooth_process);
4✔
230
}
4✔
231

232
// drain all buffers and queues and reset global state
233
static void reset_all(void) {
×
234
    send_msg_t *msg;
235

236
    while ((msg = list_pop(send_queue))) {
×
237
        msg->is_queued = false;
×
238
    }
239

240
    send_busy = false;
×
241

242
    lwrb_reset(&stdin_ring_buf);
×
243
    lwrb_reset(&stdout_ring_buf);
×
244
}
×
245

246
static PT_THREAD(pbsys_bluetooth_monitor_status(struct pt *pt)) {
6✔
247
    static struct etimer timer;
248
    static uint32_t old_status_flags, new_status_flags;
249
    static send_msg_t msg;
250

251
    PT_BEGIN(pt);
6✔
252

253
    // This ensures that we always send a notification with the current status
254
    // right after notifications are enabled.
255
    old_status_flags = ~0;
1✔
256

257
    // Send status periodically as well in case a notification was missed due
258
    // to bad RF environment or bug like https://crbug.com/1195592
259
    etimer_set(&timer, 500);
1✔
260

261
    for (;;) {
262
        // wait for status to change or timeout
263
        PT_WAIT_UNTIL(pt, (new_status_flags = pbsys_status_get_flags()) != old_status_flags || etimer_expired(&timer));
5✔
264

265
        etimer_restart(&timer);
2✔
266

267
        // send the message
268
        msg.context.size = pbio_pybricks_event_status_report(&msg.payload[0], new_status_flags);
2✔
269
        msg.context.connection = PBDRV_BLUETOOTH_CONNECTION_PYBRICKS;
2✔
270
        list_add(send_queue, &msg);
2✔
271
        msg.is_queued = true;
2✔
272
        old_status_flags = new_status_flags;
2✔
273

274
        // wait for message to be sent - note: it is possible to miss status changes
275
        // if the status changes and then changes back to old_status_flags while we
276
        // are waiting.
277
        PT_WAIT_WHILE(pt, msg.is_queued);
5✔
278
    }
279

280
    PT_END(pt);
×
281
}
282

283
PROCESS_THREAD(pbsys_bluetooth_process, ev, data) {
79✔
284
    static struct etimer timer;
285
    static struct pt status_monitor_pt;
286

287
    PROCESS_BEGIN();
79✔
288

289
    pbdrv_bluetooth_set_on_event(on_event);
1✔
290
    pbdrv_bluetooth_set_receive_handler(handle_receive);
1✔
291

292
    while (!pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN)) {
1✔
293
        // make sure the Bluetooth chip is in reset long enough to actually reset
294
        etimer_set(&timer, 150);
1✔
295
        PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER && etimer_expired(&timer));
2✔
296

297
        pbdrv_bluetooth_power_on(true);
1✔
298

299
        PROCESS_WAIT_UNTIL(pbdrv_bluetooth_is_ready());
1✔
300

301
        pbdrv_bluetooth_start_advertising();
1✔
302

303
        // TODO: allow user programs to initiate BLE connections
304
        pbsys_status_set(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING);
1✔
305
        PROCESS_WAIT_UNTIL(
70✔
306
            pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE)
307
            || pbsys_status_test(PBIO_PYBRICKS_STATUS_USER_PROGRAM_RUNNING)
308
            || pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN));
309

310
        if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE)) {
1✔
311
            // if a connection wasn't made, we need to manually stop advertising
312
            pbdrv_bluetooth_stop_advertising();
×
313
        }
314

315
        pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING);
1✔
316

317
        PT_INIT(&status_monitor_pt);
1✔
318

319
        while (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE)
9✔
320
               && !pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN)) {
9✔
321

322
            if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) {
9✔
323
                // Since pbsys status events are broadcast to all processes, this
324
                // will get triggered right away if there is a status change event.
325
                pbsys_bluetooth_monitor_status(&status_monitor_pt);
6✔
326
            } else {
327
                // REVISIT: this is probably a bit inefficient since it only
328
                // needs to be called once each time notifications are enabled
329
                PT_INIT(&status_monitor_pt);
3✔
330
            }
331

332
            if (!send_busy) {
9✔
333
                // msg is removed from queue in send_done callback rather than here
334
                send_msg_t *msg = list_head(send_queue);
9✔
335
                if (msg) {
9✔
336
                    msg->context.done = send_done;
4✔
337

338
                    if (msg == &stdout_msg) {
4✔
339
                        msg->payload[0] = PBIO_PYBRICKS_EVENT_WRITE_STDOUT;
2✔
340
                        // REVISIT: use negotiated MTU instead of minimum safe size
341
                        msg->context.size = lwrb_read(&stdout_ring_buf, &msg->payload[1], PBIO_ARRAY_SIZE(msg->payload) - 1) + 1;
2✔
342
                        assert(msg->context.size > 1);
2✔
343
                    }
344

345
                    msg->context.data = &msg->payload[0];
4✔
346
                    send_busy = true;
4✔
347
                    pbdrv_bluetooth_send(&msg->context);
4✔
348
                }
349
            }
350

351
            PROCESS_WAIT_EVENT();
17✔
352
        }
353

354
        reset_all();
×
355
        PROCESS_WAIT_WHILE(pbsys_status_test(PBIO_PYBRICKS_STATUS_USER_PROGRAM_RUNNING));
×
356

357
        // reset Bluetooth chip
358
        pbdrv_bluetooth_power_on(false);
×
359
        PROCESS_WAIT_WHILE(pbdrv_bluetooth_is_ready());
×
360
    }
361

362
    PROCESS_END();
×
363
}
364

365
#endif // PBSYS_CONFIG_BLUETOOTH
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc