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

pybricks / pybricks-micropython / 5044552036

pending completion
5044552036

Pull #169

github

GitHub
Merge 98773a05d into edaf7e51e
Pull Request #169: Refactor src/uartdev for async and code size

69 of 283 new or added lines in 13 files covered. (24.38%)

8 existing lines in 7 files now uncovered.

3169 of 5993 relevant lines covered (52.88%)

32960120.55 hits per line

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

67.82
/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
 * @tx_start_time: Time of most recently started transmission.
160
 * @data_rec: Flag that indicates that good DATA data->msg has been received
161
 *      since last watchdog timeout.
162
 * @tx_busy: mutex that protects tx_msg
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
    uint32_t tx_start_time;
191
    bool data_rec;
192
    bool tx_busy;
193
    uint8_t data_set_len;
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
                case LUMP_INFO_PCT:
483
                case LUMP_INFO_SI:
484
                case LUMP_INFO_UNITS:
485
                    break;
486
                #endif // UARTDEV_ENABLE_EXTRAS
487
                case LUMP_INFO_MAPPING:
27✔
488
                    if (data->new_mode != mode) {
27✔
489
                        DBG_ERR(data->last_err = "Received INFO for incorrect mode");
490
                        goto err;
×
491
                    }
492
                    if (test_and_set_bit(EV3_UART_INFO_BIT_INFO_MAPPING, &data->info_flags)) {
27✔
493
                        DBG_ERR(data->last_err = "Received duplicate mapping INFO");
494
                        goto err;
×
495
                    }
496

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

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

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

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

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

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

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

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

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

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

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

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

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

609
            data->data_rec = true;
42✔
610
            if (data->num_data_err) {
42✔
611
                data->num_data_err--;
4✔
612
            }
613
            break;
42✔
614
    }
615

616
    return;
268✔
617

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

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

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

635
    if (len == 0 || len > 32) {
6✔
636
        return PBIO_ERROR_INVALID_ARG;
×
637
    }
638

639
    if (port_data->tx_busy) {
6✔
640
        return PBIO_ERROR_AGAIN;
×
641
    }
642

643
    port_data->tx_busy = true;
6✔
644
    port_data->tx_type = msg_type;
6✔
645
    port_data->tx_start_time = pbdrv_clock_get_ms();
6✔
646

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

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

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

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

686
    header = ev3_uart_set_msg_hdr(msg_type, size, cmd);
6✔
687
    checksum ^= header;
6✔
688

689
    port_data->tx_msg[offset] = header;
6✔
690
    port_data->tx_msg[offset + i + 1] = checksum;
6✔
691

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

697
    return err;
6✔
698
}
699

700
static PT_THREAD(pbio_uartdev_send_speed_msg(uartdev_port_data_t * data, uint32_t speed)) {
8✔
701
    pbio_error_t err;
702

703
    PT_BEGIN(&data->speed_pt);
8✔
704

705
    PT_WAIT_WHILE(&data->speed_pt, data->tx_busy);
4✔
706

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

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

723
    data->tx_busy = false;
4✔
724

725
    PT_END(&data->speed_pt);
4✔
726

727
err:
×
728
    PT_EXIT(&data->speed_pt);
×
729
}
730

731
static PT_THREAD(pbio_uartdev_update(uartdev_port_data_t * data)) {
617✔
732
    pbio_error_t err;
733
    uint8_t checksum;
734

735
    PT_BEGIN(&data->pt);
617✔
736

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

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

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

756
    // block until pbio_uartdev_ready() is called
757
    PT_WAIT_UNTIL(&data->pt, data->status == PBIO_UARTDEV_STATUS_SYNCING);
8✔
758

759
    pbdrv_uart_flush(data->uart);
4✔
760

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

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

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

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

799
        if (data->rx_msg[0] == (LUMP_MSG_TYPE_CMD | LUMP_CMD_TYPE)) {
4✔
800
            break;
4✔
801
        }
802
    }
803

804
    // then read the rest of the message
805

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

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

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

828
    // if all was good, we are ready to start receiving the mode info
829

830
    data->info->num_modes = 1;
4✔
831

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

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

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

858
        // read the rest of the message
