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

pybricks / pybricks-micropython / 5024910657

pending completion
5024910657

Pull #169

github

GitHub
Merge aaf8a56d0 into 016157561
Pull Request #169: Refactor src/uartdev for async and code size

64 of 285 new or added lines in 13 files covered. (22.46%)

7 existing lines in 6 files now uncovered.

3161 of 5991 relevant lines covered (52.76%)

32971130.27 hits per line

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

66.8
/lib/pbio/src/uartdev.c
1
// SPDX-License-Identifier: MIT OR GPL-2.0-only
2
// Copyright (c) 2018-2022 The Pybricks Authors
3

4
/*
5
 * Based on:
6
 * LEGO MINDSTORMS EV3 UART Sensor tty line discipline
7
 *
8
 * Copyright (c) 2014-2016,2018-2019 David Lechner <david@lechnology.com>
9
 *
10
 * Relicensed under MIT license by author for Pybricks I/O library.
11
 * This file may be redistributed under either or both licenses.
12
 */
13

14
#include <pbio/config.h>
15

16
#if PBIO_CONFIG_UARTDEV
17

18
#define DEBUG 0
19
#if DEBUG
20
#include <inttypes.h>
21
#define debug_pr(fmt, ...)   printf((fmt), __VA_ARGS__)
22
#define DBG_ERR(expr) expr
23
#else
24
#define debug_pr(...)
25
#define DBG_ERR(expr)
26
#endif
27

28
// Enables processing of information not needed for normal operation
29
#define UARTDEV_ENABLE_EXTRAS (DEBUG)
30

31
#include <stdint.h>
32
#include <stdio.h>
33
#include <string.h>
34

35
#include <contiki.h>
36
#include <lego_uart.h>
37

38
#include "pbdrv/config.h"
39
#include "pbdrv/ioport.h"
40
#include "pbdrv/uart.h"
41
#include "pbio/error.h"
42
#include "pbio/event.h"
43
#include "pbio/iodev.h"
44
#include "pbio/port.h"
45
#include "pbio/uartdev.h"
46
#include "pbio/util.h"
47
#include <pbdrv/motor_driver.h>
48

49
#define EV3_UART_MAX_MESSAGE_SIZE   (LUMP_MAX_MSG_SIZE + 3)
50

51
#define EV3_UART_MAX_DATA_ERR       6
52

53
#define EV3_UART_TYPE_MIN           29      // EV3 color sensor
54
#define EV3_UART_TYPE_MAX           101
55
#define EV3_UART_SPEED_MIN          2400
56
#define EV3_UART_SPEED_LPF2         115200  // standard baud rate for Powered Up
57
#define EV3_UART_SPEED_MAX          460800  // in practice 115200 is max
58

59
#define EV3_UART_DATA_KEEP_ALIVE_TIMEOUT    100 /* msec */
60
#define EV3_UART_IO_TIMEOUT                 250 /* msec */
61

62
enum ev3_uart_info_bit {
63
    EV3_UART_INFO_BIT_CMD_TYPE,
64
    EV3_UART_INFO_BIT_CMD_MODES,
65
    EV3_UART_INFO_BIT_CMD_SPEED,
66
    EV3_UART_INFO_BIT_CMD_VERSION,
67
    EV3_UART_INFO_BIT_INFO_NAME,
68
    EV3_UART_INFO_BIT_INFO_RAW,
69
    EV3_UART_INFO_BIT_INFO_PCT,
70
    EV3_UART_INFO_BIT_INFO_SI,
71
    EV3_UART_INFO_BIT_INFO_UNITS,
72
    EV3_UART_INFO_BIT_INFO_MAPPING,
73
    EV3_UART_INFO_BIT_INFO_MODE_COMBOS,
74
    EV3_UART_INFO_BIT_INFO_UNK7,
75
    EV3_UART_INFO_BIT_INFO_UNK8,
76
    EV3_UART_INFO_BIT_INFO_UNK9,
77
    EV3_UART_INFO_BIT_INFO_FORMAT,
78
    EV3_UART_INFO_BIT_INFO_UNK11,
79
};
80

81
enum ev3_uart_info_flags {
82
    EV3_UART_INFO_FLAG_CMD_TYPE                 = 1 << EV3_UART_INFO_BIT_CMD_TYPE,
83
    EV3_UART_INFO_FLAG_CMD_MODES                = 1 << EV3_UART_INFO_BIT_CMD_MODES,
84
    EV3_UART_INFO_FLAG_CMD_SPEED                = 1 << EV3_UART_INFO_BIT_CMD_SPEED,
85
    EV3_UART_INFO_FLAG_CMD_VERSION              = 1 << EV3_UART_INFO_BIT_CMD_VERSION,
86
    EV3_UART_INFO_FLAG_INFO_NAME                = 1 << EV3_UART_INFO_BIT_INFO_NAME,
87
    EV3_UART_INFO_FLAG_INFO_RAW                 = 1 << EV3_UART_INFO_BIT_INFO_RAW,
88
    EV3_UART_INFO_FLAG_INFO_PCT                 = 1 << EV3_UART_INFO_BIT_INFO_PCT,
89
    EV3_UART_INFO_FLAG_INFO_SI                  = 1 << EV3_UART_INFO_BIT_INFO_SI,
90
    EV3_UART_INFO_FLAG_INFO_UNITS               = 1 << EV3_UART_INFO_BIT_INFO_UNITS,
91
    EV3_UART_INFO_FLAG_INFO_MAPPING             = 1 << EV3_UART_INFO_BIT_INFO_MAPPING,
92
    EV3_UART_INFO_FLAG_INFO_MODE_COMBOS         = 1 << EV3_UART_INFO_BIT_INFO_MODE_COMBOS,
93
    EV3_UART_INFO_FLAG_INFO_UNK7                = 1 << EV3_UART_INFO_BIT_INFO_UNK7,
94
    EV3_UART_INFO_FLAG_INFO_UNK8                = 1 << EV3_UART_INFO_BIT_INFO_UNK8,
95
    EV3_UART_INFO_FLAG_INFO_UNK9                = 1 << EV3_UART_INFO_BIT_INFO_UNK9,
96
    EV3_UART_INFO_FLAG_INFO_FORMAT              = 1 << EV3_UART_INFO_BIT_INFO_FORMAT,
97

98
    EV3_UART_INFO_FLAG_ALL_INFO     = EV3_UART_INFO_FLAG_INFO_NAME
99
        | EV3_UART_INFO_FLAG_INFO_RAW
100
        | EV3_UART_INFO_FLAG_INFO_PCT
101
        | EV3_UART_INFO_FLAG_INFO_SI
102
        | EV3_UART_INFO_FLAG_INFO_UNITS
103
        | EV3_UART_INFO_FLAG_INFO_MAPPING
104
        | EV3_UART_INFO_FLAG_INFO_MODE_COMBOS
105
        | EV3_UART_INFO_FLAG_INFO_UNK7
106
        | EV3_UART_INFO_FLAG_INFO_UNK8
107
        | EV3_UART_INFO_FLAG_INFO_UNK9
108
        | EV3_UART_INFO_FLAG_INFO_FORMAT,
109
    EV3_UART_INFO_FLAG_REQUIRED     = EV3_UART_INFO_FLAG_CMD_TYPE
110
        | EV3_UART_INFO_FLAG_CMD_MODES
111
        | EV3_UART_INFO_FLAG_INFO_NAME
112
        | EV3_UART_INFO_FLAG_INFO_FORMAT,
113
};
114

115
/**
116
 * Indicates the current state of the UART device.
117
 */
118
typedef enum {
119
    /** Something bad happened. */
120
    PBIO_UARTDEV_STATUS_ERR,
121
    /** Waiting for port to be placed in UART mode with device attached. */
122
    PBIO_UARTDEV_STATUS_WAITING,
123
    /**< Waiting for data that looks like LEGO UART protocol. */
124
    PBIO_UARTDEV_STATUS_SYNCING,
125
    /**< Reading device info before changing baud rate. */
126
    PBIO_UARTDEV_STATUS_INFO,
127
    /**< ACK received, delay changing baud rate. */
128
    PBIO_UARTDEV_STATUS_ACK,
129
    /**< Ready to send commands and receive data. */
130
    PBIO_UARTDEV_STATUS_DATA,
131
} pbio_uartdev_status_t;
132

