From 49bd85d00ac6abb0cfb8c6e5fbd493155d55e752 Mon Sep 17 00:00:00 2001 From: Hauke Petersen <hauke.petersen@fu-berlin.de> Date: Thu, 5 Apr 2018 11:10:40 +0200 Subject: [PATCH] sys/net: added Skald Skald is a very small and simple, TX-only BLE stack that supports sending advertisements only. It is useful for building all kinds of BLE beacons with very minimal memory footprints. --- Makefile.dep | 9 ++ makefiles/pseudomodules.inc.mk | 5 + sys/Makefile | 3 + sys/include/net/skald.h | 124 +++++++++++++++++++++++ sys/include/net/skald/eddystone.h | 83 ++++++++++++++++ sys/include/net/skald/ibeacon.h | 54 ++++++++++ sys/net/skald/Makefile | 11 +++ sys/net/skald/skald.c | 158 ++++++++++++++++++++++++++++++ sys/net/skald/skald_eddystone.c | 109 +++++++++++++++++++++ sys/net/skald/skald_ibeacon.c | 63 ++++++++++++ 10 files changed, 619 insertions(+) create mode 100644 sys/include/net/skald.h create mode 100644 sys/include/net/skald/eddystone.h create mode 100644 sys/include/net/skald/ibeacon.h create mode 100644 sys/net/skald/Makefile create mode 100644 sys/net/skald/skald.c create mode 100644 sys/net/skald/skald_eddystone.c create mode 100644 sys/net/skald/skald_ibeacon.c diff --git a/Makefile.dep b/Makefile.dep index c0b24c5f55..0e999f9411 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -698,6 +698,15 @@ ifneq (,$(filter benchmark,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter skald_%,$(USEMODULE))) + USEMODULE += skald +endif + +ifneq (,$(filter skald,$(USEMODULE))) + FEATURES_REQUIRED += radio_ble + USEMODULE += xtimer + USEMODULE += random +endif # always select gpio (until explicit dependencies are sorted out) FEATURES_OPTIONAL += periph_gpio diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 4fec53670a..15b562684e 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -117,3 +117,8 @@ PSEUDOMODULES += stm32_periph_% # declare periph submodules as pseudomodules, but exclude periph_common PSEUDOMODULES += periph_% NO_PSEUDOMODULES += periph_common + +# Submodules and auto-init code provided by Skald +PSEUDOMODULES += auto_init_skald +PSEUDOMODULES += skald_ibeacon +PSEUDOMODULES += skald_eddystone diff --git a/sys/Makefile b/sys/Makefile index 76701bd74d..cc6d2ab074 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -124,6 +124,9 @@ endif ifneq (,$(filter nanocoap,$(USEMODULE))) DIRS += net/application_layer/nanocoap endif +ifneq (,$(filter skald,$(USEMODULE))) + DIRS += net/skald +endif DIRS += $(dir $(wildcard $(addsuffix /Makefile, $(USEMODULE)))) diff --git a/sys/include/net/skald.h b/sys/include/net/skald.h new file mode 100644 index 0000000000..4583341c53 --- /dev/null +++ b/sys/include/net/skald.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018 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 net_skald Skald, who advertises to the world + * @ingroup net + * @brief Skald, a minimalistic BLE advertising stack + * + * # About + * + * Skald is a very minimalistic BLE implementation, implementing the + * `broadcaster` role only. With this focus, the stack allows for setting up + * different kind of beacons using an extremely low memory footprint. + * + * # Design Decisions and Limitations + * - support for local addresses only (using `luid` to generate them) + * - advertising interval is configured during compile time, override by setting + * `CFLAGS+=-DSKALD_INTERVAL=xxx` + * - advertising channels are configured during compile time, override by + * setting `CFLAGS+=-DSKALD_ADV_CHAN={37,39}` + * + * # Implementation state + * Supported: + * - advertising of custom GAP payloads + * - iBeacon (full support) + * - Eddystone (partly supported) + * + * Limitations: + * - currently Skald supports random static addresses only (generated using + * the `luid` module) + * + * @{ + * @file + * @brief Skald's basic interface + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + */ + +#ifndef NET_SKALD_H +#define NET_SKALD_H + +#include <stdint.h> + +#include "xtimer.h" +#include "net/ble.h" +#include "net/netdev/ble.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Static advertising interval + */ +#ifndef SKALD_INTERVAL +#define SKALD_INTERVAL (1 * US_PER_SEC) +#endif + +/** + * @brief Static list of used advertising channels + */ +#ifndef SKALD_ADV_CHAN +#define SKALD_ADV_CHAN { 37, 38, 39 } +#endif + +/** + * @brief UUID representation format used by Skald + */ +typedef struct { + uint8_t u8[16]; /**< UUID with byte-wise access */ +} skald_uuid_t; + +/** + * @brief Advertising context holding the advertising data and state + */ +typedef struct { + netdev_ble_pkt_t pkt; /**< packet holding the advertisement (GAP) data */ + xtimer_t timer; /**< timer for scheduling advertising events */ + uint32_t last; /**< last timer trigger (for offset compensation) */ + uint8_t cur_chan; /**< keep track of advertising channels */ +} skald_ctx_t; + +/** + * @brief Initialize Skald and the underlying radio + */ +void skald_init(void); + +/** + * @brief Start advertising the given packet + * + * The packet will be send out each advertising interval (see SKALD_INTERVAL) on + * each of the defined advertising channels (see SKALD_ADV_CHAN). + * + * @param[in,out] ctx start advertising this context + */ +void skald_adv_start(skald_ctx_t *ctx); + +/** + * @brief Stop the ongoing advertisement + * + * @param[in,out] ctx stop advertising this context + */ +void skald_adv_stop(skald_ctx_t *ctx); + +/** + * @brief Generate a random public address + * + * @note @p buf must be able to hold BLE_ADDR_LEN (6) bytes + * + * @param[out] buf the generated address is written to this buffer + */ +void skald_generate_random_addr(uint8_t *buf); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_SKALD_H */ +/** @} */ diff --git a/sys/include/net/skald/eddystone.h b/sys/include/net/skald/eddystone.h new file mode 100644 index 0000000000..9c8ddd6365 --- /dev/null +++ b/sys/include/net/skald/eddystone.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 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 net_skald_eddystone Skald meets Eddy + * @ingroup net_skald + * @brief Skald's Eddystone implementation + * + * # About + * This module allows for creation and advertisement of Eddystone beacons (see + * https://github.com/google/eddystone). + * + * + * # Implementation state + * supported: + * - Eddystone-UID + * - Eddystone-URL + * + * not (yet) supported: + * - Eddystone-TLM + * - Eddystone-EID + * + * @{ + * @file + * @brief Skald's basic interface + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + */ + +#ifndef NET_SKALD_EDDYSTONE_H +#define NET_SKALD_EDDYSTONE_H + +#include "net/eddystone.h" +#include "net/skald.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Unique and opaque 16-byte beacon id format used by Eddystone + */ +typedef struct __attribute__((packed)) { + uint8_t namespace[EDDYSTONE_NAMESPACE_LEN]; /**< 10-byte namespace */ + uint8_t instance[EDDYSTONE_INSTANCE_LEN]; /**< 6-byte instance */ +} skald_eddystone_uid_t; + +/** + * @brief Advertise Eddystone-UID data + * + * @see https://github.com/google/eddystone/tree/master/eddystone-uid + * + * @param[out] ctx advertising context + * @param[in] uid UID to advertise + * @param[in] tx_pwr calibrated TX power to be advertised by the beacon + */ +void skald_eddystone_uid_adv(skald_ctx_t *ctx, + const skald_eddystone_uid_t *uid, uint8_t tx_pwr); + +/** + * @brief Advertise Eddystone-URL data + * + * @see https://github.com/google/eddystone/tree/master/eddystone-url + * + * @param[out] ctx advertising context + * @param[in] scheme encoded URL scheme prefix + * @param[in] url (short) url as \0 terminated string + * @param[in] tx_pwr calibrated TX power to be advertised by the beacon + */ +void skald_eddystone_url_adv(skald_ctx_t *ctx, + uint8_t scheme, const char *url, uint8_t tx_pwr); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_SKALD_EDDYSTONE_H */ +/** @} */ diff --git a/sys/include/net/skald/ibeacon.h b/sys/include/net/skald/ibeacon.h new file mode 100644 index 0000000000..0f87430068 --- /dev/null +++ b/sys/include/net/skald/ibeacon.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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 net_skald_ibeacon Skald about iBeacon + * @ingroup net_skald + * @brief Skald's simple iBeacon abstraction + * + * # About + * This Skald module supports the creation and advertisement of BLE iBeacons as + * defined by Apple (see https://developer.apple.com/ibeacon/). + * + * # Implementation state + * - all known iBeacon properties are supported + * + * @{ + * @file + * @brief Skald's basic interface + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + */ + +#ifndef NET_SKALD_IBEACON_H +#define NET_SKALD_IBEACON_H + +#include "net/skald.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Configure the IBeacon payload and start advertising + * + * @param[out] ctx advertising context + * @param[in] uuid UUID advertised by the iBeacon + * @param[in] major the iBeacon's major number + * @param[in] minor the iBeacon's minor number + * @param[in] txpower calibrated TX power to be advertised by the beacon + */ +void skald_ibeacon_advertise(skald_ctx_t *ctx, const skald_uuid_t *uuid, + uint16_t major, uint16_t minor, uint8_t txpower); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_SKALD_IBEACON_H */ +/** @} */ diff --git a/sys/net/skald/Makefile b/sys/net/skald/Makefile new file mode 100644 index 0000000000..c835b4d8dd --- /dev/null +++ b/sys/net/skald/Makefile @@ -0,0 +1,11 @@ +SRC = skald.c + +ifneq (,$(filter skald_ibeacon,$(USEMODULE))) + SRC += skald_ibeacon.c +endif + +ifneq (,$(filter skald_eddystone,$(USEMODULE))) + SRC += skald_eddystone.c +endif + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/skald/skald.c b/sys/net/skald/skald.c new file mode 100644 index 0000000000..858f8b03a5 --- /dev/null +++ b/sys/net/skald/skald.c @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2018 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 net_skald + * @{ + * + * @file + * @brief Skald's link layer implementation + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <stdint.h> + +#include "assert.h" +#include "random.h" +#include "luid.h" + +#include "net/netdev/ble.h" +#include "net/skald.h" + +/* include fitting radio driver */ +#if defined(MODULE_NRFBLE) +#include "nrfble.h" +/* add other BLE radio drivers once implemented - and potentially move to + * auto-init at some point */ +#else +#error "[skald] error: unable to find any netdev-ble capable radio" +#endif + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define JITTER_MIN (0U) /* 0ms */ +#define JITTER_MAX (10000U) /* 10ms */ + +#define ADV_CHAN_NUMOF sizeof(_adv_chan) +#define ADV_AA (0x8e89bed6) /* access address */ +#define ADV_CRC (0x00555555) /* CRC initializer */ + +static const uint8_t _adv_chan[] = SKALD_ADV_CHAN; + +static netdev_ble_ctx_t _ble_ctx = { + .aa.u32 = ADV_AA, + .crc = ADV_CRC, +}; + +static netdev_t *_radio; + +static void _stop_radio(void) +{ + netdev_ble_stop(_radio); + _radio->context = NULL; +} + +static void _sched_next(skald_ctx_t *ctx) +{ + ctx->last += SKALD_INTERVAL; + /* schedule next advertising event, adding a random jitter between + * 0ms and 10ms (see spec v5.0-vol6-b-4.4.2.2.1) */ + ctx->last += random_uint32_range(JITTER_MIN, JITTER_MAX); + /* compensate the time passed since the timer triggered last by using the + * current value of the timer */ + xtimer_set(&ctx->timer, (ctx->last - xtimer_now_usec())); +} + +static void _on_adv_evt(void *arg) +{ + skald_ctx_t *ctx = (skald_ctx_t *)arg; + + /* advertise on the next adv channel - or skip this event if the radio is + * busy */ + if ((ctx->cur_chan < ADV_CHAN_NUMOF) && (_radio->context == NULL)) { + _radio->context = ctx; + _ble_ctx.chan = _adv_chan[ctx->cur_chan]; + netdev_ble_set_ctx(_radio, &_ble_ctx); + netdev_ble_send(_radio, &ctx->pkt); + ++ctx->cur_chan; + } + else { + ctx->cur_chan = 0; + _sched_next(ctx); + } +} + +static void _on_radio_evt(netdev_t *netdev, netdev_event_t event) +{ + (void)netdev; + + if (event == NETDEV_EVENT_TX_COMPLETE) { + skald_ctx_t *ctx = _radio->context; + _stop_radio(); + xtimer_set(&ctx->timer, 150); + } +} + +void skald_init(void) +{ + assert(dev); + + /* setup and a fitting radio driver - potentially move to auto-init at some + * point */ +#if defined(MODULE_NRFBLE) + _radio = nrfble_setup(); +#endif + + _radio->event_callback = _on_radio_evt; + _radio->driver->init(_radio); +} + +void skald_adv_start(skald_ctx_t *ctx) +{ + assert(ctx); + + /* make sure the given context is not advertising at the moment */ + skald_adv_stop(ctx); + + /* initialize advertising context */ + ctx->timer.callback = _on_adv_evt; + ctx->timer.arg = ctx; + ctx->last = xtimer_now_usec(); + ctx->cur_chan = 0; + ctx->pkt.flags = (BLE_ADV_NONCON_IND | BLE_LL_FLAG_TXADD); + + /* start advertising */ + _sched_next(ctx); +} + +void skald_adv_stop(skald_ctx_t *ctx) +{ + assert(ctx); + + xtimer_remove(&ctx->timer); + if (_radio->context == (void *)ctx) { + _stop_radio(); + } +} + +void skald_generate_random_addr(uint8_t *buf) +{ + assert(buf); + + luid_get(buf, BLE_ADDR_LEN); + /* swap byte 0 and 5, so that the unique byte given by luid does not clash + * with universal/local and individual/group bits of address */ + uint8_t tmp = buf[5]; + buf[5] = buf[0]; + /* make address individual and local */ + buf[0] = ((tmp & 0xfc) | 0x02); +} diff --git a/sys/net/skald/skald_eddystone.c b/sys/net/skald/skald_eddystone.c new file mode 100644 index 0000000000..847d6f09d8 --- /dev/null +++ b/sys/net/skald/skald_eddystone.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 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 net_skald_eddystone + * @{ + * + * @file + * @brief Skald's Eddystone implementation + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <string.h> + +#include "assert.h" +#include "net/skald/eddystone.h" + +#define PREAMBLE_LEN (11U) +#define PA_LEN (7U) +#define PB_LEN (3U) + +#define URL_HDR_LEN (6U) + +#define UID_LEN (23U) + +typedef struct __attribute__((packed)) { + uint8_t txadd[BLE_ADDR_LEN]; + uint8_t pa[PA_LEN]; + uint8_t service_data_len; + uint8_t pb[PB_LEN]; + uint8_t type; +} pre_t; + +typedef struct __attribute__((packed)) { + pre_t pre; + uint8_t tx_pwr; + uint8_t namespace[EDDYSTONE_NAMESPACE_LEN]; + uint8_t instance[EDDYSTONE_INSTANCE_LEN]; + uint8_t reserved[2]; +} eddy_uid_t; + +typedef struct __attribute__((packed)) { + pre_t pre; + uint8_t tx_pwr; + uint8_t scheme; + uint8_t url[]; +} eddy_url_t; + +/* ćonstant GAP data preamble parts, containing the following GAP fields: + * - flags: BR/EDR not support set + * - complete list of 16-bit UUIDs: holding the Eddystone UUID only (0xfeaa) + * - service data of type 0xfeaa (Eddystone) */ +static const uint8_t _pa[PA_LEN] = { 0x02, 0x01, 0x04, 0x03, 0x03, 0xaa, 0xfe }; +static const uint8_t _pb[PB_LEN] = { 0x16, 0xaa, 0xfe }; + +static void _init_pre(pre_t *data, uint8_t type, uint8_t len) +{ + skald_generate_random_addr(data->txadd); + memcpy(data->pa, _pa, PA_LEN); + memcpy(data->pb, _pb, PB_LEN); + data->service_data_len = len; + data->type = type; +} + +void skald_eddystone_uid_adv(skald_ctx_t *ctx, + const skald_eddystone_uid_t *uid, uint8_t tx_pwr) +{ + assert(ctx && uid); + + eddy_uid_t *pdu = (eddy_uid_t *)ctx->pkt.pdu; + _init_pre(&pdu->pre, EDDYSTONE_UID, UID_LEN); + + pdu->tx_pwr = tx_pwr; + memcpy(pdu->namespace, uid->namespace, EDDYSTONE_NAMESPACE_LEN); + memcpy(pdu->instance, uid->instance, EDDYSTONE_INSTANCE_LEN); + memset(pdu->reserved, 0, 2); + + /* start advertising */ + ctx->pkt.len = sizeof(eddy_uid_t); + skald_adv_start(ctx); +} + +void skald_eddystone_url_adv(skald_ctx_t *ctx, + uint8_t scheme, const char *url, uint8_t tx_pwr) +{ + assert(url && ctx); + size_t len = strlen(url); + assert(len <= (NETDEV_BLE_PDU_MAXLEN - (URL_HDR_LEN + PREAMBLE_LEN))); + + eddy_url_t *pdu = (eddy_url_t *)ctx->pkt.pdu; + _init_pre(&pdu->pre, EDDYSTONE_URL, (URL_HDR_LEN + len)); + + /* set remaining service data fields */ + pdu->tx_pwr = tx_pwr; + pdu->scheme = scheme; + memcpy(pdu->url, url, len); + + /* start advertising */ + ctx->pkt.len = (sizeof(pre_t) + 2 + len); + skald_adv_start(ctx); +} diff --git a/sys/net/skald/skald_ibeacon.c b/sys/net/skald/skald_ibeacon.c new file mode 100644 index 0000000000..7d67eb7da2 --- /dev/null +++ b/sys/net/skald/skald_ibeacon.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 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 net_skald_ibeacon + * @{ + * + * @file + * @brief Skald's iBeacon implementation + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <string.h> + +#include "byteorder.h" + +#include "net/skald/ibeacon.h" + +#define PREFIX_LEN (9U) + +/** + * @brief PDU format for iBeacon packets + */ +typedef struct __attribute__((packed)) { + uint8_t txadd[BLE_ADDR_LEN]; + uint8_t prefix[PREFIX_LEN]; + skald_uuid_t uuid; + be_uint16_t major; + be_uint16_t minor; + uint8_t txpower; +} ibeacon_t; + +/* constant GAP type value fields, fixed for the iBeacon format */ +static const uint8_t prefix[PREFIX_LEN] = { 0x02, 0x01, 0x06, 0x1a, 0xff, + 0x4c, 0x00, 0x02, 0x15 }; + +void skald_ibeacon_advertise(skald_ctx_t *ctx, const skald_uuid_t *uuid, + uint16_t major, uint16_t minor, uint8_t txpower) +{ + /* configure the iBeacon PDU */ + ibeacon_t *pdu = (ibeacon_t *)ctx->pkt.pdu; + + ctx->pkt.len = (uint8_t)sizeof(ibeacon_t); + + skald_generate_random_addr(pdu->txadd); + memcpy(pdu->prefix, prefix, PREFIX_LEN); + memcpy(&pdu->uuid, uuid, sizeof(skald_uuid_t)); + be_uint16_t tmp = byteorder_htons(major); + memcpy(&pdu->major, &tmp, sizeof(uint16_t)); + tmp = byteorder_htons(minor); + memcpy(&pdu->minor, &tmp, sizeof(uint16_t)); + pdu->txpower = txpower; + + skald_adv_start(ctx); +} -- GitLab