859
        if (data->rx_msg_size > 1) {
219✔
860
            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✔
861
            if (err != PBIO_SUCCESS) {
215✔
862
                DBG_ERR(data->last_err = "UART Rx begin error during info");
863
                goto err;
×
864
            }
865
            PBIO_PT_WAIT_READY(&data->pt, err = pbdrv_uart_read_end(data->uart));
431✔
866
            if (err != PBIO_SUCCESS) {
215✔
867
                DBG_ERR(data->last_err = "UART Rx end error during info");
868
                goto err;
×
869
            }
870
        }
871

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

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

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

900
    // schedule baud rate change
901
    etimer_set(&data->timer, 10);
4✔
902
    PT_WAIT_UNTIL(&data->pt, etimer_expired(&data->timer));
8✔
903

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

908
    data->status = PBIO_UARTDEV_STATUS_DATA;
4✔
909
    // reset data rx thread
910
    PT_INIT(&data->data_pt);
4✔
911

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

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

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

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

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

965
    // Turn off battery supply to this port
966
    if (data->motor_driver) {
×
967
        pbdrv_motor_driver_coast(data->motor_driver);
×
968
    }
969

970
    process_post(PROCESS_BROADCAST, PROCESS_EVENT_SERVICE_REMOVED, &data->iodev);
×
971

972
    PT_END(&data->pt);
×
973
}
974

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

981
    PT_BEGIN(&data->data_pt);
154✔
982

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

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

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

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

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

1026
    PT_END(&data->data_pt);
×
1027
}
1028

1029
/**
1030
 * Gets the minimum time needed before stale data is discarded.
1031
 *
1032
 * This is empirically determined based on sensor experiments.
1033
 *
1034
 * @param [in]  id          The device type ID.
1035
 * @param [in]  mode        The device mode.
1036
 * @return                  Required delay in milliseconds.
1037
 */
1038
static uint32_t pbio_iodev_delay_stale_data(pbio_iodev_type_id_t id, uint8_t mode) {
171✔
1039
    switch (id) {
171✔
1040
        case PBIO_IODEV_TYPE_ID_COLOR_DIST_SENSOR:
101✔
1041
            return mode == PBIO_IODEV_MODE_PUP_COLOR_DISTANCE_SENSOR__IR_TX ? 0 : 30;
101✔
NEW
1042
        case PBIO_IODEV_TYPE_ID_SPIKE_COLOR_SENSOR:
×
NEW
1043
            return mode == PBIO_IODEV_MODE_PUP_COLOR_SENSOR__LIGHT ? 0 : 30;
×
NEW
1044
        case PBIO_IODEV_TYPE_ID_SPIKE_ULTRASONIC_SENSOR:
×
NEW
1045
            return mode == PBIO_IODEV_MODE_PUP_ULTRASONIC_SENSOR__LIGHT ? 0 : 50;
×
1046
        default:
70✔
1047
            // Default delay for other sensors and modes.
1048
            return 0;
70✔
1049
    }
1050
}
1051

1052
/**
1053
 * Gets the minimum time needed for the device to handle written data.
1054
 *
1055
 * This is empirically determined based on sensor experiments.
1056
 *
1057
 * @param [in]  id          The device type ID.
1058
 * @param [in]  mode        The device mode.
1059
 * @return                  Required delay in milliseconds.
1060
 */
NEW
1061
static uint32_t pbio_iodev_delay_set_data(pbio_iodev_type_id_t id, uint8_t mode) {
×
1062
    // The Boost Color Distance Sensor requires a long delay or successive
1063
    // writes are ignored.
NEW
1064
    if (id == PBIO_IODEV_TYPE_ID_COLOR_DIST_SENSOR && mode == PBIO_IODEV_MODE_PUP_COLOR_DISTANCE_SENSOR__IR_TX) {
×
NEW
1065
        return 250;
×
1066
    }
1067

1068
    // Default delay for setting data. In practice, this is the delay for setting
1069
    // the light on the color sensor and ultrasonic sensor.
NEW
1070
    return 2;
×
1071
}
1072

1073
/**
1074
 * Checks if LEGO UART device mode change or data set operation is complete.
1075
 *
1076
 * @param [in]  iodev       The I/O device
1077
 * @return                  @c true if ready, @c false otherwise.
1078
 */