133
/**
134
 * struct ev3_uart_port_data - Data for EV3/LPF2 UART Sensor communication
135
 * @iodev: The I/O device state information struct
136
 * @pt: Protothread for main communication protocol
137
 * @data_pt: Protothread for receiving sensor data
138
 * @speed_pt: Protothread for setting the baud rate
139
 * @timer: Timer for sending keepalive messages and other delays.
140
 * @uart: Pointer to the UART device to use for communications
141
 * @info: The I/O device information struct for the connected device
142
 * @status: The current device connection state
143
 * @type_id: The type ID received
144
 * @new_mode: The mode requested by set_mode. Also used to keep track of mode
145
 *  in INFO messages while syncing.
146
 * @new_baud_rate: New baud rate that will be set with ev3_uart_change_bitrate
147
 * @info_flags: Flags indicating what information has already been read
148
 *      from the data.
149
 * @abs_pos: The absolute position received from an LPF2 motor
150
 * @tx_msg: Buffer to hold messages transmitted to the device
151
 * @rx_msg: Buffer to hold messages received from the device
152
 * @rx_msg_size: Size of the current message being received
153
 * @ext_mode: Extra mode adder for Powered Up devices (for modes > LUMP_MAX_MODE)
154
 * @write_cmd_size: The size parameter received from a WRITE command
155
 * @last_err: data->msg to be printed in case of an error.
156
 * @err_count: Total number of errors that have occurred
157
 * @num_data_err: Number of bad reads when receiving DATA data->msgs.
158
 * @mode_switch_time: Time of most recent successful mode switch, used to discard stale data.
159
 * @data_rec: Flag that indicates that good DATA data->msg has been received
160
 *      since last watchdog timeout.
161
 * @tx_busy: mutex that protects tx_msg
162
 * @tx_start_time: Time of most recently started transmission.
163
 * @tx_type: The data type of the current or last transmission.
164
 * @data_set_len: Length of data to be set, data is stored in bin_data.
165
 * @speed_payload: Buffer for holding baud rate change message data
166
 */
167
typedef struct {
168
    pbio_iodev_t iodev;
169
    struct pt pt;
170
    struct pt data_pt;
171
    struct pt speed_pt;
172
    struct etimer timer;
173
    pbdrv_uart_dev_t *uart;
174
    pbio_iodev_info_t *info;
175
    pbdrv_motor_driver_dev_t *motor_driver;
176
    pbio_uartdev_status_t status;
177
    pbio_iodev_type_id_t type_id;
178
    uint8_t new_mode;
179
    uint32_t new_baud_rate;
180
    uint32_t info_flags;
181
    uint8_t *tx_msg;
182
    uint8_t *rx_msg;
183
    uint8_t rx_msg_size;
184
    uint8_t ext_mode;
185
    uint8_t write_cmd_size;
186
    DBG_ERR(const char *last_err);
187
    uint32_t err_count;
188
    uint32_t num_data_err;
189
    uint32_t mode_switch_time;
190
    bool data_rec;
191
    bool tx_busy;
192
    uint8_t data_set_len;
193
    uint32_t tx_start_time;
194
    lump_msg_type_t tx_type;
195
    uint8_t speed_payload[4];
196
} uartdev_port_data_t;
197

198
enum {
199
    BUF_TX_MSG,
200
    BUF_RX_MSG,
201
    NUM_BUF
202
};
203

204
PROCESS(pbio_uartdev_process, "UART device");
205

206
static struct {
207
    pbio_iodev_info_t info;
208
    pbio_iodev_mode_t modes[PBIO_IODEV_MAX_NUM_MODES];
209
} infos[PBIO_CONFIG_UARTDEV_NUM_DEV];
210

211
static uint8_t bufs[PBIO_CONFIG_UARTDEV_NUM_DEV][NUM_BUF][EV3_UART_MAX_MESSAGE_SIZE];
212

213
static uartdev_port_data_t dev_data[PBIO_CONFIG_UARTDEV_NUM_DEV];
214

215
#define PBIO_PT_WAIT_READY(pt, expr) PT_WAIT_UNTIL((pt), (expr) != PBIO_ERROR_AGAIN)
216

217
pbio_error_t pbio_uartdev_get(uint8_t id, pbio_iodev_t **iodev) {
4✔
218
    if (id >= PBIO_CONFIG_UARTDEV_NUM_DEV) {
4✔
219
        return PBIO_ERROR_INVALID_ARG;
×
220
    }
221

222
    *iodev = &dev_data[id].iodev;
4✔
223

224
    if (!(*iodev)->info) {
4✔
225
        // device has not been initialized yet
226
        return PBIO_ERROR_AGAIN;
×
227
    }
228

229
    return PBIO_SUCCESS;
4✔
230
}
231

232
/**
233
 * Indicates to the uartdev driver that the port has been placed in uart mode
234
 * (i.e. pin mux) and is ready to start syncing with the attached I/O device.
235
 * @param [in] id The uartdev device id.
236
 */
237
void pbio_uartdev_ready(uint8_t id) {
4✔
238
    if (id >= PBIO_CONFIG_UARTDEV_NUM_DEV) {
4✔
239
        return;
×
240
    }
241

242
    uartdev_port_data_t *data = &dev_data[id];
4✔
243

244
    // REVISIT: For now we are assuming that this function is never called
245
    // at the wrong time. If this assumption turns out to be false, we will
246
    // need to return PBIO_ERROR_AGAIN and modify callers to retry.
247
    if (data->status != PBIO_UARTDEV_STATUS_WAITING) {
4✔
248
        return;
×
249
    }
250

251
    // notify pbio_uartdev_update() that there should be a device ready to
252
    // communicate with now
253
    data->status = PBIO_UARTDEV_STATUS_SYNCING;
4✔
254
    process_poll(&pbio_uartdev_process);
4✔
255
}
256

257
static inline bool test_and_set_bit(uint8_t bit, uint32_t *flags) {
62✔
258
    bool result = *flags & (1 << bit);
62✔
259
    *flags |= (1 << bit);
62✔
260
    return result;
62✔
261
}
262

263
static uint8_t ev3_uart_get_msg_size(uint8_t header) {
544✔
264
    uint8_t size;
265

266
    if ((header & LUMP_MSG_TYPE_MASK) == LUMP_MSG_TYPE_SYS) {
544✔
267
        return 1;
8✔
268
    }
269

270
    size = LUMP_MSG_SIZE(header);
536✔
271
    size += 2; /* header and checksum */
536✔
272
    if ((header & LUMP_MSG_TYPE_MASK) == LUMP_MSG_TYPE_INFO) {
536✔
273
        size++; /* extra command byte */
406✔
274

275
    }
276
    return size;
536✔
277
}
278

279

