Simple BlueTooth module for BT LE advertisements.
This commit is contained in:
parent
8e441b59c1
commit
f9bf50cf03
|
@ -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
|
||||
|
|
|
@ -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 <jmattsson@dius.com.au>
|
||||
*/
|
||||
|
||||
#include "module.h"
|
||||
#include "lauxlib.h"
|
||||
#include "task/task.h"
|
||||
#include "platform.h"
|
||||
#include "bt.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
Loading…
Reference in New Issue