1079
static bool pbio_uartdev_operation_complete(pbio_iodev_t *iodev) {
220✔
1080

1081
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
220✔
1082
    const pbio_iodev_type_id_t id = iodev->info->type_id;
220✔
1083
    const uint8_t mode = iodev->mode;
220✔
1084

1085
    // Not ready if busy writing.
1086
    if (port_data->tx_busy) {
220✔
1087
        return false;
42✔
1088
    }
1089

1090
    uint32_t time = pbdrv_clock_get_ms();
178✔
1091

1092
    // If we were setting data, then wait for the matching delay.
1093
    if (port_data->tx_type == LUMP_MSG_TYPE_DATA) {
178✔
NEW
1094
        return time - port_data->tx_start_time >= pbio_iodev_delay_set_data(id, mode);
×
1095
    }
1096

1097
    // Ready if mode change is complete and we have waited long enough for stale data to be discarded.
1098
    return mode == port_data->new_mode &&
349✔
1099
           time - port_data->mode_switch_time >= pbio_iodev_delay_stale_data(id, mode);
171✔
1100
}
1101

1102
/**
1103
 * Starts sending data to the LEGO UART device mode.
1104
 *
1105
 * @param [in]  iodev       The I/O device
1106
 * @return                  Error code corresponding to ::ev3_uart_begin_tx_msg
1107
 *                          or ::PBIO_ERROR_INVALID_OP if device not in expected mode.
1108
 */
NEW
1109
static pbio_error_t pbio_uartdev_start_buffered_data_set(pbio_iodev_t *iodev) {
×
1110

1111
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
×
NEW
1112
    const pbio_iodev_mode_t *mode_info = &iodev->info->mode_info[iodev->mode];
×
1113

1114
    // Reset data length so we transmit only once.
NEW
1115
    uint8_t size = port_data->data_set_len;
×
NEW
1116
    port_data->data_set_len = 0;
×
1117

1118
    // Not all modes support setting data and data must be of expected size.
NEW
1119
    if (!(mode_info->data_type & PBIO_IODEV_DATA_TYPE_WRITABLE) ||
×
NEW
1120
        size != mode_info->num_values * pbio_iodev_size_of(mode_info->data_type)) {
×
UNCOV
1121
        return PBIO_ERROR_INVALID_OP;
×
1122
    }
1123

NEW
1124
    return ev3_uart_begin_tx_msg(port_data, LUMP_MSG_TYPE_DATA, iodev->mode, iodev->bin_data, size);
×
1125
}
1126

1127
/**
1128
 * Starts sending data to the LEGO UART device mode if there is any.
1129
 */
1130
static void pbio_uartdev_handle_data_set_start(pbio_iodev_t *iodev) {
154✔
1131
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
154✔
1132
    if (pbio_uartdev_operation_complete(iodev) && port_data->data_set_len > 0) {
154✔
NEW
1133
        pbio_uartdev_start_buffered_data_set(iodev);
×
1134
    }
1135
}
154✔
1136

1137
static void pbio_uartdev_handle_write_end(pbio_iodev_t *iodev) {
617✔
1138

1139
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
617✔
1140
    if (!port_data->tx_busy) {
617✔
1141
        return;
567✔
1142
    }
1143

1144
    pbio_error_t err = pbdrv_uart_write_end(port_data->uart);
50✔
1145
    if (err != PBIO_ERROR_AGAIN) {
50✔
1146
        port_data->tx_busy = false;
2✔
1147
    }
1148
}
1149

1150
static PT_THREAD(pbio_uartdev_init(struct pt *pt, uint8_t id)) {
4✔
1151
    const pbio_uartdev_platform_data_t *pdata = &pbio_uartdev_platform_data[id];
4✔
1152
    uartdev_port_data_t *port_data = &dev_data[id];
4✔
1153

1154
    PT_BEGIN(pt);
4✔
1155

1156
    PT_WAIT_UNTIL(pt, pbdrv_uart_get(pdata->uart_id, &port_data->uart) == PBIO_SUCCESS);
4✔
1157
    port_data->iodev.info = &infos[id].info;
4✔
1158
    // FIXME: uartdev should not have to care about port numbers
1159
    port_data->iodev.port = PBIO_CONFIG_UARTDEV_FIRST_PORT + id;
4✔
1160
    port_data->info = &infos[id].info;
4✔
1161
    port_data->tx_msg = &bufs[id][BUF_TX_MSG][0];
4✔
1162
    port_data->rx_msg = &bufs[id][BUF_RX_MSG][0];
4✔
1163

1164
    PT_END(pt);
4✔
1165
}
1166

