mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #12012 from HendrikVE/nimble_shell_module
sys/stdio_nimble: add new stdio module using nimble
This commit is contained in:
commit
14f22c17aa
11
dist/pythonlibs/testrunner/spawn.py
vendored
11
dist/pythonlibs/testrunner/spawn.py
vendored
@ -39,7 +39,13 @@ TEST_INTERACTIVE_DELAY = int(os.environ.get('TEST_INTERACTIVE_DELAY') or 1)
|
||||
# By default never reset after the terminal is open unless explicitly requested
|
||||
# through an environment variable.
|
||||
TESTRUNNER_RESET_AFTER_TERM = int(os.environ.get('TESTRUNNER_RESET_AFTER_TERM')
|
||||
or '0')
|
||||
or 0)
|
||||
|
||||
# When running e.g. tests/shell_ble we don't want to reset the board, because
|
||||
# then ble-serial would terminate and the created virtual serial port would get
|
||||
# lost. By default the board is reset before the test starts.
|
||||
TESTRUNNER_RESET_BOARD_ON_STARTUP = \
|
||||
int(os.environ.get('TESTRUNNER_RESET_BOARD_ON_STARTUP') or 1)
|
||||
|
||||
MAKE = os.environ.get('MAKE', 'make')
|
||||
|
||||
@ -70,7 +76,8 @@ def find_exc_origin(exc_info):
|
||||
def setup_child(timeout=10, spawnclass=pexpect.spawnu, env=None, logfile=None):
|
||||
# Some boards can't be reset after a terminal is open. Therefore reset
|
||||
# before `cleanterm`.
|
||||
_reset_board(env)
|
||||
if TESTRUNNER_RESET_BOARD_ON_STARTUP:
|
||||
_reset_board(env)
|
||||
|
||||
# on platforms exposing UART over USB, wait a little before connecting to
|
||||
# the serial terminal. This gives time for stdio to be ready.
|
||||
|
@ -201,6 +201,7 @@ PSEUDOMODULES += stdin
|
||||
PSEUDOMODULES += stdio_available
|
||||
PSEUDOMODULES += stdio_cdc_acm
|
||||
PSEUDOMODULES += stdio_ethos
|
||||
PSEUDOMODULES += stdio_nimble_debug
|
||||
PSEUDOMODULES += stdio_uart_rx
|
||||
PSEUDOMODULES += stm32_eth
|
||||
PSEUDOMODULES += stm32_eth_auto
|
||||
|
@ -2,6 +2,7 @@ STDIO_MODULES = \
|
||||
slipdev_stdio \
|
||||
stdio_cdc_acm \
|
||||
stdio_ethos \
|
||||
stdio_nimble \
|
||||
stdio_null \
|
||||
stdio_rtt \
|
||||
stdio_semihosting \
|
||||
@ -29,6 +30,18 @@ ifneq (,$(filter stdio_ethos,$(USEMODULE)))
|
||||
USEMODULE += stdin
|
||||
endif
|
||||
|
||||
ifneq (,$(filter stdio_nimble,$(USEMODULE)))
|
||||
USEMODULE += stdio_available
|
||||
USEPKG += nimble
|
||||
USEMODULE += tsrb
|
||||
USEMODULE += isrpipe
|
||||
USEMODULE += nimble_svc_gap
|
||||
USEMODULE += nimble_svc_gatt
|
||||
ifneq (,$(filter stdio_nimble_debug,$(USEMODULE)))
|
||||
FEATURES_REQUIRED += periph_uart
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(filter stdin,$(USEMODULE)))
|
||||
ifneq (,$(filter stdio_uart,$(USEMODULE)))
|
||||
USEMODULE += stdio_uart_rx
|
||||
|
@ -135,6 +135,17 @@ ifneq (,$(filter nimble_netif,$(USEMODULE)))
|
||||
CFLAGS += -DMYNEWT_VAL_BLE_LL_CONN_INIT_SLOTS=1
|
||||
CFLAGS += -DMYNEWT_VAL_BLE_LL_CFG_FEAT_DATA_LEN_EXT=1
|
||||
endif
|
||||
else
|
||||
ifneq (,$(filter stdio_nimble,$(USEMODULE)))
|
||||
# the maximum fragment size that we can receive. For maximum efficiency this
|
||||
# should be equal to the maximum configured link layer packet size.
|
||||
# WARNING: this value MUST never be larger than MYNEWT_VAL_BLE_LL_MAX_PKT_SIZE
|
||||
CFLAGS += -DMYNEWT_VAL_BLE_L2CAP_COC_MPS=251
|
||||
|
||||
# in order to fit a 251 byte COC data segment into a single mbuf buffer, the
|
||||
# used block size must be at least 297 byte (251 data + 48 overhead)
|
||||
CFLAGS += -DMYNEWT_VAL_MSYS_1_BLOCK_SIZE="(MYNEWT_VAL_BLE_L2CAP_COC_MPS + 48)"
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(filter nimble_rpble,$(USEMODULE)))
|
||||
|
@ -176,6 +176,13 @@ void nimble_riot_init(void)
|
||||
nimble_autoconn_enable();
|
||||
#endif
|
||||
|
||||
#ifdef MODULE_STDIO_NIMBLE
|
||||
extern void stdio_nimble_init(void);
|
||||
/* stdio_nimble_init() needs to be called after nimble stack initialization
|
||||
* and before nimble_autoadv_init() */
|
||||
stdio_nimble_init();
|
||||
#endif
|
||||
|
||||
#ifdef MODULE_NIMBLE_AUTOADV
|
||||
extern void nimble_autoadv_init(void);
|
||||
nimble_autoadv_init();
|
||||
|
69
sys/include/stdio_nimble.h
Normal file
69
sys/include/stdio_nimble.h
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Freie Universität Berlin
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup sys_stdio_nimble STDIO over NimBLE
|
||||
* @ingroup sys
|
||||
*
|
||||
* @experimental This feature is experimental as some use-cases, such as examples/twr_aloha, show
|
||||
* unexpected behaviour.
|
||||
*
|
||||
* @brief Standard input/output backend using NimBLE.
|
||||
*
|
||||
* @note 'stdio_read' blocks until at least one character was read.
|
||||
*
|
||||
* @note 'stdio_write' is considered non-blocking even though it uses a mutex to protect the
|
||||
* write buffer since only 'stdio_write' uses this mutex. Characters will be written
|
||||
* in FIFO mode. Characters that do not fit in the buffer will be dropped.
|
||||
*
|
||||
* @{
|
||||
* @file
|
||||
*
|
||||
* @author Hendrik van Essen <hendrik.ve@fu-berlin.de>
|
||||
*/
|
||||
|
||||
#ifndef STDIO_NIMBLE_H
|
||||
#define STDIO_NIMBLE_H
|
||||
|
||||
#include "stdio_base.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Whether to clear the buffers when establishing a new connection or
|
||||
* not. Defaults to true.
|
||||
*/
|
||||
#ifndef CONFIG_STDIO_NIMBLE_CLEAR_BUFFER_ON_CONNECT
|
||||
#define CONFIG_STDIO_NIMBLE_CLEAR_BUFFER_ON_CONNECT 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Size of buffer for stdin in bytes
|
||||
*
|
||||
* @note Must be a power of two!
|
||||
*/
|
||||
#ifndef CONFIG_STDIO_NIMBLE_STDIN_BUFSIZE
|
||||
#define CONFIG_STDIO_NIMBLE_STDIN_BUFSIZE 1024
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Size of buffer for stdout in bytes
|
||||
*
|
||||
* @note Must be a power of two!
|
||||
*/
|
||||
#ifndef CONFIG_STDIO_NIMBLE_STDOUT_BUFSIZE
|
||||
#define CONFIG_STDIO_NIMBLE_STDOUT_BUFSIZE 2048
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
/** @} */
|
||||
#endif /* STDIO_NIMBLE_H */
|
1
sys/stdio_nimble/Makefile
Normal file
1
sys/stdio_nimble/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
110
sys/stdio_nimble/README.md
Normal file
110
sys/stdio_nimble/README.md
Normal file
@ -0,0 +1,110 @@
|
||||
# STDIO NimBLE
|
||||
|
||||
This module uses NimBLE for stdio. The bluetooth characteristic for
|
||||
stdin is writable and the characteristic for stdout uses the indicate
|
||||
mechanism to publish the system's output to a connected device. Data will be
|
||||
sent out asynchronously via callout functions.
|
||||
|
||||
To use this module, add
|
||||
```
|
||||
USEMODULE += stdio_nimble
|
||||
```
|
||||
to your makefile.
|
||||
|
||||
You can change the default buffer sizes by adding
|
||||
```
|
||||
CFLAGS += -DCONFIG_STDIO_NIMBLE_STDIN_BUFSIZE=1024
|
||||
CFLAGS += -DCONFIG_STDIO_NIMBLE_STDOUT_BUFSIZE=2048
|
||||
```
|
||||
to your makefile. \
|
||||
**NOTE:** These values must be a power of two!
|
||||
|
||||
By default, stdin and stdout buffers are cleared on a connect event. To keep the
|
||||
content add the following to your makefile:
|
||||
```
|
||||
CFLAGS += -DCONFIG_STDIO_NIMBLE_CLEAR_BUFFER_ON_CONNECT=0
|
||||
```
|
||||
|
||||
For automatic bluetooth advertising a module is provided: *nimble_autoadv*.
|
||||
It will take care of enabling advertising on disconnect events and
|
||||
disabling advertising on connect events. It can be enabled by adding
|
||||
```
|
||||
USEMODULE += nimble_autoadv
|
||||
```
|
||||
to your makefile.
|
||||
|
||||
The advertised device name can then optionally be configured with
|
||||
```
|
||||
CFLAGS += -DNIMBLE_AUTOADV_DEVICE_NAME='"Riot OS device"'
|
||||
```
|
||||
Otherwise the device will appear as "*RIOT OS device*".
|
||||
|
||||
## Instructions to connect to the bluetooth shell via ble-serial
|
||||
|
||||
- Configure and compile shell app for nrf52840dongle target in `tests/shell`.\
|
||||
|
||||
Add following to Makefile:
|
||||
```
|
||||
BOARD = nrf52840dongle
|
||||
USEMODULE += nimble_autoadv
|
||||
USEMODULE += stdio_nimble
|
||||
```
|
||||
|
||||
**NOTE:** You can also have a look at `tests/shell_ble`.
|
||||
|
||||
- Flash
|
||||
|
||||
`$ make -C tests/shell -j clean all flash`
|
||||
|
||||
- Install the ble-serial tool
|
||||
|
||||
`$ pip install ble-serial`
|
||||
|
||||
- Scan for your device (device name `Riot OS device`) and note its BLE address.\
|
||||
When you get `ble-scan: command not found` you can also run
|
||||
`python -m ble_serial.scan` instead.
|
||||
```
|
||||
$ ble-scan
|
||||
Started BLE scan
|
||||
|
||||
6BE8174C-A0F8-4479-AFA6-9828372CAFE9 (RSSI=-40): Riot OS device
|
||||
A2862DCB-D382-4C0B-95BF-FA9A961F8D88 (RSSI=-48): Unknown
|
||||
F2C75C08-7DD7-4F43-BEF0-151C92068FE5 (RSSI=-66): Unknown
|
||||
69400683-FBE5-4B45-8CFE-98594076E5F4 (RSSI=-89): Unknown
|
||||
```
|
||||
|
||||
- Discover characteristics (check the one advertised by the gatt server stdin/stdout)
|
||||
```
|
||||
$ ble-scan -d 6BE8174C-A0F8-4479-AFA6-9828372CAFE9
|
||||
Started deep scan of 6BE8174C-A0F8-4479-AFA6-9828372CAFE9
|
||||
|
||||
SERVICE e6d54866-0292-4779-b8f8-c52bbec91e71 (Handle: 10): Unknown
|
||||
CHARACTERISTIC 35f28386-3070-4f3b-ba38-27507e991762 (Handle: 11): Unknown ['indicate']
|
||||
DESCRIPTOR 00002902-0000-1000-8000-00805f9b34fb (Handle: 13): Client Characteristic Configuration
|
||||
CHARACTERISTIC ccdd113f-40d5-4d68-86ac-a728dd82f4aa (Handle: 14): Unknown ['write']
|
||||
|
||||
Completed deep scan of 6BE8174C-A0F8-4479-AFA6-9828372CAFE9
|
||||
```
|
||||
|
||||
- Create a virtual port and mount it on /tmp/dev_riot_ble
|
||||
```
|
||||
$ ble-serial -d 6BE8174C-A0F8-4479-AFA6-9828372CAFE9 -p /tmp/dev_riot_ble --write-uuid ccdd113f-40d5-4d68-86ac-a728dd82f4aa --read-uuid 35f28386-3070-4f3b-ba38-27507e991762
|
||||
17:44:18.765 | INFO | linux_pty.py: Slave created on /tmp/dev_riot_ble -> /dev/ttys006
|
||||
17:44:18.766 | INFO | ble_interface.py: Receiver set up
|
||||
17:44:18.766 | INFO | ble_interface.py: Trying to connect with 6BE8174C-A0F8-4479-AFA6-9828372CAFE9
|
||||
17:44:19.861 | INFO | ble_interface.py: Device 6BE8174C-A0F8-4479-AFA6-9828372CAFE9 connected
|
||||
17:44:19.862 | INFO | ble_interface.py: Found write characteristic ccdd113f-40d5-4d68-86ac-a728dd82f4aa (H. 14)
|
||||
17:44:19.862 | INFO | ble_interface.py: Found notify characteristic 35f28386-3070-4f3b-ba38-27507e991762 (H. 11)
|
||||
17:44:19.883 | INFO | main.py: Running main loop!
|
||||
```
|
||||
|
||||
- Open the virtual com port (the port name is from the logs in previous steps)
|
||||
```
|
||||
$ picocom -q -b 115200 --imap lfcrlf /tmp/dev_riot_ble
|
||||
ps
|
||||
pid | state Q | pri
|
||||
1 | running Q | 7
|
||||
2 | bl anyfl _ | 5
|
||||
3 | bl anyfl _ | 0
|
||||
>
|
||||
```
|
387
sys/stdio_nimble/stdio_nimble.c
Normal file
387
sys/stdio_nimble/stdio_nimble.c
Normal file
@ -0,0 +1,387 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Freie Universität Berlin
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup sys
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief STDIO over NimBLE implementation
|
||||
*
|
||||
*
|
||||
* @author Hendrik van Essen <hendrik.ve@fu-berlin.de>
|
||||
* @author Francisco Molina <francois-xavier.molina@inria.fr>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "nimble_riot.h"
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "net/bluetil/ad.h"
|
||||
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/util/util.h"
|
||||
#include "host/ble_gatt.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "services/gatt/ble_svc_gatt.h"
|
||||
|
||||
#if IS_USED(MODULE_STDIO_NIMBLE_DEBUG)
|
||||
#include <stdarg.h>
|
||||
#include "stdio_uart.h"
|
||||
#include "periph/uart.h"
|
||||
#endif /* IS_USED(MODULE_STDIO_NIMBLE_DEBUG) */
|
||||
|
||||
#if IS_USED(MODULE_VFS)
|
||||
#include "vfs.h"
|
||||
#endif
|
||||
|
||||
#include "tsrb.h"
|
||||
#include "isrpipe.h"
|
||||
#include "stdio_nimble.h"
|
||||
|
||||
#define NIMBLE_MAX_PAYLOAD MYNEWT_VAL(BLE_LL_MAX_PKT_SIZE)
|
||||
|
||||
/* Nimble uses ZTIMER_MSEC => 1 tick equals 1 ms */
|
||||
#define CALLOUT_TICKS_MS 1
|
||||
|
||||
enum {
|
||||
STDIO_NIMBLE_DISCONNECTED,
|
||||
STDIO_NIMBLE_CONNECTED,
|
||||
STDIO_NIMBLE_SUBSCRIBED,
|
||||
STDIO_NIMBLE_SENDING,
|
||||
};
|
||||
|
||||
/* isrpipe for stdin */
|
||||
static uint8_t _isrpipe_stdin_mem[CONFIG_STDIO_NIMBLE_STDIN_BUFSIZE];
|
||||
static isrpipe_t _isrpipe_stdin = ISRPIPE_INIT(_isrpipe_stdin_mem);
|
||||
|
||||
/* tsrb for stdout */
|
||||
static uint8_t _tsrb_stdout_mem[CONFIG_STDIO_NIMBLE_STDOUT_BUFSIZE];
|
||||
static tsrb_t _tsrb_stdout = TSRB_INIT(_tsrb_stdout_mem);
|
||||
|
||||
/* intermediate buffer to transfer data between tsrb and nimble functions,
|
||||
* which are all based on os_mbuf implementation */
|
||||
static uint8_t _stdin_read_buf[NIMBLE_MAX_PAYLOAD];
|
||||
static uint8_t _stdout_write_buf[NIMBLE_MAX_PAYLOAD];
|
||||
|
||||
/* information about bluetooth connection */
|
||||
static uint16_t _conn_handle;
|
||||
static uint16_t _val_handle_stdout;
|
||||
static volatile uint8_t _status = STDIO_NIMBLE_DISCONNECTED;
|
||||
|
||||
/* nimble related structs */
|
||||
static struct ble_npl_callout _send_stdout_callout;
|
||||
static struct ble_gap_event_listener _gap_event_listener;
|
||||
|
||||
#if IS_USED(MODULE_STDIO_NIMBLE_DEBUG)
|
||||
#define DEBUG_PRINTF_BUFSIZE 512
|
||||
#define PREFIX_STDIN "\nSTDIN: "
|
||||
#define PREFIX_STDOUT "STDOUT: "
|
||||
|
||||
static char _debug_printf_buf[DEBUG_PRINTF_BUFSIZE];
|
||||
#endif /* IS_USED(MODULE_STDIO_NIMBLE_DEBUG) */
|
||||
|
||||
static int _debug_printf(const char *format, ...)
|
||||
{
|
||||
#if IS_USED(MODULE_STDIO_NIMBLE_DEBUG)
|
||||
unsigned state = irq_disable();
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
int rc = vsnprintf(_debug_printf_buf, DEBUG_PRINTF_BUFSIZE, format, va);
|
||||
va_end(va);
|
||||
uart_write(STDIO_UART_DEV, (const uint8_t *)_debug_printf_buf, rc);
|
||||
irq_restore(state);
|
||||
|
||||
return rc;
|
||||
#else
|
||||
(void)format;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UUID for stdio service (value: e6d54866-0292-4779-b8f8-c52bbec91e71)
|
||||
*/
|
||||
static const ble_uuid128_t gatt_svr_svc_stdio_uuid
|
||||
= BLE_UUID128_INIT(0x71, 0x1e, 0xc9, 0xbe, 0x2b, 0xc5, 0xf8, 0xb8,
|
||||
0x79, 0x47, 0x92, 0x02, 0x66, 0x48, 0xd5, 0xe6);
|
||||
|
||||
/**
|
||||
* @brief UUID for stdout characteristic (value: 35f28386-3070-4f3b-ba38-27507e991762)
|
||||
*/
|
||||
static const ble_uuid128_t gatt_svr_chr_stdout_uuid
|
||||
= BLE_UUID128_INIT(0x62, 0x17, 0x99, 0x7e, 0x50, 0x27, 0x38, 0xba,
|
||||
0x3b, 0x4f, 0x70, 0x30, 0x86, 0x83, 0xf2, 0x35);
|
||||
|
||||
/**
|
||||
* @brief UUID for stdin characteristic (value: ccdd113f-40d5-4d68-86ac-a728dd82f4aa)
|
||||
*/
|
||||
static const ble_uuid128_t gatt_svr_chr_stdin_uuid
|
||||
= BLE_UUID128_INIT(0xaa, 0xf4, 0x82, 0xdd, 0x28, 0xa7, 0xac, 0x86,
|
||||
0x68, 0x4d, 0xd5, 0x40, 0x3f, 0x11, 0xdd, 0xcc);
|
||||
|
||||
/**
|
||||
* @brief Nimble access callback for stdin characteristic
|
||||
*/
|
||||
static int gatt_svr_chr_access_stdin(
|
||||
uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
|
||||
/**
|
||||
* @brief Dummy access callback, because nimble requires one
|
||||
*/
|
||||
static int gatt_svr_chr_access_noop(
|
||||
uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
|
||||
(void)conn_handle;
|
||||
(void)attr_handle;
|
||||
(void)ctxt;
|
||||
(void)arg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Struct to define the stdio bluetooth service with its characteristics
|
||||
*/
|
||||
static const struct ble_gatt_svc_def _gatt_svr_svcs[] =
|
||||
{
|
||||
/*
|
||||
* access_cb defines a callback for read and write access events on
|
||||
* given characteristics
|
||||
*/
|
||||
{
|
||||
/* Service: stdio */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t *)&gatt_svr_svc_stdio_uuid.u,
|
||||
.characteristics = (struct ble_gatt_chr_def[]) { {
|
||||
/* Characteristic: stdout */
|
||||
.uuid = (ble_uuid_t *)&gatt_svr_chr_stdout_uuid.u,
|
||||
.access_cb = gatt_svr_chr_access_noop,
|
||||
.val_handle = &_val_handle_stdout,
|
||||
.flags = BLE_GATT_CHR_F_INDICATE,
|
||||
}, {
|
||||
/* Characteristic: stdin */
|
||||
.uuid = (ble_uuid_t *)&gatt_svr_chr_stdin_uuid.u,
|
||||
.access_cb = gatt_svr_chr_access_stdin,
|
||||
.flags = BLE_GATT_CHR_F_WRITE,
|
||||
}, {
|
||||
0, /* No more characteristics in this service */
|
||||
}, }
|
||||
},
|
||||
{
|
||||
0, /* No more services */
|
||||
},
|
||||
};
|
||||
|
||||
static void _purge_buffer(void)
|
||||
{
|
||||
tsrb_clear(&_isrpipe_stdin.tsrb);
|
||||
|
||||
#if IS_USED(MODULE_SHELL)
|
||||
/* send Ctrl-C to the shell to reset the input */
|
||||
isrpipe_write_one(&_isrpipe_stdin, '\x03');
|
||||
#endif
|
||||
|
||||
tsrb_clear(&_tsrb_stdout);
|
||||
}
|
||||
|
||||
static void _send_stdout(struct ble_npl_event *ev)
|
||||
{
|
||||
(void)ev;
|
||||
|
||||
/* rearm callout */
|
||||
ble_npl_callout_reset(&_send_stdout_callout, CALLOUT_TICKS_MS);
|
||||
|
||||
if (_status == STDIO_NIMBLE_SUBSCRIBED) {
|
||||
_status = STDIO_NIMBLE_SENDING;
|
||||
int to_send = tsrb_peek(&_tsrb_stdout, _stdout_write_buf, NIMBLE_MAX_PAYLOAD);
|
||||
|
||||
if (to_send > 0) {
|
||||
struct os_mbuf *om = ble_hs_mbuf_from_flat(_stdout_write_buf, to_send);
|
||||
if (om != NULL) {
|
||||
int rc = ble_gattc_indicate_custom(_conn_handle, _val_handle_stdout, om);
|
||||
if (rc == 0) {
|
||||
/* bytes were successfully sent, so drop them from the buffer */
|
||||
tsrb_drop(&_tsrb_stdout, to_send);
|
||||
_debug_printf("%d bytes sent successfully\n", to_send);
|
||||
}
|
||||
else {
|
||||
_status = STDIO_NIMBLE_SUBSCRIBED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_status = STDIO_NIMBLE_SUBSCRIBED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_status = STDIO_NIMBLE_SUBSCRIBED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int _gap_event_cb(struct ble_gap_event *event, void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
switch (event->type) {
|
||||
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
_debug_printf("BLE_GAP_EVENT_CONNECT\n");
|
||||
if (event->connect.status == 0) {
|
||||
_status = STDIO_NIMBLE_CONNECTED;
|
||||
if (CONFIG_STDIO_NIMBLE_CLEAR_BUFFER_ON_CONNECT) {
|
||||
_purge_buffer();
|
||||
}
|
||||
_conn_handle = event->connect.conn_handle;
|
||||
}
|
||||
else {
|
||||
_status = STDIO_NIMBLE_DISCONNECTED;
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
_debug_printf("BLE_GAP_EVENT_DISCONNECT\n");
|
||||
_status = STDIO_NIMBLE_DISCONNECTED;
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_SUBSCRIBE:
|
||||
_debug_printf("BLE_GAP_EVENT_SUBSCRIBE\n");
|
||||
if (event->subscribe.attr_handle == _val_handle_stdout) {
|
||||
if (event->subscribe.cur_indicate == 1) {
|
||||
_status = STDIO_NIMBLE_SUBSCRIBED;
|
||||
}
|
||||
else {
|
||||
_status = STDIO_NIMBLE_CONNECTED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_NOTIFY_TX:
|
||||
_debug_printf("BLE_GAP_EVENT_NOTIFY_TX\n");
|
||||
if (event->notify_tx.indication == 1) {
|
||||
if (event->notify_tx.status == BLE_HS_EDONE) {
|
||||
_status = STDIO_NIMBLE_SUBSCRIBED;
|
||||
}
|
||||
else if (event->notify_tx.status != 0) {
|
||||
_status = STDIO_NIMBLE_SUBSCRIBED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_MTU:
|
||||
_debug_printf("BLE_GAP_EVENT_MTU: mtu = %d\n", event->mtu.value);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gatt_svr_chr_access_stdin(
|
||||
uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
(void)conn_handle;
|
||||
(void)attr_handle;
|
||||
(void)arg;
|
||||
|
||||
uint16_t om_len = OS_MBUF_PKTLEN(ctxt->om);
|
||||
|
||||
/* read sent data */
|
||||
int rc = ble_hs_mbuf_to_flat(ctxt->om, _stdin_read_buf, sizeof(_stdin_read_buf), &om_len);
|
||||
|
||||
isrpipe_write(&_isrpipe_stdin, _stdin_read_buf, om_len);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void stdio_init(void)
|
||||
{
|
||||
#if IS_USED(MODULE_VFS)
|
||||
vfs_bind_stdio();
|
||||
#endif
|
||||
|
||||
#if IS_USED(MODULE_STDIO_NIMBLE_DEBUG)
|
||||
uart_init(STDIO_UART_DEV, STDIO_UART_BAUDRATE, NULL, NULL);
|
||||
#endif
|
||||
|
||||
ble_npl_callout_init(&_send_stdout_callout, nimble_port_get_dflt_eventq(),
|
||||
_send_stdout, NULL);
|
||||
}
|
||||
|
||||
#if IS_USED(MODULE_STDIO_AVAILABLE)
|
||||
int stdio_available(void)
|
||||
{
|
||||
return tsrb_avail(&_isrpipe_stdin.tsrb);
|
||||
}
|
||||
#endif
|
||||
|
||||
ssize_t stdio_read(void *buffer, size_t count)
|
||||
{
|
||||
/* blocks until at least one character was read */
|
||||
ssize_t res = isrpipe_read(&_isrpipe_stdin, buffer, count);
|
||||
|
||||
#if IS_USED(MODULE_STDIO_NIMBLE_DEBUG)
|
||||
unsigned state = irq_disable();
|
||||
uart_write(STDIO_UART_DEV, (const uint8_t *)PREFIX_STDIN, strlen(PREFIX_STDIN));
|
||||
uart_write(STDIO_UART_DEV, (const uint8_t *)buffer, res);
|
||||
uart_write(STDIO_UART_DEV, (const uint8_t *)"\n", 1);
|
||||
irq_restore(state);
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ssize_t stdio_write(const void *buffer, size_t len)
|
||||
{
|
||||
unsigned state = irq_disable();
|
||||
|
||||
#if IS_USED(MODULE_STDIO_NIMBLE_DEBUG)
|
||||
uart_write(STDIO_UART_DEV, (const uint8_t *)PREFIX_STDOUT, strlen(PREFIX_STDOUT));
|
||||
uart_write(STDIO_UART_DEV, (const uint8_t *)buffer, len);
|
||||
uart_write(STDIO_UART_DEV, (const uint8_t *)"\n", 1);
|
||||
#endif
|
||||
|
||||
irq_restore(state);
|
||||
|
||||
unsigned int consumed = tsrb_add(&_tsrb_stdout, buffer, len);
|
||||
|
||||
if (!ble_npl_callout_is_active(&_send_stdout_callout)) {
|
||||
/* bootstrap callout */
|
||||
ble_npl_callout_reset(&_send_stdout_callout, CALLOUT_TICKS_MS);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/* is going to be called by auto_init */
|
||||
void stdio_nimble_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
/* verify and add our custom services */
|
||||
rc = ble_gatts_count_cfg(_gatt_svr_svcs);
|
||||
assert(rc == 0);
|
||||
rc = ble_gatts_add_svcs(_gatt_svr_svcs);
|
||||
assert(rc == 0);
|
||||
|
||||
/* reload the GATT server to link our added services */
|
||||
ble_gatts_start();
|
||||
|
||||
/* register gap event listener */
|
||||
rc = ble_gap_event_listener_register(&_gap_event_listener, _gap_event_cb, NULL);
|
||||
assert(rc == 0);
|
||||
|
||||
/* fix compilation error when using DEVELHELP=0 */
|
||||
(void)rc;
|
||||
}
|
@ -32,6 +32,8 @@ EXPECTED_PS = (
|
||||
RIOT_TERMINAL = os.environ.get('RIOT_TERMINAL')
|
||||
CLEANTERMS = {"socat"}
|
||||
|
||||
TESTRUNNER_SHELL_SKIP_REBOOT = bool(int(os.environ.get('TESTRUNNER_SHELL_SKIP_REBOOT') or 0))
|
||||
|
||||
# In native we are directly executing the binary (no terminal program). We must
|
||||
# therefore use Ctrl-V (DLE or "data link escape") before Ctrl-C to send a
|
||||
# literal ETX instead of SIGINT.
|
||||
@ -220,6 +222,10 @@ def testfunc(child):
|
||||
|
||||
# loop other defined commands and expected output
|
||||
for cmd, expected in CMDS:
|
||||
|
||||
if cmd == "reboot" and TESTRUNNER_SHELL_SKIP_REBOOT:
|
||||
continue
|
||||
|
||||
check_cmd(child, cmd, expected)
|
||||
|
||||
if RIOT_TERMINAL in CLEANTERMS:
|
||||
|
42
tests/shell_ble/Makefile
Normal file
42
tests/shell_ble/Makefile
Normal file
@ -0,0 +1,42 @@
|
||||
DEVELHELP = 0
|
||||
BOARD ?= nrf52dk
|
||||
include ../Makefile.tests_common
|
||||
|
||||
USEMODULE += app_metadata
|
||||
USEMODULE += shell
|
||||
USEMODULE += shell_commands
|
||||
USEMODULE += ps
|
||||
|
||||
USEMODULE += stdio_nimble stdio_nimble_debug
|
||||
USEMODULE += nimble_autoadv
|
||||
CFLAGS += -DNIMBLE_AUTOADV_DEVICE_NAME='"tests/shell_ble"'
|
||||
|
||||
TESTRUNNER_SHELL_SKIP_REBOOT = 1
|
||||
TESTRUNNER_RESET_BOARD_ON_STARTUP = 0
|
||||
|
||||
ifneq (,$(filter term,$(MAKECMDGOALS)))
|
||||
# for z1, socat doesn't work (unknown reason)
|
||||
ifeq (z1, $(BOARD))
|
||||
RIOT_TERMINAL ?= pyterm
|
||||
endif
|
||||
|
||||
# Use a terminal that does not introduce extra characters into the stream.
|
||||
RIOT_TERMINAL ?= socat
|
||||
else ifneq (,$(filter test,$(MAKECMDGOALS)))
|
||||
# Use the virtual serial port created by ble-serial
|
||||
RIOT_TERMINAL = picocom
|
||||
endif
|
||||
|
||||
# The test requires some setup so it cannot currently be run
|
||||
TEST_ON_CI_BLACKLIST += all
|
||||
|
||||
APP_SHELL_FMT ?= NONE
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
|
||||
# the test script skips tests if socat is not used
|
||||
$(call target-export-variables,$(RIOT_TERMINAL),RIOT_TERMINAL)
|
||||
|
||||
# a reboot or a reset would disconnect the device from bluetooth and break the test
|
||||
$(call target-export-variables,$(TESTRUNNER_SHELL_SKIP_REBOOT),TESTRUNNER_SHELL_SKIP_REBOOT)
|
||||
$(call target-export-variables,$(TESTRUNNER_RESET_BOARD_ON_STARTUP),TESTRUNNER_RESET_BOARD_ON_STARTUP)
|
22
tests/shell_ble/README.md
Normal file
22
tests/shell_ble/README.md
Normal file
@ -0,0 +1,22 @@
|
||||
This is basically the same tests as for the normal shell, but here we are
|
||||
testing via blueooth instead of UART. You have to set up a virtual serial port
|
||||
manually.
|
||||
|
||||
For instructions on how to open a virtual serial port to your bluetooth device
|
||||
see `sys/stdio_nimble/README`.
|
||||
|
||||
**Note:** `make term` and `make test-with-config` will open two different types of terminals.
|
||||
- When calling `make term` then a terminal will communicate with the board
|
||||
via UART. Due to the nature of `stdio_nimble` the board won't respond to input
|
||||
coming from here)
|
||||
- When calling `make test-with-config` then picocom will communicate with the board via the
|
||||
given virtual serial port
|
||||
|
||||
So a procedure to run this test could be:
|
||||
0. Make sure that the current test application instance is fresh and no test was
|
||||
run on it before. Otherwise your test might fail, because the test case
|
||||
`check_control_d` only works once per run.
|
||||
1. Execute `make flash term`
|
||||
2. Open a virtual serial port with `ble-serial` and note the virtual serial port
|
||||
that was created (search for `Slave created on /tmp/dev_riot_ble -> /dev/pts/25`)
|
||||
3. Execute `PORT=/dev/pts/25 make test-with-config`
|
1
tests/shell_ble/main.c
Symbolic link
1
tests/shell_ble/main.c
Symbolic link
@ -0,0 +1 @@
|
||||
../shell/main.c
|
1
tests/shell_ble/tests-with-config/01-run.py
Symbolic link
1
tests/shell_ble/tests-with-config/01-run.py
Symbolic link
@ -0,0 +1 @@
|
||||
../../shell/tests/01-run.py
|
Loading…
Reference in New Issue
Block a user