nodemcu-firmware/components/modules/bthci.c

582 lines
16 KiB
C

/*
* 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 "sdkconfig.h"
#ifdef CONFIG_LUA_MODULE_BTHCI
#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 esp_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 (esp_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);
}
esp_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
esp_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;
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
esp_bt_controller_init (&bt_cfg);
esp_bt_controller_enable (ESP_BT_MODE_BTDM);
esp_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);
#endif