diff --git a/pkg/nimble/Makefile b/pkg/nimble/Makefile index 412cea1a9c..a6663a4ed8 100644 --- a/pkg/nimble/Makefile +++ b/pkg/nimble/Makefile @@ -67,4 +67,8 @@ nimble_drivers_nrf52: # additional, RIOT specific nimble modules nimble_addr: "$(MAKE)" -C $(TDIR)/addr/ + +nimble_scanlist: + "$(MAKE)" -C $(TDIR)/scanlist + include $(RIOTBASE)/pkg/pkg.mk diff --git a/pkg/nimble/Makefile.dep b/pkg/nimble/Makefile.dep index da0edf3996..f20696dd88 100644 --- a/pkg/nimble/Makefile.dep +++ b/pkg/nimble/Makefile.dep @@ -34,3 +34,8 @@ endif ifneq (,$(filter nimble_addr,$(USEMODULE))) USEMODULE += bluetil_addr endif + +ifneq (,$(filter nimble_scanlist,$(USEMODULE))) + USEMODULE += nimble_addr + USEMODULE += bluetil_ad +endif diff --git a/pkg/nimble/Makefile.include b/pkg/nimble/Makefile.include index 6c7c67c681..e637c34770 100644 --- a/pkg/nimble/Makefile.include +++ b/pkg/nimble/Makefile.include @@ -73,3 +73,6 @@ endif ifneq (,$(filter nimble_addr,$(USEMODULE))) INCLUDES += -I$(RIOTPKG)/nimble/addr/include endif +ifneq (,$(filter nimble_scanlist,$(USEMODULE))) + INCLUDES += -I$(RIOTPKG)/nimble/scanlist/include +endif diff --git a/pkg/nimble/scanlist/Makefile b/pkg/nimble/scanlist/Makefile new file mode 100644 index 0000000000..62b9d2e06d --- /dev/null +++ b/pkg/nimble/scanlist/Makefile @@ -0,0 +1,3 @@ +MODULE = nimble_scanlist + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/nimble/scanlist/include/nimble_scanlist.h b/pkg/nimble/scanlist/include/nimble_scanlist.h new file mode 100644 index 0000000000..ad1de1d5a8 --- /dev/null +++ b/pkg/nimble/scanlist/include/nimble_scanlist.h @@ -0,0 +1,119 @@ +/* + * 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 ble_nimble_scanlist Scan Result List + * @ingroup ble_nimble + * @brief List for storing and printing BLE scan results + * + * @note This scanlist implementation is not thread safe. So calling + * nimble_scanlist_update() in between nimble_scanlist_get*() + * calls is not a good idea. + * @{ + * + * @file + * @brief List for keeping scanned BLE devices + * + * @author Hauke Petersen + */ + +#ifndef NIMBLE_SCANLIST_H +#define NIMBLE_SCANLIST_H + +#include "clist.h" +#include "net/ble.h" +#include "nimble/ble.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Default number of list entries that are allocated in RAM + */ +#ifndef NIMBLE_SCANLIST_SIZE +#define NIMBLE_SCANLIST_SIZE (20U) +#endif + +/** + * @brief Data structure for holding a single scanlist entry + */ +typedef struct { + clist_node_t node; /**< list node */ + ble_addr_t addr; /**< a node's BLE address */ + uint8_t ad[BLE_ADV_PDU_LEN]; /**< the received raw advertising data */ + uint8_t ad_len; /**< length of the advertising data */ + int8_t last_rssi; /**< last RSSI of a scanned node */ + uint32_t adv_msg_cnt; /**< number of adv packets by a node */ + uint32_t first_update; /**< first packet timestamp */ + uint32_t last_update; /**< last packet timestamp */ +} nimble_scanlist_entry_t; + +/** + * @brief Initialize the scanlist + */ +void nimble_scanlist_init(void); + +/** + * @brief Add/update the entry for the a scanned node + * + * If the list is already full, the scanned node is simply ignored. + * + * @param[in] addr BLE address of the scanned node + * @param[in] rssi RSSI of the received advertising packet + * @param[in] ad the payload of the advertising packet + * @param[in] len length of @p ad + */ +void nimble_scanlist_update(const ble_addr_t *addr, int8_t rssi, + const uint8_t *ad, size_t len); + +/** + * @brief Get an entry of the scanlist by its position + * + * @param[in] pos position in the list + * + * @return the scanlist entry at position @p pos + * @return NULL if there is no entry a the given position + */ +nimble_scanlist_entry_t *nimble_scanlist_get_by_pos(unsigned pos); + +/** + * @brief Get the next entry from the list, pass NULL for getting the first + * entry + * + * @param[in] e any entry in the list + * + * @return the entry following @p e + * @return NULL if @p e was the last entry + */ +nimble_scanlist_entry_t *nimble_scanlist_get_next(nimble_scanlist_entry_t *e); + +/** + * @brief Clear all entries in the list + */ +void nimble_scanlist_clear(void); + +/** + * @brief Dump the entire scanlist to STDIO using nimble_scanlist_print_entry() + */ +void nimble_scanlist_print(void); + +/** + * @brief Dump a single scanlist entry to STDIO + * + * @param[in] e entry to dump + */ +void nimble_scanlist_print_entry(nimble_scanlist_entry_t *e); + + +#ifdef __cplusplus +} +#endif + +#endif /* NIMBLE_SCANLIST_H */ +/** @} */ diff --git a/pkg/nimble/scanlist/nimble_scanlist.c b/pkg/nimble/scanlist/nimble_scanlist.c new file mode 100644 index 0000000000..ad349a880a --- /dev/null +++ b/pkg/nimble/scanlist/nimble_scanlist.c @@ -0,0 +1,121 @@ +/* + * 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 ble_nimble_scanlist + * @{ + * + * @file + * @brief Implementation of a list for NimBLE scan results + * + * @author Hauke Petersen + * + * @} + */ +#include + +#include "xtimer.h" +#include "net/bluetil/ad.h" + +#include "nimble_scanlist.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static nimble_scanlist_entry_t _mem[NIMBLE_SCANLIST_SIZE]; + +static clist_node_t _pool; +static clist_node_t _list; + +static int _finder(clist_node_t *node, void *arg) +{ + const ble_addr_t *addr = (const ble_addr_t *)arg; + nimble_scanlist_entry_t *e = (nimble_scanlist_entry_t *)node; + if (ble_addr_cmp(&e->addr, addr) == 0) { + return 1; + } + return 0; +} + +static nimble_scanlist_entry_t *_find(const ble_addr_t *addr) +{ + return (nimble_scanlist_entry_t *)clist_foreach(&_list, _finder, + (void *)addr); +} + +void nimble_scanlist_init(void) +{ + for (unsigned i = 0; i < (sizeof(_mem) / sizeof(_mem[0])); i++) { + clist_rpush(&_pool, &_mem[i].node); + } +} + +void nimble_scanlist_update(const ble_addr_t *addr, int8_t rssi, + const uint8_t *ad, size_t len) +{ + assert(addr); + assert(len <= BLE_ADV_PDU_LEN); + + uint32_t now = xtimer_now_usec(); + nimble_scanlist_entry_t *e = _find(addr); + + if (!e) { + e = (nimble_scanlist_entry_t *)clist_lpop(&_pool); + if (!e) { + /* no space available, dropping newly discovered node */ + return; + } + memcpy(&e->addr, addr, sizeof(ble_addr_t)); + if (ad) { + memcpy(e->ad, ad, len); + } + e->ad_len = len; + e->last_rssi = rssi; + e->first_update = now; + e->adv_msg_cnt = 1; + clist_rpush(&_list, (clist_node_t *)e); + } + else { + e->adv_msg_cnt++; + } + + e->last_update = now; +} + +nimble_scanlist_entry_t *nimble_scanlist_get_next(nimble_scanlist_entry_t *e) +{ + if (e == NULL) { + return (nimble_scanlist_entry_t *)_list.next; + } + else { + e = (nimble_scanlist_entry_t *)e->node.next; + if (e == (nimble_scanlist_entry_t *)_list.next) { + /* end of list */ + return NULL; + } + return e; + } +} + +nimble_scanlist_entry_t *nimble_scanlist_get(unsigned pos) +{ + nimble_scanlist_entry_t *cur = nimble_scanlist_get_next(NULL); + for (unsigned i = 0; i < pos; i++) { + cur = nimble_scanlist_get_next(cur); + } + return cur; +} + +void nimble_scanlist_clear(void) +{ + clist_node_t *node = clist_lpop(&_list); + while (node) { + clist_rpush(&_pool, node); + node = clist_lpop(&_list); + } +} diff --git a/pkg/nimble/scanlist/nimble_scanlist_print.c b/pkg/nimble/scanlist/nimble_scanlist_print.c new file mode 100644 index 0000000000..7cc8d024c1 --- /dev/null +++ b/pkg/nimble/scanlist/nimble_scanlist_print.c @@ -0,0 +1,71 @@ +/* + * 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 ble_nimble_scanlist + * @{ + * + * @file + * @brief Print functions for printing a scanlist or selected entries to + * STDIO + * + * @author Hauke Petersen + * + * @} + */ + +#include "net/bluetil/ad.h" +#include "nimble_scanlist.h" + +static void _print_addr(const ble_addr_t *addr) +{ + printf("%02x", (int)addr->val[5]); + for (int i = 4; i >= 0; i--) { + printf(":%02x", addr->val[i]); + } + switch (addr->type) { + case BLE_ADDR_PUBLIC: printf(" (PUBLIC) "); break; + case BLE_ADDR_RANDOM: printf(" (RANDOM) "); break; + case BLE_ADDR_PUBLIC_ID: printf(" (PUB_ID) "); break; + case BLE_ADDR_RANDOM_ID: printf(" (RAND_ID)"); break; + default: printf(" (UNKNOWN)"); break; + } +} + +void nimble_scanlist_print(void) +{ + unsigned i = 0; + + nimble_scanlist_entry_t *e = nimble_scanlist_get_next(NULL); + while (e) { + printf("[%2u] ", i++); + nimble_scanlist_print_entry(e); + e = nimble_scanlist_get_next(e); + } +} + +void nimble_scanlist_print_entry(nimble_scanlist_entry_t *e) +{ + assert(e); + + /* try to find a device name */ + char name[(BLE_ADV_PDU_LEN + 1)] = { 0 }; + bluetil_ad_t ad = BLUETIL_AD_INIT(e->ad, e->ad_len, e->ad_len); + int res = bluetil_ad_find_str(&ad, BLE_GAP_AD_NAME, name, sizeof(name)); + if (res != BLUETIL_AD_OK) { + res = bluetil_ad_find_str(&ad, BLE_GAP_AD_NAME_SHORT, name, sizeof(name)); + } + if (res != BLUETIL_AD_OK) { + strncpy(name, "undefined", sizeof(name)); + } + + _print_addr(&e->addr); + unsigned adv_int = ((e->last_update - e->first_update) / e->adv_msg_cnt); + printf(" \"%s\", adv_msg_cnt: %u, adv_int: %uus, last_rssi: %i\n", + name, (unsigned)e->adv_msg_cnt, adv_int, (int)e->last_rssi); +}