280
static void pbio_uartdev_parse_msg(uartdev_port_data_t *data) {
272✔
281
    uint32_t speed;
282
    uint8_t msg_type, cmd, msg_size, mode, cmd2;
283

284
    msg_type = data->rx_msg[0] & LUMP_MSG_TYPE_MASK;
272✔
285
    cmd = data->rx_msg[0] & LUMP_MSG_CMD_MASK;
272✔
286
    msg_size = ev3_uart_get_msg_size(data->rx_msg[0]);
272✔
287
    mode = cmd;
272✔
288
    cmd2 = data->rx_msg[1];
272✔
289

290
    // The original EV3 spec only allowed for up to 8 modes (3-bit number).
291
    // The Powered UP spec extents this by adding an extra flag to INFO commands.
292
    // Not sure that LUMP_INFO_MODE_PLUS_8 is used in practice, but rather
293
    // an extra (separate) LUMP_CMD_EXT_MODE message seems to be used instead
294
    if (msg_type == LUMP_MSG_TYPE_INFO && (cmd2 & LUMP_INFO_MODE_PLUS_8)) {
272✔
295
        mode += 8;
21✔
296
        cmd2 &= ~LUMP_INFO_MODE_PLUS_8;
21✔
297
    } else {
298
        mode += data->ext_mode;
251✔
299
    }
300

301
    if (msg_size > 1) {
272✔
302
        uint8_t checksum = 0xFF;
268✔
303
        for (int i = 0; i < msg_size - 1; i++) {
2,285✔
304
            checksum ^= data->rx_msg[i];
2,017✔
305
        }
306
        if (checksum != data->rx_msg[msg_size - 1]) {
268✔
307
            DBG_ERR(data->last_err = "Bad checksum");
308
            // if INFO messages are done and we are now receiving data, it is
309
            // OK to occasionally have a bad checksum
310
            if (data->status == PBIO_UARTDEV_STATUS_DATA) {
×
311

312
                // The LEGO EV3 color sensor sends bad checksums
313
                // for RGB-RAW data (mode 4). The check here could be
314
                // improved if someone can find a pattern.
315
                if (data->type_id != PBIO_IODEV_TYPE_ID_EV3_COLOR_SENSOR
×
316
                    || data->rx_msg[0] != (LUMP_MSG_TYPE_DATA | LUMP_MSG_SIZE_8 | 4)) {
×
317
                    return;
×
318
                }
319
            } else {
320
                goto err;
×
321
            }
322
        }
323
    }
324

325
    switch (msg_type) {
272✔
326
        case LUMP_MSG_TYPE_SYS:
4✔
327
            switch (cmd) {
4✔
328
                case LUMP_SYS_SYNC:
×
329
                    /* IR sensor (type 33) sends checksum after SYNC */
330
                    if (msg_size > 1 && (cmd ^ cmd2) == 0xFF) {
×
331
                        msg_size++;
×
332
                    }
333
                    break;
×
334
                case LUMP_SYS_ACK:
4✔
335
                    if (!data->info->num_modes) {
4✔
336
                        DBG_ERR(data->last_err = "Received ACK before all mode INFO");
337
                        goto err;
×
338
                    }
339
                    if ((data->info_flags & EV3_UART_INFO_FLAG_REQUIRED) != EV3_UART_INFO_FLAG_REQUIRED) {
4✔
340
                        DBG_ERR(data->last_err = "Did not receive all required INFO");
341
                        goto err;
×
342
                    }
343

344
                    data->status = PBIO_UARTDEV_STATUS_ACK;
4✔
345
                    data->iodev.mode = data->new_mode;
4✔
346

347
                    return;
4✔
348
            }
349
            break;
×
350
        case LUMP_MSG_TYPE_CMD:
23✔
351
            switch (cmd) {
23✔
352
                case LUMP_CMD_MODES:
4✔
353
                    if (test_and_set_bit(EV3_UART_INFO_BIT_CMD_MODES, &data->info_flags)) {
4✔
354
                        DBG_ERR(data->last_err = "Received duplicate modes INFO");
355
                        goto err;
×
356
                    }
357
                    if (cmd2 > LUMP_MAX_MODE) {
4✔
358
                        DBG_ERR(data->last_err = "Number of modes is out of range");
359
                        goto err;
×
360
                    }
361
                    data->info->num_modes = cmd2 + 1;
4✔
362
                    if (msg_size > 5) {
4✔
363
                        // Powered Up devices can have an extended mode message that
364
                        // includes modes > LUMP_MAX_MODE
365
                        data->info->num_modes = data->rx_msg[3] + 1;
1✔
366
                    }
367

368
                    debug_pr("num_modes: %d\n", data->info->num_modes);
369

370
                    break;
4✔
371
                case LUMP_CMD_SPEED:
4✔
372
                    if (test_and_set_bit(EV3_UART_INFO_BIT_CMD_SPEED, &data->info_flags)) {
4✔
373
                        DBG_ERR(data->last_err = "Received duplicate speed INFO");
374
                        goto err;
×
375
                    }
376
                    speed = pbio_get_uint32_le(data->rx_msg + 1);
4✔
377
                    if (speed < EV3_UART_SPEED_MIN || speed > EV3_UART_SPEED_MAX) {
4✔
378
                        DBG_ERR(data->last_err = "Speed is out of range");
379
                        goto err;
×
380
                    }
381
                    data->new_baud_rate = speed;
4✔
382

383
                    debug_pr("speed: %" PRIu32 "\n", speed);
384

385
                    break;
4✔
386
                case LUMP_CMD_WRITE:
×
387
                    if (cmd2 & 0x20) {
×
388
                        data->write_cmd_size = cmd2 & 0x3;
×
389
                        if (PBIO_IODEV_IS_FEEDBACK_MOTOR(&data->iodev)) {
×
390
                            // TODO: msg[3] and msg[4] probably give us useful information
391
                        } else {
392
                            // TODO: handle other write commands
393
                        }
394
                    }
395
                    break;
×
396
                case LUMP_CMD_EXT_MODE:
11✔
397
                    // Powered up devices can have modes > LUMP_MAX_MODE. This
398
                    // command precedes other commands to add the extra 8 to the mode
399
                    data->ext_mode = data->rx_msg[1];
11✔
400
                    break;
11✔
401
                case LUMP_CMD_VERSION:
4✔
402
                    #if UARTDEV_ENABLE_EXTRAS
403
                    if (test_and_set_bit(EV3_UART_INFO_BIT_CMD_VERSION, &data->info_flags)) {
404
                        DBG_ERR(data->last_err = "Received duplicate version INFO");
405
                        goto err;
406
                    }
407
                    // TODO: this might be useful someday
408
                    debug_pr("fw version: %08" PRIx32 "\n", pbio_get_uint32_le(data->rx_msg + 1));
409
                    debug_pr("hw version: %08" PRIx32 "\n", pbio_get_uint32_le(data->rx_msg + 5));
410
                    #endif // LUMP_CMD_VERSION
411
                    break;
4✔
412
                default:
×
413
                    DBG_ERR(data->last_err = "Unknown command");
414
                    goto err;
×
415
            }
416
            break;
23✔
417
        case LUMP_MSG_TYPE_INFO:
203✔
418
            switch (cmd2) {
203✔
419
                case LUMP_INFO_NAME: {
27✔
420
                    data->info_flags &= ~EV3_UART_INFO_FLAG_ALL_INFO;
27✔
421
                    if (data->rx_msg[2] < 'A' || data->rx_msg[2] > 'z') {
27✔
422
                        DBG_ERR(data->last_err = "Invalid name INFO");
423
                        goto err;
×
424
                    }
425
                    /*
426
                    * Name may not have null terminator and we
427
                    * are done with the checksum at this point
428
                    * so we are writing 0 over the checksum to
429
                    * ensure a null terminator for the string
430
                    * functions.
431
                    */
432
                    data->rx_msg[msg_size - 1] = 0;
27✔
433
                    size_t name_len = strlen((char *)(data->rx_msg + 2));
27✔
434
                    if (name_len > LUMP_MAX_NAME_SIZE) {
27✔
435
                        DBG_ERR(data->last_err = "Name is too long");
436
                        goto err;
×
437
                    }
438
                    data->new_mode = mode;
27✔
439
                    data->info_flags |= EV3_UART_INFO_FLAG_INFO_NAME;
27✔
440

441
                    // newer LEGO UART devices send additional 6 mode capability flags
442
                    uint8_t flags = 0;
27✔
443
                    if (name_len <= LUMP_MAX_SHORT_NAME_SIZE && msg_size > LUMP_MAX_NAME_SIZE) {
27✔
444
                        // Only the first is used in practice.
445
                        flags = data->rx_msg[8];
12✔
446
                    } else {
447
                        // for newer devices that don't send it, set flags by device ID
448
                        // TODO: Look up from static info like we do for basic devices
449
                        if (data->type_id == PBIO_IODEV_TYPE_ID_INTERACTIVE_MOTOR) {
15✔
450
                            flags = LUMP_MODE_FLAGS0_MOTOR | LUMP_MODE_FLAGS0_MOTOR_POWER | LUMP_MODE_FLAGS0_MOTOR_SPEED | LUMP_MODE_FLAGS0_MOTOR_REL_POS;
4✔
451
                        }
452
                    }
453

454
                    // Although capabilities are sent per mode, we apply them to the whole device
455
                    if (flags & LUMP_MODE_FLAGS0_MOTOR_POWER) {
27✔
456
                        data->info->capability_flags |= PBIO_IODEV_CAPABILITY_FLAG_IS_DC_OUTPUT;
6✔
457
                    }
458
                    if (flags & LUMP_MODE_FLAGS0_MOTOR_SPEED) {
27✔
459
                        data->info->capability_flags |= PBIO_IODEV_CAPABILITY_FLAG_HAS_MOTOR_SPEED;
6✔
460
                    }
461
                    if (flags & LUMP_MODE_FLAGS0_MOTOR_REL_POS) {
27✔
462
                        data->info->capability_flags |= PBIO_IODEV_CAPABILITY_FLAG_HAS_MOTOR_REL_POS;
6✔
463
                    }
464
                    if (flags & LUMP_MODE_FLAGS0_MOTOR_ABS_POS) {
27✔
465
                        data->info->capability_flags |= PBIO_IODEV_CAPABILITY_FLAG_HAS_MOTOR_ABS_POS;
4✔
466
                    }
467
                    if (flags & LUMP_MODE_FLAGS0_NEEDS_SUPPLY_PIN1) {
27✔
468
                        data->info->capability_flags |= PBIO_IODEV_CAPABILITY_FLAG_NEEDS_SUPPLY_PIN1;
×
469
                    }
470
                    if (flags & LUMP_MODE_FLAGS0_NEEDS_SUPPLY_PIN2) {
27✔
471
                        data->info->capability_flags |= PBIO_IODEV_CAPABILITY_FLAG_NEEDS_SUPPLY_PIN2;
×
472
                    }
473

474
                    debug_pr("new_mode: %d\n", data->new_mode);
475
                    debug_pr("flags: %02X %02X %02X %02X %02X %02X\n",
476
                        data->rx_msg[8 + 0], data->rx_msg[8 + 1], data->rx_msg[8 + 2],
477
                        data->rx_msg[8 + 3], data->rx_msg[8 + 4], data->rx_msg[8 + 5]);
478
                }
479
                break;
27✔
480
                #if UARTDEV_ENABLE_EXTRAS
481
                case LUMP_INFO_RAW:
482
                    break;
483
                case LUMP_INFO_PCT:
484
                    break;
485
                case LUMP_INFO_SI:
486
                    break;
487
                case LUMP_INFO_UNITS:
488
                    break;
489
                #endif // UARTDEV_ENABLE_EXTRAS
490
                case LUMP_INFO_MAPPING:
27✔
491
                    if (data->new_mode != mode) {
27✔
492
                        DBG_ERR(data->last_err = "Received INFO for incorrect mode");
493
                        goto err;
×
494
                    }
495
                    if (test_and_set_bit(EV3_UART_INFO_BIT_INFO_MAPPING, &data->info_flags)) {
27✔
496
                        DBG_ERR(data->last_err = "Received duplicate mapping INFO");
497
                        goto err;
×
498
                    }
499

500
                    // Mode supports writing if rx_msg[3] is nonzero. Store in 3rd bit of data type.
501
                    data->info->mode_info[mode].data_type = (data->rx_msg[3] != 0) << 2;
27✔
502

503
                    debug_pr("mapping: in %02x out %02x\n", data->rx_msg[2], data->rx_msg[3]);
504
                    debug_pr("mapping: in %02x out %02x\n", data->rx_msg[2], data->rx_msg[3]);
505
                    debug_pr("Writable: %d\n", data->info->mode_info[mode].data_type & PBIO_IODEV_DATA_TYPE_WRITABLE);
506

507
                    break;
27✔
508
                #if UARTDEV_ENABLE_EXTRAS
509
                case LUMP_INFO_MODE_COMBOS:
510
                    if (data->new_mode != mode) {
511
                        DBG_ERR(data->last_err = "Received INFO for incorrect mode");
512
                        goto err;
513
                    }
514
                    if (test_and_set_bit(EV3_UART_INFO_BIT_INFO_MODE_COMBOS, &data->info_flags)) {
515
                        DBG_ERR(data->last_err = "Received duplicate mode combos INFO");
516
                        goto err;
517
                    }
518

519
                    // REVISIT: this is potentially an array of combos
520
                    debug_pr("mode combos: %04x\n", data->rx_msg[3] << 8 | data->rx_msg[2]);
521

522
                    break;
523
                case LUMP_INFO_UNK9:
524
                    if (data->new_mode != mode) {
525
                        DBG_ERR(data->last_err = "Received INFO for incorrect mode");
526
                        goto err;
527
                    }
528
                    if (test_and_set_bit(EV3_UART_INFO_BIT_INFO_UNK9, &data->info_flags)) {
529
                        DBG_ERR(data->last_err = "Received duplicate UNK9 INFO");
530
                        goto err;
531
                    }
532

533
                    // first 3 parameters look like PID constants, 4th is max tacho_rate
534
                    debug_pr("motor parameters: %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 "\n",
535
                        pbio_get_uint32_le(data->rx_msg + 2), pbio_get_uint32_le(data->rx_msg + 6),
536
                        pbio_get_uint32_le(data->rx_msg + 10), pbio_get_uint32_le(data->rx_msg + 14));
537

538
                    break;
539
                case LUMP_INFO_UNK11:
540
                    if (data->new_mode != mode) {
541
                        DBG_ERR(data->last_err = "Received INFO for incorrect mode");
542
                        goto err;
543
                    }
544
                    if (test_and_set_bit(EV3_UART_INFO_BIT_INFO_UNK11, &data->info_flags)) {
545
                        DBG_ERR(data->last_err = "Received duplicate UNK11 INFO");
546
                        goto err;
547
                    }
548
                    break;
549
                #endif // UARTDEV_ENABLE_EXTRAS
550
                case LUMP_INFO_FORMAT:
27✔
551
                    if (data->new_mode != mode) {
27✔
552
                        DBG_ERR(data->last_err = "Received INFO for incorrect mode");
553
                        goto err;
×
554
                    }
555
                    if (test_and_set_bit(EV3_UART_INFO_BIT_INFO_FORMAT, &data->info_flags)) {
27✔
556
                        DBG_ERR(data->last_err = "Received duplicate format INFO");
557
                        goto err;
×
558
                    }
559
                    data->info->mode_info[mode].num_values = data->rx_msg[2];
27✔
560
                    if (!data->info->mode_info[mode].num_values) {
27✔
561
                        DBG_ERR(data->last_err = "Invalid number of data sets");
562
                        goto err;
×
563
                    }
564
                    if (msg_size < 7) {
27✔
565
                        DBG_ERR(data->last_err = "Invalid format data->msg size");
566
                        goto err;
×
567
                    }
568
                    if ((data->info_flags & EV3_UART_INFO_FLAG_REQUIRED) != EV3_UART_INFO_FLAG_REQUIRED) {
27✔
569
                        DBG_ERR(data->last_err = "Did not receive all required INFO");
570
                        goto err;
×
571
                    }
572
                    // Mode writability has been set previously. Now OR with mode type.
573
                    data->info->mode_info[mode].data_type |= data->rx_msg[3];
27✔
574
                    if (data->new_mode) {
27✔
575
                        data->new_mode--;
23✔
576
                    }
577

578
                    debug_pr("num_values: %d\n", data->info->mode_info[mode].num_values);
579
                    debug_pr("data_type: %d\n", data->info->mode_info[mode].data_type & PBIO_IODEV_DATA_TYPE_MASK);
580

581
                    break;
27✔
582
            }
583
            break;
203✔
584
        case LUMP_MSG_TYPE_DATA:
42✔
585
            if (data->status != PBIO_UARTDEV_STATUS_DATA) {
42✔
586
                DBG_ERR(data->last_err = "Received DATA before INFO was complete");
587
                goto err;
×
588
            }
589

590
            if (mode >= data->info->num_modes) {
42✔
591
                DBG_ERR(data->last_err = "Invalid mode received");
592
                goto err;
×
593
            }
594

595
            // Data is for requested mode.
596
            if (mode == data->new_mode) {
42✔
597
                if (!data->data_set_len) {
41✔
598
                    // Copy the new data unless buffer contains data for sending.
599
                    memcpy(data->iodev.bin_data, data->rx_msg + 1, msg_size - 2);
41✔
600
                }
601

602
                if (data->iodev.mode != mode) {
41✔
603
                    // First time getting data in this mode, so register time.
604
                    data->mode_switch_time = pbdrv_clock_get_ms();
2✔
605
                }
606
            }
607
            data->iodev.mode = mode;
42✔
608

609
            // setting type_id in info struct lets external modules know a device is connected and receiving good data
610
            data->info->type_id = data->type_id;
42✔
611

612
            data->data_rec = true;
42✔
613
            if (data->num_data_err) {
42✔
614
                data->num_data_err--;
4✔
615
            }
616
            break;
42✔
617
    }
618

619
    return;
268✔
620

621
err:
×
622
    // FIXME: Setting status to ERR here does not allow recovering from bad
623
    // message when receiving data. Maybe return error instead?
624
    data->status = PBIO_UARTDEV_STATUS_ERR;
×
625
}
626