1167
PROCESS_THREAD(pbio_uartdev_process, ev, data) {
617✔
1168
    static struct pt pt;
1169
    static int i;
1170

1171
    PROCESS_BEGIN();
617✔
1172

1173
    for (i = 0; i < PBIO_CONFIG_UARTDEV_NUM_DEV; i++) {
8✔
1174
        PROCESS_PT_SPAWN(&pt, pbio_uartdev_init(&pt, i));
4✔
1175
    }
1176

1177
    while (true) {
1178
        for (i = 0; i < PBIO_CONFIG_UARTDEV_NUM_DEV; i++) {
1,234✔
1179
            uartdev_port_data_t *data = &dev_data[i];
617✔
1180
            pbio_uartdev_update(data);
617✔
1181
            if (data->status == PBIO_UARTDEV_STATUS_DATA) {
617✔
1182
                pbio_uartdev_receive_data(data);
154✔
1183
            }
1184
            pbio_uartdev_handle_write_end(&data->iodev);
617✔
1185
            if (data->status == PBIO_UARTDEV_STATUS_DATA) {
617✔
1186
                pbio_uartdev_handle_data_set_start(&data->iodev);
154✔
1187
            }
1188
        }
1189
        PROCESS_WAIT_EVENT();
1,230✔
1190
    }
1191

1192
    PROCESS_END();
×
1193
}
1194

1195
/**
1196
 * Checks if LEGO UART device has data available for reading or is ready to write.
1197
 *
1198
 * @param [in]  iodev       The I/O device
1199
 * @return                  ::PBIO_SUCCESS if ready.
1200
 *                          ::PBIO_ERROR_AGAIN if not ready yet.
1201
 *                          ::PBIO_ERROR_NO_DEV if no device is attached.
1202
 */
1203
pbio_error_t pbio_iodev_is_ready(pbio_iodev_t *iodev) {
66✔
1204

1205
    // Device is not there or still syncing.
1206
    if (iodev->info->type_id == PBIO_IODEV_TYPE_ID_NONE) {
66✔
NEW
1207
        return PBIO_ERROR_NO_DEV;
×
1208
    }
1209

1210
    // Ready if operations are complete and there is no data left to set.
1211
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
66✔
1212
    return pbio_uartdev_operation_complete(iodev) && port_data->data_set_len == 0 ? PBIO_SUCCESS : PBIO_ERROR_AGAIN;
66✔
1213
}
1214

1215
/**
1216
 * Starts setting the mode of a LEGO UART device.
1217
 *
1218
 * @param [in]  iodev       The I/O device.
1219
 * @param [in]  id          The ID of the device to request data from.
1220
 * @param [in]  mode        The mode to set.
1221
 * @return                  ::PBIO_SUCCESS on success or if mode already set.
1222
 *                          ::PBIO_ERROR_NO_DEV if the port does not have a device attached.
1223
 *                          ::PBIO_ERROR_INVALID_ARG if the mode is not valid.
1224
 *                          ::PBIO_ERROR_AGAIN if the device is not ready for this operation.
1225
 */
