From f9bf50cf033f3fbda45790374b93de5fb0c1214b Mon Sep 17 00:00:00 2001 From: Johny Mattsson Date: Thu, 29 Sep 2016 17:02:40 +1000 Subject: [PATCH] Simple BlueTooth module for BT LE advertisements. --- components/modules/Kconfig | 6 + components/modules/bthci.c | 575 ++++++++++++++++++++++++++++++++ components/modules/component.mk | 2 +- docs/en/modules/bthci.md | 222 ++++++++++++ 4 files changed, 804 insertions(+), 1 deletion(-) create mode 100644 components/modules/bthci.c create mode 100644 docs/en/modules/bthci.md diff --git a/components/modules/Kconfig b/components/modules/Kconfig index 43cd1c72..859287d0 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -19,4 +19,10 @@ config LUA_MODULE_ENCODER Includes the encoder module. This provides hex and base64 encoding and decoding functionality. +config LUA_MODULE_BTHCI + bool "BlueTooth HCI interface module" + default "n" + help + Includes the simple BlueTooth HCI module. + endmenu diff --git a/components/modules/bthci.c b/components/modules/bthci.c new file mode 100644 index 00000000..e9a596cf --- /dev/null +++ b/components/modules/bthci.c @@ -0,0 +1,575 @@ +/* + * Copyright 2016 DiUS Computing Pty Ltd. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * - Neither the name of the copyright holders nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Johny Mattsson + */ + +#include "module.h" +#include "lauxlib.h" +#include "task/task.h" +#include "platform.h" +#include "bt.h" +#include +#include + +#define BD_ADDR_LEN 6 +typedef uint8_t bd_addr_t[BD_ADDR_LEN]; + +#define st(x) do { x } while (0) + +#define STREAM_U8(p, v) st( *(p)++ = (uint8_t)(v); ) +#define STREAM_U16(p, v) \ + st( \ + *(p)++ = (uint8_t)(v); \ + *(p)++ = (uint8_t)((v) >> 8); \ + ) +#define STREAM_BD_ADDR(p, addr) \ + st( \ + for (int i = 0; i < BD_ADDR_LEN; ++i) \ + *(p)++ = (uint8_t)addr[BD_ADDR_LEN - 1 - i];\ + ) +#define STREAM_ARRAY(p, arr, len) \ + st( \ + for (int i = 0; i < len; ++i) \ + *(p)++ = (uint8_t)arr[i]; \ + ) + + +enum { + H4_TYPE_COMMAND = 1, + H4_TYPE_ACL = 2, + H4_TYPE_SCO = 3, + H4_TYPE_EVENT = 4 +}; + +#define EVENT_COMMAND_COMPLETE 0x0e +#define EVENT_LE_META 0x3e + +// Subevent codes for LE-Meta +#define EVENT_LE_META_ADV_REPORT 0x02 + +#define ERROR_UNSPECIFIED 0x1f + +#define HCI_OGF(x) (x << 10) + +#define HCI_GRP_HOST_CONT_BASEBAND_CMDS HCI_OGF(0x03) +#define HCI_GRP_BLE_CMDS HCI_OGF(0x08) + +#define HCI_RESET (HCI_GRP_HOST_CONT_BASEBAND_CMDS | 0x0003) +#define HCI_SET_EVENT_MASK (HCI_GRP_HOST_CONT_BASEBAND_CMDS | 0x0001) +#define HCI_BLE_ADV_WRI_PARAMS (HCI_GRP_BLE_CMDS | 0x0006) +#define HCI_BLE_ADV_WRI_DATA (HCI_GRP_BLE_CMDS | 0x0008) +#define HCI_BLE_ADV_WRI_ENABLE (HCI_GRP_BLE_CMDS | 0x000a) +#define HCI_BLE_SET_SCAN_PARAMS (HCI_GRP_BLE_CMDS | 0x000b) +#define HCI_BLE_SET_SCAN_ENABLE (HCI_GRP_BLE_CMDS | 0x000c) + +#define SZ_HDR 4 + +#define SZ_HCI_RESET 4 +#define SZ_HCI_BLE_ADV_WRI_PARAMS 19 +#define SZ_HCI_BLE_ADV_WRI_DATA 36 +#define SZ_HCI_BLE_ADV_WRI_ENABLE 5 +#define SZ_HCI_BLE_SET_SCAN_ENABLE 6 +#define SZ_HCI_BLE_SET_SCAN_PARAMS 11 + +#define ADV_MIN_INTERVAL 0x00a0 +#define ADV_MAX_DATA 31 + +enum { + ADV_IND = 0x00, + ADV_DIRECT_IND_HI_DUTY = 0x01, + ADV_SCAN_IND = 0x02, + ADV_NONCONN_IND = 0x03, + ADV_DIRECT_IND_LO_DUTY = 0x04 +}; +enum { + ADV_OWN_ADDR_PUB = 0x00, + ADV_OWN_ADDR_RAND = 0x01, + ADV_OWN_ADDR_PRIV_OR_PUB = 0x02, + ADV_OWN_ADDR_PRIV_OR_RAND = 0x03 +}; +enum { + ADV_PEER_ADDR_PUB = 0x00, + ADV_PEER_ADDR_RAND = 0x01 +}; +#define ADV_CHAN_37 0x01 +#define ADV_CHAN_38 0x02 +#define ADV_CHAN_39 0x03 +#define ADV_CHAN_ALL (ADV_CHAN_37 | ADV_CHAN_38 | ADV_CHAN_39) + +#define ADV_FILTER_NONE 0x00 +#define ADV_FILTER_SCAN_WHITELIST 0x01 +#define ADV_FILTER_CONN_WHITELIST 0x02 +#define ADV_FILTER_SCAN_CONN_WHITELIST \ + (ADV_FILTER_SCAN_WHITELIST | ADV_FILTER_CONN_WHITELIST) + + +#define get_opt_field_int(idx, var, name) \ + st( \ + lua_getfield (L, idx, name); \ + if (!lua_isnil (L, -1)) \ + var = lua_tointeger (L, -1); \ + ) + + +#define MAX_CMD_Q 5 + + +// --- Local state -------------------------------------------- + +static struct cmd_list +{ + uint16_t cmd; + int cb_ref; +} __attribute__((packed)) cmd_q[MAX_CMD_Q]; + +static task_handle_t hci_event_task_handle; +static int adv_rep_cb_ref = LUA_NOREF; + + +// --- VHCI callbacks ------------------------------------------ + +static void on_bthci_can_send (void) +{ + // Unused, we don't support queuing up commands on this level +} + + +static int on_bthci_receive (uint8_t *data, uint16_t len) +{ +#if 0 + printf ("BT:"); + for (int i = 0; i < len; ++i) + printf (" %02x", data[i]); + printf ("\n"); +#endif + + if (data[0] == H4_TYPE_EVENT) + { + unsigned len = data[2]; + char *copy = malloc (SZ_HDR + len); + if (copy) + memcpy (copy, data, SZ_HDR + len); + if (!copy || !task_post_high (hci_event_task_handle, (task_param_t)copy)) + { + NODE_ERR("Dropped BT event due to no mem!"); + free (copy); + return 0; + } + } + + return 0; +} + +static const vhci_host_callback_t bthci_callbacks = +{ + on_bthci_can_send, + on_bthci_receive +}; + + +// --- Helper functions --------------------------------- + +// Expects callback function at top of stack +static int send_hci_command (lua_State *L, uint8_t *data, unsigned len) +{ + if (API_vhci_host_check_send_available ()) + { + uint16_t cmd = (((uint16_t)data[2]) << 8) | data[1]; + for (int i = 0; i < MAX_CMD_Q; ++i) + { + if (cmd_q[i].cb_ref == LUA_NOREF) + { + if (lua_gettop (L) > 0 && !lua_isnil (L, -1)) + { + cmd_q[i].cmd = cmd; + luaL_checkanyfunction (L, -1); + lua_pushvalue (L, -1); + cmd_q[i].cb_ref = luaL_ref (L, LUA_REGISTRYINDEX); + } + API_vhci_host_send_packet (data, len); + return 0; + } + } + } + // Nope, couldn't send this command! + lua_pushinteger (L, ERROR_UNSPECIFIED); + lua_pushlstring (L, NULL, 0); + lua_call (L, 2, 0); + return 0; +} + + +static void enable_le_meta_events (void) +{ + uint8_t buf[SZ_HDR + 8]; + uint8_t *p = buf; + + STREAM_U8 (p, H4_TYPE_COMMAND); + STREAM_U16(p, HCI_SET_EVENT_MASK); + STREAM_U8 (p, sizeof (buf) - SZ_HDR); + + STREAM_U8 (p, 0xff); + STREAM_U8 (p, 0xff); + STREAM_U8 (p, 0xff); + STREAM_U8 (p, 0xff); + STREAM_U8 (p, 0xff); + STREAM_U8 (p, 0x1f); + STREAM_U8 (p, 0x00); + STREAM_U8 (p, 0x20); // LE Meta-Event + + API_vhci_host_send_packet (buf, sizeof (buf)); +} + + +// --- Lua context handlers ------------------------------------ + +static void invoke_cmd_q_callback ( + lua_State *L, unsigned idx, uint8_t code, unsigned len, const uint8_t *data) +{ + if (cmd_q[idx].cb_ref != LUA_NOREF) + { + lua_rawgeti (L, LUA_REGISTRYINDEX, cmd_q[idx].cb_ref); + luaL_unref (L, LUA_REGISTRYINDEX, cmd_q[idx].cb_ref); + cmd_q[idx].cb_ref = LUA_NOREF; + + if (code) // non-zero response code? + lua_pushinteger (L, code); + else + lua_pushnil (L); // no error + lua_pushlstring (L, (const char *)data, len ); // extra bytes, if any + lua_call (L, 2, 0); + } +} + + +static void handle_hci_event (task_param_t arg, task_prio_t prio) +{ + (void)prio; + lua_State *L = lua_getstate (); + uint8_t *hci_event = (uint8_t *)arg; + + unsigned type = hci_event[1]; + unsigned len = hci_event[2]; + if (type == EVENT_COMMAND_COMPLETE) + { + uint16_t cmd = (((uint16_t)hci_event[5]) << 8) | hci_event[4]; + for (int i = 0; i < MAX_CMD_Q; ++i) + { + if (cmd_q[i].cb_ref != LUA_NOREF && cmd_q[i].cmd == cmd) + { + invoke_cmd_q_callback (L, i, hci_event[6], len - 4, &hci_event[7]); + break; + } + } + if (cmd == HCI_RESET) // clear cmd_q to prevent leaking slots + { + for (int i = 0; i < MAX_CMD_Q; ++i) + invoke_cmd_q_callback (L, i, ERROR_UNSPECIFIED, 0, NULL); + + enable_le_meta_events (); // renenable le events + } + } + else if (type == EVENT_LE_META) + { + unsigned subtype = hci_event[3]; + if (subtype == EVENT_LE_META_ADV_REPORT) + { + unsigned num_reps = hci_event[4]; + // The encoding of multiple reports is not clear in spec, and I've never + // seen a multiple-report event, so for now we only handle single reports + if (adv_rep_cb_ref != LUA_NOREF && num_reps == 1) + { + uint8_t *report = &hci_event[5]; + lua_rawgeti (L, LUA_REGISTRYINDEX, adv_rep_cb_ref); + lua_pushlstring (L, (const char *)report, len - 2); + lua_call (L, 1, 0); + } + } + } + + free (hci_event); +} + + +// --- Lua functions ------------------------------------ + +static int lbthci_init (lua_State *L) +{ + hci_event_task_handle = task_get_id (handle_hci_event); + for (int i = 0; i < MAX_CMD_Q; ++i) + cmd_q[i].cb_ref = LUA_NOREF; + + bt_controller_init (); + + API_vhci_host_register_callback (&bthci_callbacks); + + enable_le_meta_events (); + return 0; +} + + +static int lbthci_reset (lua_State *L) +{ + uint8_t buf[SZ_HCI_RESET]; + uint8_t *p = buf; + STREAM_U8 (p, H4_TYPE_COMMAND); + STREAM_U16(p, HCI_RESET); + STREAM_U8 (p, 0); + + lua_settop (L, 1); + return send_hci_command (L, buf, sizeof (buf)); +} + + +static int lbthci_adv_enable (lua_State *L) +{ + bool onoff = lua_tointeger (L, 1) > 0; + + uint8_t buf[SZ_HCI_BLE_ADV_WRI_ENABLE]; + uint8_t *p = buf; + + STREAM_U8 (p, H4_TYPE_COMMAND); + STREAM_U16(p, HCI_BLE_ADV_WRI_ENABLE); + STREAM_U8 (p, 1); + STREAM_U8 (p, onoff); + + lua_settop (L, 2); + return send_hci_command (L, buf, sizeof (buf)); +} + + +static int lbthci_adv_setdata (lua_State *L) +{ + size_t len; + const char *data = luaL_checklstring (L, 1, &len); + if (len > ADV_MAX_DATA) + len = ADV_MAX_DATA; + + uint8_t buf[SZ_HCI_BLE_ADV_WRI_DATA]; + uint8_t *p = buf; + STREAM_U8 (p, H4_TYPE_COMMAND); + STREAM_U16(p, HCI_BLE_ADV_WRI_DATA); + STREAM_U8 (p, sizeof (buf) - SZ_HDR); + + STREAM_U8 (p, len); + STREAM_ARRAY(p, data, len); + + lua_settop (L, 2); + return send_hci_command (L, buf, sizeof (buf)); +} + + +static int lbthci_adv_setparams (lua_State *L) +{ + uint16_t adv_interval_min = 0x0400; // 0.64s + uint16_t adv_interval_max = 0x0800; // 1.28s + uint8_t adv_type = ADV_IND; + uint8_t own_addr_type = ADV_OWN_ADDR_PUB; + uint8_t peer_addr_type = ADV_PEER_ADDR_PUB; + bd_addr_t peer_addr; + uint8_t adv_chan_map = ADV_CHAN_ALL; + uint8_t adv_filter_pol = ADV_FILTER_NONE; + + luaL_checkanytable (L, 1); + lua_settop (L, 2); // Pad a nil into the function slot if necessary + + get_opt_field_int (1, adv_interval_min, "interval_min"); + get_opt_field_int (1, adv_interval_max, "interval_max"); + get_opt_field_int (1, adv_type, "type"); + get_opt_field_int (1, own_addr_type, "own_addr_type"); + get_opt_field_int (1, peer_addr_type, "peer_addr_type"); + // TODO: peer addr + get_opt_field_int (1, adv_chan_map, "channel_map"); + get_opt_field_int (1, adv_filter_pol, "filter_policy"); + + + if (adv_type == ADV_SCAN_IND || adv_type == ADV_NONCONN_IND) + { + if (adv_interval_min < ADV_MIN_INTERVAL) + adv_interval_min = ADV_MIN_INTERVAL; + if (adv_interval_max < ADV_MIN_INTERVAL) + adv_interval_max = ADV_MIN_INTERVAL; + } + else if (adv_type == ADV_DIRECT_IND_HI_DUTY || + adv_type == ADV_DIRECT_IND_LO_DUTY) + { + // TODO: enforce peer addr validity + } + + if (adv_chan_map == 0 || adv_chan_map > ADV_CHAN_ALL) + adv_chan_map = ADV_CHAN_ALL; + + if (adv_filter_pol > ADV_FILTER_SCAN_CONN_WHITELIST) + adv_filter_pol = ADV_FILTER_SCAN_CONN_WHITELIST; + + uint8_t buf[SZ_HCI_BLE_ADV_WRI_PARAMS]; + uint8_t *p = buf; + STREAM_U8 (p, H4_TYPE_COMMAND); + STREAM_U16(p, HCI_BLE_ADV_WRI_PARAMS); + STREAM_U8 (p, sizeof (buf) - SZ_HDR); + + STREAM_U16(p, adv_interval_min); + STREAM_U16(p, adv_interval_max); + STREAM_U8 (p, adv_type); + STREAM_U8 (p, own_addr_type); + STREAM_U8 (p, peer_addr_type); + STREAM_BD_ADDR(p, peer_addr); + STREAM_U8 (p, adv_chan_map); + STREAM_U8 (p, adv_filter_pol); + + lua_settop (L, 2); + return send_hci_command (L, buf, sizeof (buf)); +} + + +static int lbthci_scan (lua_State *L) +{ + bool onoff = lua_tointeger (L, 1) > 0; + + uint8_t buf[SZ_HCI_BLE_SET_SCAN_ENABLE]; + uint8_t *p = buf; + STREAM_U8 (p, H4_TYPE_COMMAND); + STREAM_U16(p, HCI_BLE_SET_SCAN_ENABLE); + STREAM_U8 (p, sizeof (buf) - SZ_HDR); + STREAM_U8 (p, onoff ? 0x01 : 0x00); + STREAM_U8 (p, 0x00); // no filter duplicates + + lua_settop (L, 2); + return send_hci_command (L, buf, sizeof (buf)); +} + + +static int lbthci_scan_setparams (lua_State *L) +{ + uint8_t scan_mode = 0; + uint16_t scan_interval = 0x0010; + uint16_t scan_window = 0x0010; + uint8_t own_addr_type = 0; + uint8_t filter_policy = 0; + + luaL_checkanytable (L, 1); + lua_settop (L, 2); // Pad a nil into the function slot if necessary + + get_opt_field_int (1, scan_mode, "mode"); + get_opt_field_int (1, scan_interval, "interval"); + get_opt_field_int (1, scan_window, "window"); + get_opt_field_int (1, own_addr_type, "own_addr_type"); + get_opt_field_int (1, filter_policy, "filter_policy"); + + // TODO clamp ranges + + uint8_t buf[SZ_HCI_BLE_SET_SCAN_PARAMS]; + uint8_t *p = buf; + STREAM_U8 (p, H4_TYPE_COMMAND); + STREAM_U16(p, HCI_BLE_SET_SCAN_PARAMS); + STREAM_U8 (p, sizeof (buf) - SZ_HDR); + + STREAM_U8 (p, scan_mode); + STREAM_U16(p, scan_interval); + STREAM_U16(p, scan_window); + STREAM_U8 (p, own_addr_type); + STREAM_U8 (p, filter_policy); + + lua_settop (L, 2); + return send_hci_command (L, buf, sizeof (buf)); +} + + +static int lbthci_scan_on (lua_State *L) +{ + const char *on_what = luaL_checkstring (L, 1); + if (strcmp (on_what, "adv_report") == 0) + { + lua_settop (L, 2); + luaL_unref (L, LUA_REGISTRYINDEX, adv_rep_cb_ref); + adv_rep_cb_ref = LUA_NOREF; + if (!lua_isnil (L, 2)) + adv_rep_cb_ref = luaL_ref (L, LUA_REGISTRYINDEX); + return 0; + } + else + return luaL_error (L, "unknown event '%s'", on_what); +} + +static int lbthci_rawhci (lua_State *L) +{ + size_t len; + uint8_t *data = (uint8_t *)luaL_checklstring (L, 1, &len); + if (len < SZ_HDR || data[0] != H4_TYPE_COMMAND) + return luaL_error (L, "definitely not a valid HCI command"); + + lua_settop (L, 2); + return send_hci_command (L, data, len); +} + + +static const LUA_REG_TYPE bthci_adv_map[] = +{ + { LSTRKEY( "enable" ), LFUNCVAL( lbthci_adv_enable ) }, + { LSTRKEY( "setdata" ), LFUNCVAL( lbthci_adv_setdata ) }, + { LSTRKEY( "setparams" ), LFUNCVAL( lbthci_adv_setparams ) }, + + // Advertising types + { LSTRKEY( "CONN_UNDIR"), LNUMVAL( ADV_IND ) }, + { LSTRKEY( "CONN_DIR_HI"), LNUMVAL( ADV_DIRECT_IND_HI_DUTY ) }, + { LSTRKEY( "SCAN_UNDIR"), LNUMVAL( ADV_SCAN_IND ) }, + { LSTRKEY( "NONCONN_UNDIR"), LNUMVAL( ADV_NONCONN_IND ) }, + { LSTRKEY( "CONN_DIR_LO"), LNUMVAL( ADV_DIRECT_IND_LO_DUTY ) }, + + { LSTRKEY( "CHAN_37" ), LNUMVAL( ADV_CHAN_37 ) }, + { LSTRKEY( "CHAN_38" ), LNUMVAL( ADV_CHAN_38 ) }, + { LSTRKEY( "CHAN_39" ), LNUMVAL( ADV_CHAN_39 ) }, + { LSTRKEY( "CHAN_ALL" ), LNUMVAL( ADV_CHAN_ALL ) }, + + { LNILKEY, LNILVAL } +}; + + +static const LUA_REG_TYPE bthci_scan_map[] = +{ + { LSTRKEY( "enable" ), LFUNCVAL( lbthci_scan ) }, + { LSTRKEY( "setparams" ), LFUNCVAL( lbthci_scan_setparams ) }, + { LSTRKEY( "on" ), LFUNCVAL( lbthci_scan_on ) }, + { LNILKEY, LNILVAL } +}; + + +static const LUA_REG_TYPE bthci_map[] = +{ + { LSTRKEY( "rawhci" ), LFUNCVAL( lbthci_rawhci ) }, + { LSTRKEY( "reset" ), LFUNCVAL( lbthci_reset ) }, + { LSTRKEY( "adv" ), LROVAL( bthci_adv_map ) }, + { LSTRKEY( "scan" ), LROVAL( bthci_scan_map ) }, + + { LNILKEY, LNILVAL } +}; + +NODEMCU_MODULE(BTHCI, "bthci", bthci_map, lbthci_init); diff --git a/components/modules/component.mk b/components/modules/component.mk index c9fcf524..f326ec1c 100644 --- a/components/modules/component.mk +++ b/components/modules/component.mk @@ -8,6 +8,6 @@ include $(PROJECT_PATH)/components/modules/uppercase.mk MODULE_NAMES:=$(call uppercase,$(subst .c,,$(wildcard *.c))) FORCE_LINK:=$(foreach mod,$(MODULE_NAMES),$(if $(CONFIG_LUA_MODULE_$(mod)), -u $(mod)_module_selected1)) -COMPONENT_ADD_LDFLAGS=$(FORCE_LINK) -lmodules +COMPONENT_ADD_LDFLAGS=$(FORCE_LINK) -lmodules $(if $(CONFIG_LUA_MODULE_BTHCI),-lbtdm_app) include $(IDF_PATH)/make/component_common.mk diff --git a/docs/en/modules/bthci.md b/docs/en/modules/bthci.md new file mode 100644 index 00000000..4f6fc828 --- /dev/null +++ b/docs/en/modules/bthci.md @@ -0,0 +1,222 @@ +# BT HCI Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2016-09-29 | [DiUS](https://github.com/DiUS), [Johny Mattsson](https://github.com/jmattsson) | [Johny Mattsson](https://github.com/jmattsson) | [bthci.c](../../../components/modules/bthci.c)| + +The BT HCI module provides a minimal HCI-level interface to BlueTooth +adverisements. Via this module you can set up BT LE advertisements and also +receive advertisements from other devices. + +Advertisements are an easy way of publishing sensor data to e.g. a +smartphone app. + + +# bthci.rawhci(hcibytes, callback) + +Sends a raw HCI command to the BlueTooth controller. + +Only intended for development use. + +#### Syntax +`bthci.rawhci(hcibytes [, callback])` + +#### Parameters +- `hcibytes` raw HCI command bytes to send to the BlueTooth controller. +- `callback` optional function to be invoked when the reset completes. Its + first argument is the HCI error code, or `nil` on success. The second + argument contains any subsequent raw result bytes, or an empty string + if the result only contained the status code. + +##### Returns +`nil` + +#### Example +```lua +-- Send a HCI reset command (it would be easier to use bthci.reset() though) +bthci.rawhci(encoder.fromHex("01030c00"), function(err) print(err or "Ok!")) +``` + +#### See also +[`encoder.fromHex()`](encoder.md#fromhex) +[`encoder.toHex()`](encoder.md#tohex) +[`struct.pack()`](struct.md#pack) + + + +## bthci.reset(callback) + +Resets the BlueTooth controller. + +#### Syntax +`bthci.reset([callback])` + +#### Parameters +- `callback` optional function to be invoked when the reset completes. Its + only argument is the HCI error code, or `nil` on success. + +#### Returns +`nil` + +#### Example +```lua +bthci.reset(function(err) print(err or "Ok!") end) +``` + + +# bthci.adv.enable(onoff, callback) + +Enables or disables BlueTooth LE advertisements. + +Before enabling advertisements, both parameters and data should be set. + +#### Syntax +`bthci.adv.enable(onoff [, callback])` + +#### Parameters +- `onoff` 1 or 0 to enable or disable advertisements, respectively. +- `callback` optional function to be invoked when the reset completes. Its + only argument is the HCI error code, or `nil` on success. + +##### Returns +`nil` + +#### Example +```lua +bthci.adv.enable(1, function(err) print(err or "Ok!") end) +``` + + +# bthci.adv.setdata(advbytes, callback) + +Configures the data to advertise. + +#### Syntax +`bthci.adv.setdata(advbytes [, callback])` + +#### Parameters +- `advbytes` the raw bytes to advertise (up to 31 bytes), in the correct + format (consult the BlueTooth specification for details). +- `callback` optional function to be invoked when the reset completes. Its + only argument is the HCI error code, or `nil` on success. + +##### Returns +`nil` + +#### Example +```lua +-- Configure advertisements of a short name "abcdefg" +bthci.adv.setdata(encoder.fromHex("080861626364656667"), function(err) print(err or "Ok!") end) +``` + + +# bthci.adv.setparams(paramtable, callback) + +Configures advertisement parameters. + +#### Syntax +`bthci.adv.setparams(paramtable [, callback])` + +#### Parameters +- `paramtable` a table with zero or more of the following fields: + - `interval_min` value in units of 0.625ms. Default 0x0400 (0.64s). + - `interval_max` value in units of 0.625ms. Default 0x0800 (1.28s). + - `type` advertising type, one of following constants: + - `bthci.adv.CONN_UNDIR`, the default (ADV_IND in BT spec) + - `bthci.adv.CONN_DIR_HI` (ADV_DIRECT_IND, high duty cycle in the BT spec) + - `bthci.adv.SCAN_UNDIR` (ADV_SCAN_IND in the BT spec) + - `bthci.adv.NONCONN_UNDIR` (ADV_NONCONN_IND in the BT spec) + - `bthci.adv.CONN_DIR_LO` (ADV_DIRECT_IND, low duty cycle in the BT spec) + - `own_addr_type` own address type. Default 0 (public address). + - `peer_addr_type` peer address type. Default 0 (public address). + - `peer_addr` TODO, not yet implemented + - `channel_map` which channels to advertise on. The constants + `bthci.adv.CHAN_37`, `bthci.adv.CHAN_38`, `bthci.adv.CHAN_39` or + `bthci.adv.CHAN_ALL` may be used. Default is all channels. + - `filter_policy` filter policy, default 0 (no filtering). +- `callback` optional function to be invoked when the reset completes. Its + only argument is the HCI error code, or `nil` on success. + +#### Returns +`nil` + +#### Example +```lua +bthci.adv.setparams({type=bthci.adv.NONCONN_UNDIR}, function(err) print(err or "Ok!") end) +``` + + +# bthci.scan.enable(onoff, callback) + +Enables or disable scanning for advertisements from other BlueTooth devices. + +#### Syntax +`bthci.scan.enable(onoff [, callback])` + +#### Parameters +- `onoff` 1 or 0 to enable or disable advertisements, respectively. +- `callback` optional function to be invoked when the reset completes. Its + only argument is the HCI error code, or `nil` on success. + +##### Returns +`nil` + +#### Example +```lua +bthci.scan.enable(1, function(err) print(err or "Ok!") end) +``` + + +# bthci.scan.setparams(paramstable, callback) + +Configures scan parameters. + +Note that if configuring the scan window to be the same as the scan interval +this will fully occupy the radio and no other activity takes place. + +#### Syntax +`bthci.scan.setparams(paramstable [, callback])` + +#### Parameters +- `paramstable` a table with zero or more of the following fields: + - `mode` scanning mode, 0 for passive, 1 for active. Default 0. + - `interval` scanning interval in units of 0.625ms. Default 0x0010. + - `window` length of scanning window in units of 0.625ms. Default 0x0010. + - `own_addr_type` own address type. Default 0 (public). + - `filter_policy` filtering policy. Default 0 (no filtering). +- `callback` optional function to be invoked when the reset completes. Its + only argument is the HCI error code, or `nil` on success. + +#### Returns +`nil` + +#### Example +```lua +bthci.scan.setparams({mode=1,interval=40,window=20},function(err) print(err or "Ok!") end) +``` + +# bthci.scan.on(event, callback) + +Registers the callback to be passed any received advertisements. + +#### Syntax +`bthci.scan.on(event [, callback])` + +#### Parameters +- `event` the string describing the event. Currently only "adv_report" is + supported, to register for advertising reports. +- `callback` the callback function to receive the advertising reports, or + `nil` to deregister the callback. This callback receives the raw bytes + of the advertisement payload. + +#### Returns +`nil` + +#### Example +```lua +bthci.scan.on("adv_report", function(rep) print("ADV: "..encoder.toHex(rep))end) +``` + +#### See also +[`encoder.toHex()`](encoder.md#tohex) +[`struct.unpack()`](struct.md#unpack) +