627
static uint8_t ev3_uart_set_msg_hdr(lump_msg_type_t type, lump_msg_size_t size, lump_cmd_t cmd) {
6✔
628
    return (type & LUMP_MSG_TYPE_MASK) | (size & LUMP_MSG_SIZE_MASK) | (cmd & LUMP_MSG_CMD_MASK);
6✔
629
}
630

631
static pbio_error_t ev3_uart_begin_tx_msg(uartdev_port_data_t *port_data, lump_msg_type_t msg_type,
6✔
632
    lump_cmd_t cmd, const uint8_t *data, uint8_t len) {
633
    uint8_t header, checksum, i;
634
    uint8_t offset = 0;
6✔
635
    lump_msg_size_t size;
636
    pbio_error_t err;
637

638
    if (len == 0 || len > 32) {
6✔
639
        return PBIO_ERROR_INVALID_ARG;
×
640
    }
641

642
    if (port_data->tx_busy) {
6✔
643
        return PBIO_ERROR_AGAIN;
×
644
    }
645

646
    port_data->tx_busy = true;
6✔
647
    port_data->tx_type = msg_type;
6✔
648
    port_data->tx_start_time = pbdrv_clock_get_ms();
6✔
649

650
    if (msg_type == LUMP_MSG_TYPE_DATA) {
6✔
651
        // Only Powered Up devices support setting data, and they expect to have an
652
        // extra command sent to give the part of the mode > 7
653
        port_data->tx_msg[0] = ev3_uart_set_msg_hdr(LUMP_MSG_TYPE_CMD, LUMP_MSG_SIZE_1, LUMP_CMD_EXT_MODE);
×
654
        port_data->tx_msg[1] = port_data->iodev.mode > LUMP_MAX_MODE ? 8 : 0;
×
655
        port_data->tx_msg[2] = 0xff ^ port_data->tx_msg[0] ^ port_data->tx_msg[1];
×
656
        offset = 3;
×
657
    }
658

659
    checksum = 0xff;
6✔
660
    for (i = 0; i < len; i++) {
24✔
661
        port_data->tx_msg[offset + i + 1] = data[i];
18✔
662
        checksum ^= data[i];
18✔
663
    }
664

665
    // can't send arbitrary number of bytes, so find nearest match
666
    if (i == 1) {
6✔
667
        size = LUMP_MSG_SIZE_1;
2✔
668
    } else if (i == 2) {
4✔
669
        size = LUMP_MSG_SIZE_2;
×
670
    } else if (i <= 4) {
4✔
671
        size = LUMP_MSG_SIZE_4;
4✔
672
        len = 4;
4✔
673
    } else if (i <= 8) {
×
674
        size = LUMP_MSG_SIZE_8;
×
675
        len = 8;
×
676
    } else if (i <= 16) {
×
677
        size = LUMP_MSG_SIZE_16;
×
678
        len = 16;
×
679
    } else if (i <= 32) {
×
680
        size = LUMP_MSG_SIZE_32;
×
681
        len = 32;
×
682
    }
683

684
    // pad with zeros
685
    for (; i < len; i++) {
6✔
686
        port_data->tx_msg[offset + i + 1] = 0;
×
687
    }
688

689
    header = ev3_uart_set_msg_hdr(msg_type, size, cmd);
6✔
690
    checksum ^= header;
6✔
691

692
    port_data->tx_msg[offset] = header;
6✔
693
    port_data->tx_msg[offset + i + 1] = checksum;
6✔
694

695
    err = pbdrv_uart_write_begin(port_data->uart, port_data->tx_msg, offset + i + 2, EV3_UART_IO_TIMEOUT);
6✔
696
    if (err != PBIO_SUCCESS) {
6✔
697
        port_data->tx_busy = false;
×
698
    }
699

700
    return err;
6✔
701
}
702

