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

pybricks / pybricks-micropython / 5039089374

pending completion
5039089374

Pull #169

github

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

64 of 284 new or added lines in 13 files covered. (22.54%)

7 existing lines in 6 files now uncovered.

3161 of 5990 relevant lines covered (52.77%)

32976625.3 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
 * @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
static void pbio_uartdev_handle_write_end(pbio_iodev_t *iodev) {
617✔
1030

1031
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
617✔
1032
    if (!port_data->tx_busy) {
617✔
1033
        return;
567✔
1034
    }
1035

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

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

1046
    PT_BEGIN(pt);
4✔
1047

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

1056
    PT_END(pt);
4✔
1057
}
1058

1059
PROCESS_THREAD(pbio_uartdev_process, ev, data) {
617✔
1060
    static struct pt pt;
1061
    static int i;
1062

1063
    PROCESS_BEGIN();
617✔
1064

1065
    for (i = 0; i < PBIO_CONFIG_UARTDEV_NUM_DEV; i++) {
8✔
1066
        PROCESS_PT_SPAWN(&pt, pbio_uartdev_init(&pt, i));
4✔
1067
    }
1068

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

1081
    PROCESS_END();
×
1082
}
1083

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

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

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

NEW
1128
pbio_error_t pbio_iodev_start_buffered_write(pbio_iodev_t *iodev) {
×
1129

NEW
1130
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
×
NEW
1131
    const pbio_iodev_mode_t *mode_info = &iodev->info->mode_info[iodev->mode];
×
1132

1133
    // Reset data length so we transmit only once.
NEW
1134
    uint8_t size = port_data->data_set_len;
×
NEW
1135
    port_data->data_set_len = 0;
×
1136

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

NEW
1143
    return ev3_uart_begin_tx_msg(port_data, LUMP_MSG_TYPE_DATA, iodev->mode, iodev->bin_data, size);
×
1144
}
1145

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

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

1161
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
66✔
1162
    const pbio_iodev_type_id_t id = iodev->info->type_id;
66✔
1163
    const uint8_t mode = iodev->mode;
66✔
1164

1165
    // Not ready if busy writing.
1166
    if (port_data->tx_busy) {
66✔
1167
        return PBIO_ERROR_AGAIN;
2✔
1168
    }
1169

1170
    uint32_t time = pbdrv_clock_get_ms();
64✔
1171

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

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

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

1192
    // Everything is idle and nothing to send, so we are done.
1193
    return PBIO_SUCCESS;
4✔
1194
}
1195

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

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

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

1219
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
2✔
1220

1221
    // Discard any old data that was never sent.
1222
    port_data->data_set_len = 0;
2✔
1223

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

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

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

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

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

NEW
1260
    uartdev_port_data_t *port_data = PBIO_CONTAINER_OF(iodev, uartdev_port_data_t, iodev);
×
1261

1262
    // Can only request data for mode that is set.
NEW
1263
    if (mode != port_data->iodev.mode) {
×
NEW
1264
        return PBIO_ERROR_INVALID_OP;
×
1265
    }
1266

NEW
1267
    pbio_error_t err = pbio_iodev_is_ready(iodev);
×
NEW
1268
    if (err != PBIO_SUCCESS) {
×
NEW
1269
        return err;
×
1270
    }
1271

NEW
1272
    *data = iodev->bin_data;
×
1273

NEW
1274
    return PBIO_SUCCESS;
×
1275
}
1276

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

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

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

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

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

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

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