1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 05:12:57 +01:00

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.
This commit is contained in:
Hauke Petersen 2018-04-05 11:10:40 +02:00
parent 77499905e4
commit 49bd85d00a
10 changed files with 619 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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))))

124
sys/include/net/skald.h Normal file
View File

@ -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 */
/** @} */

View File

@ -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 */
/** @} */

View File

@ -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 */
/** @} */

11
sys/net/skald/Makefile Normal file
View File

@ -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

158
sys/net/skald/skald.c Normal file
View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}