703
static PT_THREAD(pbio_uartdev_send_speed_msg(uartdev_port_data_t * data, uint32_t speed)) {
8✔
704
    pbio_error_t err;
705

706
    PT_BEGIN(&data->speed_pt);
8✔
707

708
    PT_WAIT_WHILE(&data->speed_pt, data->tx_busy);
4✔
709

710
    pbio_set_uint32_le(&data->speed_payload[0], speed);
4✔
711
    PBIO_PT_WAIT_READY(&data->speed_pt, err = ev3_uart_begin_tx_msg(data,
4✔
712
        LUMP_MSG_TYPE_CMD, LUMP_CMD_SPEED,
713
        data->speed_payload, PBIO_ARRAY_SIZE(data->speed_payload)));
714
    if (err != PBIO_SUCCESS) {
4✔
715
        DBG_ERR(data->last_err = "SPEED tx begin failed");
716
        goto err;
×
717
    }
718

719
    PBIO_PT_WAIT_READY(&data->speed_pt, err = pbdrv_uart_write_end(data->uart));
8✔
720
    if (err != PBIO_SUCCESS) {
4✔
721
        data->tx_busy = false;
×
722
        DBG_ERR(data->last_err = "SPEED tx end failed");
723
        goto err;
×
724
    }
725

726
    data->tx_busy = false;
4✔
727

728
    PT_END(&data->speed_pt);
4✔
729

730
err:
×
731
    PT_EXIT(&data->speed_pt);
×
732
}
733

734
static PT_THREAD(pbio_uartdev_update(uartdev_port_data_t * data)) {
617✔
735
    pbio_error_t err;
736
    uint8_t checksum;
737

738
    PT_BEGIN(&data->pt);
617✔
739

740
    // FIXME: The pin1/pin2 power control should be implemented via a callback
741
    // to the port that the I/O device is attached to instead of poking the
742
    // motor driver directly. The current implementation is only valid on
743
    // Powered Up platforms and it assumes that motor driver id corresponds to
744
    // the port.
745

746
    #ifdef PBDRV_CONFIG_FIRST_MOTOR_PORT
747
    if (pbdrv_motor_driver_get_dev(data->iodev.port - PBDRV_CONFIG_FIRST_MOTOR_PORT, &data->motor_driver) != PBIO_SUCCESS) {
4✔
748
        data->motor_driver = NULL;
×
749
    }
750
    #endif
751

752
    // reset state for new device
753
    data->info->type_id = PBIO_IODEV_TYPE_ID_NONE;
4✔
754
    data->info->capability_flags = PBIO_IODEV_CAPABILITY_FLAG_NONE;
4✔
755
    data->ext_mode = 0;
4✔
756
    data->data_set_len = 0;
4✔
757
    data->status = PBIO_UARTDEV_STATUS_WAITING;
4✔
758

759
    // block until pbio_uartdev_ready() is called
760
    PT_WAIT_UNTIL(&data->pt, data->status == PBIO_UARTDEV_STATUS_SYNCING);
8✔
761

762
    pbdrv_uart_flush(data->uart);
4✔
763

764
    // Send SPEED command at 115200 baud
765
    PBIO_PT_WAIT_READY(&data->pt, pbdrv_uart_set_baud_rate(data->uart, EV3_UART_SPEED_LPF2));
4✔
766
    debug_pr("set baud: %d\n", EV3_UART_SPEED_LPF2);
767
    PT_SPAWN(&data->pt, &data->speed_pt, pbio_uartdev_send_speed_msg(data, EV3_UART_SPEED_LPF2));
8✔
768

769
    // read one byte to check for ACK
770
    PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_begin(data->uart, data->rx_msg, 1, 100));
4✔
771
    if (err != PBIO_SUCCESS) {
4✔
772
        DBG_ERR(data->last_err = "UART Rx error during baud");
773
        goto err;
×
774
    }
775

776
    PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_end(data->uart));
8✔
777
    if ((err == PBIO_SUCCESS && data->rx_msg[0] != LUMP_SYS_ACK) || err == PBIO_ERROR_TIMEDOUT) {
4✔
778
        // if we did not get ACK within 100ms, then switch to slow baud rate for sync
779
        PBIO_PT_WAIT_READY(&data->pt, pbdrv_uart_set_baud_rate(data->uart, EV3_UART_SPEED_MIN));
2✔
780
        debug_pr("set baud: %d\n", EV3_UART_SPEED_MIN);
781
    } else if (err != PBIO_SUCCESS) {
2✔
782
        DBG_ERR(data->last_err = "UART Rx error during baud");
783
        goto err;
×
784
    }
785

786
    // To get in sync with the data stream from the sensor, we look for a valid TYPE command.
787
    for (;;) {
788
        PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_begin(data->uart, data->rx_msg, 1, EV3_UART_IO_TIMEOUT));
4✔
789
        if (err != PBIO_SUCCESS) {
4✔
790
            DBG_ERR(data->last_err = "UART Rx error during sync");
791
            goto err;
×
792
        }
793
        PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_end(data->uart));
8✔
794
        if (err == PBIO_ERROR_TIMEDOUT) {
4✔
795
            continue;
×
796
        }
797
        if (err != PBIO_SUCCESS) {
4✔
798
            DBG_ERR(data->last_err = "UART Rx error during sync");
799
            goto err;
×
800
        }
801

802
        if (data->rx_msg[0] == (LUMP_MSG_TYPE_CMD | LUMP_CMD_TYPE)) {
4✔
803
            break;
4✔
804
        }
805
    }
806

807
    // then read the rest of the message
808

809
    PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_begin(data->uart, data->rx_msg + 1, 2, EV3_UART_IO_TIMEOUT));