1226
pbio_error_t pbio_iodev_set_mode(pbio_iodev_t *iodev, uint8_t mode) {
2✔
1227

1228
    // Device is not there or still syncing.
1229
    if (iodev->info->type_id == PBIO_IODEV_TYPE_ID_NONE) {
2✔
NEW
1230
        return PBIO_ERROR_NO_DEV;
×
1231
    }
1232

1233
    // Can only set available modes.
1234
    if (mode >= iodev->info->num_modes) {
2✔
NEW
1235
        return PBIO_ERROR_INVALID_ARG;
×
1236
    }
1237

1238
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
2✔
1239

1240
    // Discard any old data that was never sent.
1241
    port_data->data_set_len = 0;
2✔
1242

1243
    // Mode already set or being set, so return success.
1244
    if (port_data->new_mode == mode || iodev->mode == mode) {
2✔
NEW
1245
        return PBIO_SUCCESS;
×
1246
    }
1247

1248
    // We can only initiate a mode switch if currently idle (receiving data).
1249
    pbio_error_t err = pbio_iodev_is_ready(iodev);
2✔
1250
    if (err != PBIO_SUCCESS) {
2✔
NEW
1251
        return err;
×
1252
    }
1253

1254
    // Start setting new mode.
1255
    err = ev3_uart_begin_tx_msg(port_data, LUMP_MSG_TYPE_CMD, LUMP_CMD_SELECT, &mode, 1);
2✔
1256
    if (err != PBIO_SUCCESS) {
2✔
NEW
1257
        return err;
×
1258
    }
1259
    port_data->new_mode = mode;
2✔
1260
    return PBIO_SUCCESS;
2✔
1261
}
1262

1263
/**
1264
 * Atomic operation for asserting the mode/id and getting the data of a LEGO UART device.
1265
 *
1266
 * The returned data buffer is 4-byte aligned. Data is in little-endian format.
1267
 *
1268
 * @param [in]  iodev       The I/O device
1269
 * @param [out] data        Pointer to hold array of data values.
1270
 * @return                  ::PBIO_SUCCESS on success.
1271
 *                          ::PBIO_ERROR_NO_DEV if the port does not have a device attached.
1272
 *                          ::PBIO_ERROR_AGAIN if the device is not ready for this operation.
1273
 */
NEW
1274
pbio_error_t pbio_iodev_get_data(pbio_iodev_t *iodev, uint8_t mode, uint8_t **data) {
×
1275

1276
    // Device is not there or still syncing.
NEW
1277
    if (iodev->info->type_id == PBIO_IODEV_TYPE_ID_NONE) {
×
NEW
1278
        return PBIO_ERROR_NO_DEV;
×
1279
    }
1280

NEW
1281
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
×
1282

1283
    // Can only request data for mode that is set.
NEW
1284
    if (mode != port_data->iodev.mode) {
×
NEW
1285
        return PBIO_ERROR_INVALID_OP;
×
1286
    }
1287

NEW
1288
    pbio_error_t err = pbio_iodev_is_ready(iodev);
×
NEW
1289
    if (err != PBIO_SUCCESS) {
×
NEW
1290
        return err;
×
1291
    }
1292

NEW
1293
    *data = iodev->bin_data;
×
1294

NEW
1295
    return PBIO_SUCCESS;
×
1296
}
1297

1298
/**
1299
 * Set data for the current mode.
1300
 *
1301
 * @param [in]  iodev       The I/O device
1302
 * @param [out] data        Data to be set.
1303
 * @return                  ::PBIO_SUCCESS on success.
1304
 *                          ::PBIO_ERROR_NO_DEV if the port does not have a device attached.
1305
 */
NEW
1306
pbio_error_t pbio_iodev_set_mode_with_data(pbio_iodev_t *iodev, uint8_t mode, const uint8_t *data) {
×
1307

1308
    // Start setting mode.
NEW
1309
    pbio_error_t err = pbio_iodev_set_mode(iodev, mode);
×
NEW
1310
    if (err != PBIO_SUCCESS) {
×
NEW
1311
        return err;
×
1312
    }
1313

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

1318
    // Check if the device is in this mode already.
NEW
1319
    err = pbio_iodev_is_ready(iodev);
×
NEW
1320
    if (err != PBIO_SUCCESS && err != PBIO_ERROR_AGAIN) {
×
NEW
1321
        return err;
×
1322
    }
1323

1324
    // Copy data to be sent after mode switch.
NEW
1325
    port_data->data_set_len = mode_info->num_values * pbio_iodev_size_of(mode_info->data_type);
×
NEW
1326
    memcpy(iodev->bin_data, data, port_data->data_set_len);
×
1327

1328
    // If already in the right mode, start sending data right away.
NEW
1329
    if (err == PBIO_SUCCESS) {
×
NEW
1330
        return pbio_uartdev_start_buffered_data_set(iodev);
×
1331
    }
NEW
1332
    return PBIO_SUCCESS;
×
1333
}
1334

1335
#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

© 2025 Coveralls, Inc