1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/stdio_nimble/stdio_nimble.c
2024-02-09 15:24:22 +01:00

355 lines
10 KiB
C

/*
* 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) */
#include "stdio_base.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,
};
/* 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) */
#if IS_USED(MODULE_STDIO_NIMBLE_DEBUG)
#define _debug_printf(...) \
do { \
unsigned state = irq_disable(); \
int rc = snprintf(_debug_printf_buf, DEBUG_PRINTF_BUFSIZE, __VA_ARGS__); \
uart_write(STDIO_UART_DEV, (const uint8_t *)_debug_printf_buf, rc); \
irq_restore(state); \
} while(0)
#else
#define _debug_printf(...) (void)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(&stdin_isrpipe.tsrb);
#if IS_USED(MODULE_SHELL)
/* send Ctrl-C to the shell to reset the input */
isrpipe_write_one(&stdin_isrpipe, '\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 handle: %d\n", event->connect.conn_handle);
if (event->connect.status == 0 && _conn_handle == 0) {
_status = STDIO_NIMBLE_CONNECTED;
if (CONFIG_STDIO_NIMBLE_CLEAR_BUFFER_ON_CONNECT) {
_purge_buffer();
}
}
else if (event->connect.conn_handle == _conn_handle) {
_conn_handle = 0;
_status = STDIO_NIMBLE_DISCONNECTED;
}
break;
case BLE_GAP_EVENT_DISCONNECT:
_debug_printf("BLE_GAP_EVENT_DISCONNECT %d\n", event->disconnect.conn.conn_handle);
if (event->disconnect.conn.conn_handle == _conn_handle) {
_status = STDIO_NIMBLE_DISCONNECTED;
_conn_handle = 0;
}
break;
case BLE_GAP_EVENT_SUBSCRIBE:
_debug_printf("BLE_GAP_EVENT_SUBSCRIBE %d\n", event->subscribe.conn_handle);
if (event->subscribe.attr_handle == _val_handle_stdout) {
if (event->subscribe.cur_indicate == 1) {
_status = STDIO_NIMBLE_SUBSCRIBED;
_conn_handle = event->subscribe.conn_handle;
}
else {
_status = STDIO_NIMBLE_CONNECTED;
_conn_handle = 0;
}
}
break;
case BLE_GAP_EVENT_NOTIFY_TX:
_debug_printf("BLE_GAP_EVENT_NOTIFY_TX %d\n", event->notify_tx.conn_handle);
if (event->notify_tx.indication == 1 && (event->notify_tx.conn_handle == _conn_handle)) {
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(&stdin_isrpipe, _stdin_read_buf, om_len);
return rc;
}
static void _init(void)
{
#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);
}
static ssize_t _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 (_status == STDIO_NIMBLE_SUBSCRIBED || _status == STDIO_NIMBLE_SENDING) {
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;
}
STDIO_PROVIDER(STDIO_NIMBLE, _init, NULL, _write)