4✔
810
    if (err != PBIO_SUCCESS) {
4✔
811
        DBG_ERR(data->last_err = "UART Rx error while reading type");
812
        goto err;
×
813
    }
814
    PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_end(data->uart));
8✔
815
    if (err != PBIO_SUCCESS) {
4✔
816
        DBG_ERR(data->last_err = "UART Rx error while reading type");
817
        goto err;
×
818
    }
819

820
    if (data->rx_msg[1] < EV3_UART_TYPE_MIN || data->rx_msg[1] > EV3_UART_TYPE_MAX) {
4✔
821
        DBG_ERR(data->last_err = "Bad device type id");
822
        goto err;
×
823
    }
824

825
    checksum = 0xff ^ data->rx_msg[0] ^ data->rx_msg[1];
4✔
826
    if (data->rx_msg[2] != checksum) {
4✔
827
        DBG_ERR(data->last_err = "Bad checksum for type id");
828
        goto err;
×
829
    }
830

831
    // if all was good, we are ready to start receiving the mode info
832

833
    data->info->num_modes = 1;
4✔
834

835
    data->type_id = data->rx_msg[1];
4✔
836
    data->info_flags = EV3_UART_INFO_FLAG_CMD_TYPE;
4✔
837
    data->data_rec = false;
4✔
838
    data->num_data_err = 0;
4✔
839
    data->status = PBIO_UARTDEV_STATUS_INFO;
4✔
840
    debug_pr("type id: %d\n", data->type_id);
841

842
    while (data->status == PBIO_UARTDEV_STATUS_INFO) {
223✔
843
        // read the message header
844
        PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_begin(data->uart, data->rx_msg, 1, EV3_UART_IO_TIMEOUT));
219✔
845
        if (err != PBIO_SUCCESS) {
219✔
846
            DBG_ERR(data->last_err = "UART Rx begin error during info header");
847
            goto err;
×
848
        }
849
        PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_end(data->uart));
438✔
850
        if (err != PBIO_SUCCESS) {
219✔
851
            DBG_ERR(data->last_err = "UART Rx end error during info header");
852
            goto err;
×
853
        }
854

855
        data->rx_msg_size = ev3_uart_get_msg_size(data->rx_msg[0]);
219✔
856
        if (data->rx_msg_size > EV3_UART_MAX_MESSAGE_SIZE) {
219✔
857
            DBG_ERR(data->last_err = "Bad message size during info");
858
            goto err;
×
859
        }
860

861
        // read the rest of the message
862
        if (data->rx_msg_size > 1) {
219✔
863
            PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_begin(data->uart, data->rx_msg + 1, data->rx_msg_size - 1, EV3_UART_IO_TIMEOUT));
215✔
864
            if (err != PBIO_SUCCESS) {
215✔
865
                DBG_ERR(data->last_err = "UART Rx begin error during info");
866
                goto err;
×
867
            }
868
            PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_end(data->uart));
431✔
869
            if (err != PBIO_SUCCESS) {
215✔
870
                DBG_ERR(data->last_err = "UART Rx end error during info");
871
                goto err;
×
872
            }
873
        }
874

875
        // at this point, we have a full data->msg that can be parsed
876
        pbio_uartdev_parse_msg(data);
219✔
877
    }
878

879
    // at this point we should have read all of the mode info
880
    if (data->status != PBIO_UARTDEV_STATUS_ACK) {
4✔
881
        // data->last_err should be set by pbio_uartdev_parse_msg()
882
        goto err;
×
883
    }
884

885
    // reply with ACK
886
    PT_WAIT_WHILE(&data->pt, data->tx_busy);
4✔
887
    data->tx_busy = true;
4✔
888
    data->tx_msg[0] = LUMP_SYS_ACK;
4✔
889
    PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_write_begin(data->uart, data->tx_msg, 1, EV3_UART_IO_TIMEOUT));
4✔
890
    if (err != PBIO_SUCCESS) {
4✔
891
        data->tx_busy = false;
×
892
        DBG_ERR(data->last_err = "UART Tx begin error during ack");
893
        goto err;
×
894
    }
895
    PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_write_end(data->uart));
8✔
896
    if (err != PBIO_SUCCESS) {
4✔
897
        data->tx_busy = false;
×
898
        DBG_ERR(data->last_err = "UART Tx end error during ack");
899
        goto err;
×
900
    }
901
    data->tx_busy = false;
4✔
902

903
    // schedule baud rate change
904
    etimer_set(&data->timer, 10);
4✔
905
    PT_WAIT_UNTIL(&data->pt, etimer_expired(&data->timer));
8✔
906

907
    // change the baud rate
908
    PBIO_PT_WAIT_READY(&data->pt, pbdrv_uart_set_baud_rate(data->uart, data->new_baud_rate));
4✔
909
    debug_pr("set baud: %" PRIu32 "\n", data->new_baud_rate);
910

911
    data->status = PBIO_UARTDEV_STATUS_DATA;
4✔
912
    // reset data rx thread
913
    PT_INIT(&data->data_pt);
4✔
914

915
    // Turn on power for devices that need it
916
    if (data->motor_driver) {
4✔
917
        if (data->info->capability_flags & PBIO_IODEV_CAPABILITY_FLAG_NEEDS_SUPPLY_PIN1) {
4✔
918
            pbdrv_motor_driver_set_duty_cycle(data->motor_driver, -PBDRV_MOTOR_DRIVER_MAX_DUTY);
×
919
        } else if (data->info->capability_flags & PBIO_IODEV_CAPABILITY_FLAG_NEEDS_SUPPLY_PIN2) {
4✔
920
            pbdrv_motor_driver_set_duty_cycle(data->motor_driver, PBDRV_MOTOR_DRIVER_MAX_DUTY);
×
921
        } else {
922
            pbdrv_motor_driver_coast(data->motor_driver);
4✔
923
        }
924
    }
925

926
    while (data->status == PBIO_UARTDEV_STATUS_DATA) {
44✔
927
        // setup keepalive timer
928
        etimer_reset_with_new_interval(&data->timer, EV3_UART_DATA_KEEP_ALIVE_TIMEOUT);
44✔
929
        PT_WAIT_UNTIL(&data->pt, etimer_expired(&data->timer));
154✔
930

931
        // make sure we are receiving data
932
        if (!data->data_rec) {
40✔
933
            data->num_data_err++;
4✔
934
            DBG_ERR(data->last_err = "No data since last keepalive");
935
            if (data->num_data_err > 6) {
4✔
936
                data->status = PBIO_UARTDEV_STATUS_ERR;
×
937
            }
938
        }
939
        data->data_rec = false;
40✔
940

941
        // send keepalive
942
        PT_WAIT_WHILE(&data->pt, data->tx_busy);
40✔
943
        data->tx_busy = true;
40✔
944
        data->tx_msg[0] = LUMP_SYS_NACK;
40✔
945
        PBIO_PT_WAIT_READY(&data->pt,
40✔
946
            err = pbdrv_uart_write_begin(data->uart, data->tx_msg, 1, EV3_UART_IO_TIMEOUT));
947
        if (err != PBIO_SUCCESS) {
40✔
948
            data->tx_busy = false;
×
949
            DBG_ERR(data->last_err = "UART Tx begin error during keepalive");
950
            goto err;
×
951
        }
952
        PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_write_end(data->uart));
80✔
953
        if (err != PBIO_SUCCESS) {
40✔
954
            data->tx_busy = false;
×
955
            DBG_ERR(data->last_err = "UART Tx end error during keepalive");
956
            goto err;
×
957
        }
958
        data->tx_busy = false;
40✔
959
    }
960

961
err:
×
962
    // reset and start over
963
    data->status = PBIO_UARTDEV_STATUS_ERR;
×
964
    etimer_stop(&data->timer);
×
965
    debug_pr("%s\n", data->last_err);
966
    data->err_count++;
×
967

968
    // Turn off battery supply to this port
969
    if (data->motor_driver) {
×
970
        pbdrv_motor_driver_coast(data->motor_driver);
×
971
    }
972

973
    process_post(PROCESS_BROADCAST, PROCESS_EVENT_SERVICE_REMOVED, &data->iodev);
×
974

975
    PT_END(&data->pt);
×
976
}
977

978
// REVISIT: This is not the greatest. We can easily get a buffer overrun and
979
// loose data. For now, the retry after bad message size helps get back into
980
// sync with the data stream.
981
static PT_THREAD(pbio_uartdev_receive_data(uartdev_port_data_t * data)) {
154✔
982
    pbio_error_t err;
983

984
    PT_BEGIN(&data->data_pt);
154✔
985

986
    while (true) {
53✔
987
        PBIO_PT_WAIT_READY(&data->data_pt,
57✔
988
            err = pbdrv_uart_read_begin(data->uart, data->rx_msg, 1, EV3_UART_IO_TIMEOUT));
989
        if (err != PBIO_SUCCESS) {
57✔
990
            DBG_ERR(data->last_err = "UART Rx data header begin error");
991
            break;
×
992
        }
993
        PBIO_PT_WAIT_READY(&data->data_pt, err = pbdrv_uart_read_end(data->uart));
154✔
994
        if (err != PBIO_SUCCESS) {
53✔
995
            DBG_ERR(data->last_err = "UART Rx data header end error");
996
            break;
×
997
        }
998

999
        data->rx_msg_size = ev3_uart_get_msg_size(data->rx_msg[0]);
53✔
1000
        if (data->rx_msg_size < 3 || data->rx_msg_size > EV3_UART_MAX_MESSAGE_SIZE) {
53✔
1001
            DBG_ERR(data->last_err = "Bad data message size");
1002
            continue;
×
1003
        }
1004

1005
        uint8_t msg_type = data->rx_msg[0] & LUMP_MSG_TYPE_MASK;
53✔
1006
        uint8_t cmd = data->rx_msg[0] & LUMP_MSG_CMD_MASK;
53✔
1007
        if (msg_type != LUMP_MSG_TYPE_DATA && (msg_type != LUMP_MSG_TYPE_CMD ||
53✔
1008
                                               (cmd != LUMP_CMD_WRITE && cmd != LUMP_CMD_EXT_MODE))) {
11✔
1009
            DBG_ERR(data->last_err = "Bad msg type");
1010
            continue;
×
1011
        }
1012

1013
        PBIO_PT_WAIT_READY(&data->data_pt,
53✔
1014
            err = pbdrv_uart_read_begin(data->uart, data->rx_msg + 1, data->rx_msg_size - 1, EV3_UART_IO_TIMEOUT));
1015
        if (err != PBIO_SUCCESS) {
53✔
1016
            DBG_ERR(data->last_err = "UART Rx data begin error");
1017
            break;
×
1018
        }
1019
        PBIO_PT_WAIT_READY(&data->data_pt, err = pbdrv_uart_read_end(data->uart));
106✔
1020
        if (err != PBIO_SUCCESS) {
53✔
1021
            DBG_ERR(data->last_err = "UART Rx data end error");
1022
            break;
×
1023
        }
1024

1025
        // at this point, we have a full data->msg that can be parsed
1026
        pbio_uartdev_parse_msg(data);
53✔
1027
    }
1028

1029
    PT_END(&data->data_pt);
×
1030
}
1031

1032
static void pbio_uartdev_handle_write_end(pbio_iodev_t *iodev) {
617✔
1033

1034
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
617✔
1035
    if (!port_data->tx_busy) {
617✔
1036
        return;
567✔
1037
    }
1038

1039
    pbio_error_t err = pbdrv_uart_write_end(port_data->uart);
50✔
1040
    if (err != PBIO_ERROR_AGAIN) {
50✔
1041
        port_data->tx_busy = false;
2✔
1042
    }
1043
}
1044

1045
static PT_THREAD(pbio_uartdev_init(struct pt *pt, uint8_t id)) {
4✔
1046
    const pbio_uartdev_platform_data_t *pdata = &pbio_uartdev_platform_data[id];
4✔
1047
    uartdev_port_data_t *port_data = &dev_data[id];
4✔
1048

1049
    PT_BEGIN(pt);
4✔
1050

1051
    PT_WAIT_UNTIL(pt, pbdrv_uart_get(pdata->uart_id, &port_data->uart) == PBIO_SUCCESS);
4✔
1052
    port_data->iodev.info = &infos[id].info;
4✔
1053
    // FIXME: uartdev should not have to care about port numbers
1054
    port_data->iodev.port = PBIO_CONFIG_UARTDEV_FIRST_PORT + id;
4✔
1055
    port_data->info = &infos[id].info;
4✔
1056
    port_data->tx_msg = &bufs[id][BUF_TX_MSG][0];
4✔
1057
    port_data->rx_msg = &bufs[id][BUF_RX_MSG][0];
4✔
1058

1059
    PT_END(pt);
4✔
1060
}
1061

1062
PROCESS_THREAD(pbio_uartdev_process, ev, data) {
617✔
1063
    static struct pt pt;
1064
    static int i;
1065

1066
    PROCESS_BEGIN();
617✔
1067

1068
    for (i = 0; i < PBIO_CONFIG_UARTDEV_NUM_DEV; i++) {
8✔
1069
        PROCESS_PT_SPAWN(&pt, pbio_uartdev_init(&pt, i));
4✔
1070
    }
1071

1072
    while (true) {
1073
        for (i = 0; i < PBIO_CONFIG_UARTDEV_NUM_DEV; i++) {
1,234✔
1074
            uartdev_port_data_t *data = &dev_data[i];
617✔
1075
            pbio_uartdev_update(data);
617✔
1076
            if (data->status == PBIO_UARTDEV_STATUS_DATA) {
617✔
1077
                pbio_uartdev_receive_data(data);
154✔
1078
            }
1079
            pbio_uartdev_handle_write_end(&data->iodev);
617✔
1080
        }
1081
        PROCESS_WAIT_EVENT();
1,230✔
1082
    }
1083

1084
    PROCESS_END();
×
1085
}
1086

1087
/**
1088
 * Gets the minimum time needed before stale data is discarded.
1089
 *
1090
 * This is empirically determined based on sensor experiments.
1091
 *
1092
 * @param [in]  id          The device type ID.
1093
 * @param [in]  mode        The device mode.
1094
 * @return                  Required delay in milliseconds.
1095
 */
1096
static uint32_t pbio_iodev_delay_stale_data(pbio_iodev_type_id_t id, uint8_t mode) {
62✔
1097
    switch (id) {
62✔
1098
        case PBIO_IODEV_TYPE_ID_COLOR_DIST_SENSOR:
62✔
1099
            return mode == PBIO_IODEV_MODE_PUP_COLOR_DISTANCE_SENSOR__IR_TX ? 0 : 30;
62✔
NEW
1100
        case PBIO_IODEV_TYPE_ID_SPIKE_COLOR_SENSOR:
×
NEW
1101
            return mode == PBIO_IODEV_MODE_PUP_COLOR_SENSOR__LIGHT ? 0 : 30;
×
NEW
1102
        case PBIO_IODEV_TYPE_ID_SPIKE_ULTRASONIC_SENSOR:
×
NEW
1103
            return mode == PBIO_IODEV_MODE_PUP_ULTRASONIC_SENSOR__LIGHT ? 0 : 50;
×
NEW
1104
        default:
×
1105
            // Default delay for other sensors and modes.
NEW
1106
            return 0;
×
1107
    }
1108
}
1109

1110
/**
1111
 * Gets the minimum time needed for the device to handle written data.
1112
 *
1113
 * This is empirically determined based on sensor experiments.
1114
 *
1115
 * @param [in]  id          The device type ID.
1116
 * @param [in]  mode        The device mode.
1117
 * @return                  Required delay in milliseconds.
1118
 */
NEW
1119
static uint32_t pbio_iodev_delay_set_data(pbio_iodev_type_id_t id, uint8_t mode) {
×
1120
    // The Boost Color Distance Sensor requires a long delay or successive
1121
    // writes are ignored.
NEW
1122
    if (id == PBIO_IODEV_TYPE_ID_COLOR_DIST_SENSOR && mode == PBIO_IODEV_MODE_PUP_COLOR_DISTANCE_SENSOR__IR_TX) {
×
NEW
1123
        return 250;
×
1124
    }
1125

1126
    // Default delay for setting data. In practice, this is the delay for setting
1127
    // the light on the color sensor and ultrasonic sensor.
NEW
1128
    return 2;
×
1129
}
1130

NEW
1131
pbio_error_t pbio_iodev_start_buffered_write(pbio_iodev_t *iodev) {
×
1132

NEW
1133
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
×
NEW
1134
    const pbio_iodev_mode_t *mode_info = &iodev->info->mode_info[iodev->mode];
×
1135

1136
    // Reset data length so we transmit only once.
NEW
1137
    uint8_t size = port_data->data_set_len;
×
NEW
1138
    port_data->data_set_len = 0;
×
1139

1140
    // Not all modes support setting data and data must be of expected size.
NEW
1141
    if (!(mode_info->data_type & PBIO_IODEV_DATA_TYPE_WRITABLE) ||
×
NEW
1142
        size != mode_info->num_values * pbio_iodev_size_of(mode_info->data_type)) {
×
NEW
1143
        return PBIO_ERROR_INVALID_OP;
×
1144
    }
1145

NEW
1146
    return ev3_uart_begin_tx_msg(port_data, LUMP_MSG_TYPE_DATA, iodev->mode, iodev->bin_data, size);
×
1147
}
1148

1149
/**
1150
 * Checks if LEGO UART device has data available for reading or is ready to write.
1151
 *
1152
 * @param [in]  iodev       The I/O device
1153
 * @return                  ::PBIO_SUCCESS if ready.
1154
 *                          ::PBIO_ERROR_AGAIN if not ready yet.
1155
 *                          ::PBIO_ERROR_NO_DEV if no device is attached.
1156
 */
1157
pbio_error_t pbio_iodev_is_ready(pbio_iodev_t *iodev) {
66✔
1158

1159
    // Device is not there or still syncing.
1160
    if (iodev->info->type_id == PBIO_IODEV_TYPE_ID_NONE) {
66✔
NEW
1161
        return PBIO_ERROR_NO_DEV;
×
1162
    }
1163

1164
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
66✔
1165
    const pbio_iodev_type_id_t id = iodev->info->type_id;
66✔
1166
    const uint8_t mode = iodev->mode;
66✔
1167

1168
    // Not ready if busy writing.
1169
    if (port_data->tx_busy) {
66✔
1170
        return PBIO_ERROR_AGAIN;
2✔
1171
    }
1172

1173
    uint32_t time = pbdrv_clock_get_ms();
64✔
1174

1175
    // If we were setting data, then wait for the matching delay.
1176
    if (port_data->tx_type == LUMP_MSG_TYPE_DATA) {
64✔
NEW
1177
        return (time - port_data->tx_start_time < pbio_iodev_delay_set_data(iodev->info->type_id, mode)) ?
×
NEW
1178
               PBIO_ERROR_AGAIN : PBIO_SUCCESS;
×
1179
    }
1180

1181
    if (// Mode change is underway, check back later.
64✔
1182
        port_data->iodev.mode != port_data->new_mode ||
126✔
1183
        // May still have stale data, check back later.
1184
        time - port_data->mode_switch_time < pbio_iodev_delay_stale_data(id, mode)) {
62✔
1185
        return PBIO_ERROR_AGAIN;
60✔
1186
    }
1187

1188
    // The requested modes are set. Could start sending data if there is any.
1189
    if (port_data->data_set_len) {
4✔
NEW
1190
        pbio_error_t err = pbio_iodev_start_buffered_write(iodev);
×
1191
        // On success, data only just started, so we are not ready yet.
NEW
1192
        return err == PBIO_SUCCESS ? PBIO_ERROR_AGAIN : err;
×
1193
    }
1194

1195
    // Everything is idle and nothing to send, so we are done.
1196
    return PBIO_SUCCESS;
4✔
1197
}
1198

1199
/**
1200
 * Starts setting the mode of a LEGO UART device.
1201
 *
1202
 * @param [in]  iodev       The I/O device.
1203
 * @param [in]  id          The ID of the device to request data from.
1204
 * @param [in]  mode        The mode to set.
1205
 * @return                  ::PBIO_SUCCESS on success or if mode already set.
1206
 *                          ::PBIO_ERROR_NO_DEV if the port does not have a device attached.
1207
 *                          ::PBIO_ERROR_INVALID_ARG if the mode is not valid.
1208
 *                          ::PBIO_ERROR_AGAIN if the device is not ready for this operation.
1209
 */
1210
pbio_error_t pbio_iodev_set_mode(pbio_iodev_t *iodev, uint8_t mode) {
2✔
1211

1212
    // Device is not there or still syncing.
1213
    if (iodev->info->type_id == PBIO_IODEV_TYPE_ID_NONE) {
2✔
NEW
1214
        return PBIO_ERROR_NO_DEV;
×
1215
    }
1216

1217
    // Can only set available modes.
1218
    if (mode >= iodev->info->num_modes) {
2✔
NEW
1219
        return PBIO_ERROR_INVALID_ARG;
×
1220
    }
1221

1222
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
2✔
1223

1224
    // Discard any old data that was never sent.
1225
    port_data->data_set_len = 0;
2✔
1226

1227
    // Mode already set or being set, so return success.
1228
    if (port_data->new_mode == mode || iodev->mode == mode) {
2✔
NEW
1229
        return PBIO_SUCCESS;
×
1230
    }
1231

1232
    // We can only initiate a mode switch if currently idle (receiving data).
1233
    pbio_error_t err = pbio_iodev_is_ready(iodev);
2✔
1234
    if (err != PBIO_SUCCESS) {
2✔
NEW
1235
        return err;
×
1236
    }
1237

1238
    // Start setting new mode.
1239
    err = ev3_uart_begin_tx_msg(port_data, LUMP_MSG_TYPE_CMD, LUMP_CMD_SELECT, &mode, 1);
2✔
1240
    if (err != PBIO_SUCCESS) {
2✔
NEW
1241
        return err;
×
1242
    }
1243
    port_data->new_mode = mode;
2✔
1244
    return PBIO_SUCCESS;
2✔
1245
}
1246

1247
/**
1248
 * Atomic operation for asserting the mode/id and getting the data of a LEGO UART device.
1249
 *
1250
 * @param [in]  iodev       The I/O device
1251
 * @param [out] data        Pointer to hold array of data values.
1252
 * @return                  ::PBIO_SUCCESS on success
1253
 *                          ::PBIO_ERROR_NO_DEV if the port does not have a device attached
1254
 *                          ::PBIO_ERROR_AGAIN if the device is not ready for this operation.
1255
 */
NEW
1256
pbio_error_t pbio_iodev_get_data(pbio_iodev_t *iodev, uint8_t mode, uint8_t **data) {
×
1257

1258
    // Device is not there or still syncing.
NEW
1259
    if (iodev->info->type_id == PBIO_IODEV_TYPE_ID_NONE) {
×
NEW
1260
        return PBIO_ERROR_NO_DEV;
×
1261
    }
1262

NEW
1263
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
×
1264

1265
    // Can only request data for mode that is set.
NEW
1266
    if (mode != port_data->iodev.mode) {
×
NEW
1267
        return PBIO_ERROR_BUSY;
×
1268
    }
1269

NEW
1270
    pbio_error_t err = pbio_iodev_is_ready(iodev);
×
NEW
1271
    if (err != PBIO_SUCCESS) {
×
NEW
1272
        return err;
×
1273
    }
1274

NEW
1275
    *data = iodev->bin_data;
×
1276

NEW
1277
    return PBIO_SUCCESS;
×
1278
}
1279

1280
/**
1281
 * Set data for the current mode.
1282
 *
1283
 * @param [in]  iodev       The I/O device
1284
 * @param [out] data        Data to be set.
1285
 * @return                  ::PBIO_SUCCESS on success
1286
 *                          ::PBIO_ERROR_NO_DEV if the port does not have a device attached
1287
 */
NEW
1288
pbio_error_t pbio_iodev_set_mode_with_data(pbio_iodev_t *iodev, uint8_t mode, const uint8_t *data) {
×
1289

1290
    // Start setting mode.
NEW
1291
    pbio_error_t err = pbio_iodev_set_mode(iodev, mode);
×
NEW
1292
    if (err != PBIO_SUCCESS) {
×
NEW
1293
        return err;
×
1294
    }
1295

1296
    // info of the mode *to be set*, which might not be current mode.
NEW
1297
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
×
NEW
1298
    pbio_iodev_mode_t *mode_info = &port_data->info->mode_info[mode];
×
1299

1300
    // Check if the device is in this mode already.
NEW
1301
    err = pbio_iodev_is_ready(iodev);
×
NEW
1302
    if (err != PBIO_SUCCESS && err != PBIO_ERROR_AGAIN) {
×
NEW
1303
        return err;
×
1304
    }
1305

1306
    // Copy data to be sent after mode switch.
NEW
1307
    port_data->data_set_len = mode_info->num_values * pbio_iodev_size_of(mode_info->data_type);
×
NEW
1308
    memcpy(iodev->bin_data, data, port_data->data_set_len);
×
1309

1310
    // If already in the right mode, start sending data right away.
NEW
1311
    if (err == PBIO_SUCCESS) {
×
NEW
1312
        return pbio_iodev_start_buffered_write(iodev);
×
1313
    }
NEW
1314
    return PBIO_SUCCESS;
×
1315
}
1316

1317
#endif // PBIO_CONFIG_UARTDEV
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