/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "sdkconfig.h" #ifdef CONFIG_NODEMCU_CMODULE_BLE #include #include #include "module.h" #include "lauxlib.h" #include "task/task.h" #include "platform.h" #include "esp_bt.h" #include #include #include #define TAG "ble" #ifndef CONFIG_BT_NIMBLE_ENABLED #error You must enable NIMBLE if you want the Lua ble module. Hopefully this can be made automatic some day. #endif /* BLE */ #include "esp_nimble_hci.h" #include "nimble/nimble_port.h" #include "nimble/nimble_port_freertos.h" #include "host/ble_hs.h" #include "host/util/util.h" #include "console/console.h" #include "services/gap/ble_svc_gap.h" #include "services/gatt/ble_svc_gatt.h" #include "nimble/ble.h" #include "task/task.h" #if 0 #undef MODLOG_DFLT #define MODLOG_DFLT(level, ...) printf(__VA_ARGS__) #endif #define PERROR() MODLOG_DFLT(INFO, "pcall failed: %s\n", lua_tostring(L, -1)) #ifdef CONFIG_LUA_VERSION_51 #define lua_rawlen lua_objlen static int lua_geti (lua_State *L, int index, lua_Integer i) { index = lua_absindex(L, index); lua_pushinteger(L, i); lua_gettable(L, index); return lua_type(L, -1); } #endif extern void ble_hs_lock(); extern void ble_hs_unlock(); static volatile enum { IDLE, SYNCED, READY_TO_ADVERTISE, ADVERTISING } synced; static void lble_start_advertising(); static int lble_gap_event(struct ble_gap_event *event, void *arg); static const char *gadget_name; static uint8_t *gadget_mfg; static size_t gadget_mfg_len; static const struct ble_gatt_svc_def *gatt_svr_svcs; static const uint16_t *notify_handles; static task_handle_t task_handle; static QueueHandle_t response_queue; static bool already_inited; static int struct_pack_index; static int struct_unpack_index; static enum { STOPPED, RUNNING, SHUTTING } inited; static int seqno; // Note that the buffer should be freed typedef struct { int seqno; int errcode; char *buffer; size_t length; } response_message_t; typedef struct { int seqno; struct ble_gatt_access_ctxt *ctxt; void *arg; char *buffer; size_t length; } task_block_t; /** * Utility function to log an array of bytes. */ static void print_addr(const void *addr) { const uint8_t *u8p; u8p = addr; MODLOG_DFLT(INFO, "%02x:%02x:%02x:%02x:%02x:%02x", u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); } static int gethexval(char c) { if (c < '0') { return -1; } if (c <= '9') { return c - '0'; } if (c < 'a') { c += 'a' - 'A'; } if (c < 'a') { return -1; } if (c <= 'f') { return c - 'a' + 10; } return -1; } static int decodehex(const char *s) { // two characters int v1 = gethexval(s[0]); int v2 = gethexval(s[1]); if (v1 < 0 || v2 < 0) { return -1; } return (v1 << 4) + v2; } static bool convert_uuid(ble_uuid_any_t *uuid, const char *s) { int len = strlen(s); char decodebuf[32]; const char *sptr = s + len; int i; for (i = 0; sptr > s && i < sizeof(decodebuf); ) { if (sptr < s + 2) { return false; } if (sptr[-1] == '-') { sptr--; continue; } sptr -= 2; int val = decodehex(sptr); if (val < 0) { return false; } decodebuf[i++] = val; } if (sptr != s) { return false; } if (ble_uuid_init_from_buf(uuid, decodebuf, i) != 0) { return false; } return true; } static void free_gatt_svcs(lua_State *L, const struct ble_gatt_svc_def * svcs) { void *tofree = (void *) svcs; // Need to unref anything if (!svcs) { return; } for (; svcs->characteristics; svcs++) { const struct ble_gatt_chr_def *chrs = svcs->characteristics; for (; chrs->arg; chrs++) { luaL_unref(L, LUA_REGISTRYINDEX, (int) chrs->arg); } } free(tofree); } static int lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { UNUSED(conn_handle); UNUSED(attr_handle); size_t task_block_size = sizeof(task_block_t); if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { task_block_size += OS_MBUF_PKTLEN(ctxt->om); } task_block_t *task_block = malloc(task_block_size); if (!task_block) { return BLE_ATT_ERR_UNLIKELY; } memset(task_block, 0, sizeof(*task_block)); task_block->ctxt = ctxt; task_block->arg = arg; task_block->seqno = seqno++; task_block->buffer = (char *) (task_block + 1); if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { task_block->length = OS_MBUF_PKTLEN(ctxt->om); uint16_t outlen; if (ble_hs_mbuf_to_flat(ctxt->om, task_block->buffer, task_block->length, &outlen)) { free(task_block); return BLE_ATT_ERR_UNLIKELY; } } if (!task_post(TASK_PRIORITY_HIGH, task_handle, (task_param_t) task_block)) { free(task_block); return BLE_ATT_ERR_UNLIKELY; } response_message_t message; while (1) { if (xQueueReceive(response_queue, &message, (TickType_t) (2000/portTICK_PERIOD_MS) ) != pdPASS) { free(task_block); return BLE_ATT_ERR_UNLIKELY; } if (message.seqno == task_block->seqno) { break; } } int rc = BLE_ATT_ERR_UNLIKELY; if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR || ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { rc = message.errcode; if (rc == 0) { if (os_mbuf_append(ctxt->om, message.buffer, message.length)) { rc = BLE_ATT_ERR_INSUFFICIENT_RES; } } } else if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { rc = message.errcode; } free(message.buffer); free(task_block); return rc; } static void lble_task_cb(task_param_t param, task_prio_t prio) { task_block_t *task_block = (task_block_t *) param; response_message_t message; memset(&message, 0, sizeof(message)); message.errcode = BLE_ATT_ERR_UNLIKELY; lua_State *L = lua_getstate(); int top = lua_gettop(L); lua_rawgeti(L, LUA_REGISTRYINDEX, (int) task_block->arg); // Now we have the characteristic table in -1 lua_getfield(L, -1, "type"); // -1 is the struct mapping (if any), -2 is the table size_t datalen; const char *data = 0; if (task_block->ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { // if there is a read method, then invoke it lua_getfield(L, -2, "read"); if (!lua_isnoneornil (L, -1)) { lua_pushvalue(L, -3); // dup the table onto the top if (lua_pcall(L, 1, 1, 0)) { // error. PERROR(); message.errcode = BLE_ATT_ERR_UNLIKELY; lua_pop(L, 1); goto cleanup; } } else { lua_pop(L, 1); // get rid of the null lua_getfield(L, -2, "value"); } // Now we have the value (-1), struct (-2), table (-3) if (!lua_isnoneornil(L, -2)) { // need to convert value if (!lua_istable(L, -1)) { // wrap it in a table lua_createtable(L, 1, 0); lua_pushvalue(L, -2); // Now have value, table, value, struct, table lua_rawseti(L, -2, 1); lua_remove(L, -2); // now have table, struct, chr table } // Now call struct.pack // The arguments are the format string, and then the values lua_rawgeti(L, LUA_REGISTRYINDEX, struct_pack_index); lua_pushvalue(L, -3); // dup the format int nv = lua_rawlen(L, -3); for (int i = 1; i <= nv; i++) { lua_rawgeti(L, -2 - i, i); } if (lua_pcall(L, nv + 1, 1, 0)) { PERROR(); message.errcode = BLE_ATT_ERR_UNLIKELY; lua_pop(L, 2); goto cleanup; } lua_remove(L, -2); // remove the old value // now have string (-1), struct(-2), chrtable (-3) } data = lua_tolstring(L, -1, &datalen); } if (task_block->ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) { lua_getfield(L, -2, "name"); // Now we have the name (-1), struct (-2), table (-3) data = lua_tolstring(L, -1, &datalen); } if (task_block->ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { // Push the value lua_pushlstring(L, task_block->buffer, task_block->length); // value, struct, chrtable // If we have a struct, then unpack the values if (!lua_isnoneornil(L, -2)) { lua_createtable(L, 0, 0); int stack_size = lua_gettop(L); lua_rawgeti(L, LUA_REGISTRYINDEX, struct_unpack_index); lua_pushvalue(L, -4); // dup the format lua_pushvalue(L, -4); // dup the string if (lua_pcall(L, 2, LUA_MULTRET, 0)) { PERROR(); message.errcode = BLE_ATT_ERR_UNLIKELY; lua_pop(L, 2); goto cleanup; } int vals = lua_gettop(L) - stack_size; // Note that the last entry is actually the string offset for (int i = 1; i < vals; i++) { lua_pushvalue(L, -(vals - i + 1)); lua_rawseti(L, -(vals + 2), i); } lua_pop(L, vals); lua_remove(L, -2); // Now have table, struct, chrtable } // If the value is a table of a single value, then // treat as value if (lua_istable(L, -1) && lua_rawlen(L, -1) == 1) { lua_rawgeti(L, -1, 1); lua_remove(L, -2); // and throw away the table } // value, struct, chr table // save to `value` if present lua_getfield(L, -3, "value"); if (!lua_isnoneornil(L, -1)) { lua_pop(L, 1); lua_pushvalue(L, -1); lua_setfield(L, -4, "value"); } else { lua_pop(L, 1); } lua_getfield(L, -3, "write"); if (!lua_isnoneornil(L, -1)) { lua_pushvalue(L, -4); // the characterstics table lua_pushvalue(L, -3); // the value if (lua_pcall(L, 2, 0, 0)) { PERROR(); message.errcode = BLE_ATT_ERR_UNLIKELY; lua_pop(L, 2); goto cleanup; } } else { lua_pop(L, 1); // Throw away the null write pointer } } if (data) { message.buffer = malloc(datalen); if (message.buffer) { message.length = datalen; memcpy(message.buffer, data, datalen); } } lua_pop(L, 1); // Throw away the value message.errcode = 0; cleanup: lua_settop(L, top); message.seqno = task_block->seqno; xQueueSend(response_queue, &message, (TickType_t) 0); } static int lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp, const uint16_t **handlep) { // We have to first figure out how big the allocated memory is. // This is the number of services (ns) + 1 * sizeof(ble_gatt_svc_def) // + number of characteristics (nc) + ns * sizeof(ble_gatt_chr_def) // + ns + nc * sizeof(ble_uuid_any_t) lua_getfield(L, 1, "services"); if (!lua_istable(L, -1)) { return luaL_error(L, "services entry must be a table"); } int ns = lua_rawlen(L, -1); // -1 is the services list int nc = 0; int nd = 0; for (int i = 1; i <= ns; i++) { MODLOG_DFLT(INFO, "Counting -- service %d (top %d)\n", i, lua_gettop(L)); lua_geti(L, -1, i); // -1 is now the service which should be a table. It must have a uuid if (lua_type(L, -1) != LUA_TTABLE) { luaL_error(L, "The services list must contain tables"); } if (lua_getfield(L, -1, "uuid") != LUA_TSTRING) { luaL_error(L, "The service uuid must be a string"); } lua_pop(L, 1); // -1 is the service again lua_getfield(L, -1, "characteristics"); int sccnt = lua_rawlen(L, -1); nc += sccnt; // Now count the number of descriptors that we need for (int j = 1; j <= sccnt; j++) { lua_geti(L, -1, j); if (lua_getfield(L, -1, "name") != LUA_TNIL) { nd++; } lua_pop(L, 2); } lua_pop(L, 2); } MODLOG_DFLT(INFO, "Discovered %d services with %d characteristics with %d descriptors\n", ns, nc, nd); int size = (ns + 1) * sizeof(struct ble_gatt_svc_def) + (nc + ns) * sizeof(struct ble_gatt_chr_def) + (ns + nc) * sizeof(ble_uuid_any_t) + (nc + 1) * sizeof(uint16_t) + nd * 2 * sizeof(struct ble_gatt_dsc_def); struct ble_gatt_svc_def *svcs = malloc(size); if (!svcs) { luaL_error(L, "Unable to allocate memory: %d", size); } memset(svcs, 0, size); struct ble_gatt_svc_def *result = svcs; void *eom = ((char *) svcs) + size; struct ble_gatt_chr_def *chrs = (struct ble_gatt_chr_def *) (svcs + ns + 1); ble_uuid_any_t *uuids = (ble_uuid_any_t *) (chrs + ns + nc); struct ble_gatt_dsc_def *dsc = (struct ble_gatt_dsc_def *) (uuids + ns + nc); uint16_t *handles = (uint16_t *) (dsc + nd * 2); handles[0] = 0; // number of slots used // Now fill out the data structure // -1 is the services list for (int i = 1; i <= ns; i++) { MODLOG_DFLT(INFO, "Processing service %d (top %d)\n", i, lua_gettop(L)); struct ble_gatt_svc_def *svc = svcs++; lua_geti(L, -1, i); // -1 is now the service which should be a table. It must have a uuid lua_getfield(L, -1, "uuid"); // Convert the uuid if ((void *) (uuids + 1) > eom) { free_gatt_svcs(L, result); return luaL_error(L, "Miscalculated memory requirements"); } if (!convert_uuid(uuids, lua_tostring(L, -1))) { free_gatt_svcs(L, result); return luaL_error(L, "Unable to convert UUID: %s", lua_tostring(L, -1)); } svc->type = BLE_GATT_SVC_TYPE_PRIMARY; svc->uuid = (ble_uuid_t *) uuids++; svc->characteristics = chrs; lua_pop(L, 1); // -1 is the service again lua_getfield(L, -1, "characteristics"); int nc = lua_rawlen(L, -1); for (int j = 1; j <= nc; j++) { MODLOG_DFLT(INFO, "Processing characteristic %d (top %d)\n", j, lua_gettop(L)); struct ble_gatt_chr_def *chr = chrs++; lua_geti(L, -1, j); // -1 is now the characteristic lua_getfield(L, -1, "uuid"); // Convert the uuid if ((void *) (uuids + 1) > eom) { free_gatt_svcs(L, result); return luaL_error(L, "Miscalculated memory requirements"); } if (!convert_uuid(uuids, lua_tostring(L, -1))) { free_gatt_svcs(L, result); return luaL_error(L, "Unable to convert UUID: %s", lua_tostring(L, -1)); } chr->uuid = (ble_uuid_t *) uuids++; lua_pop(L, 1); // pop off the uuid if (lua_getfield(L, -1, "value") != LUA_TNIL) { chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE; int flags = 0; lua_getfield(L, -2, "read"); if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) { flags = BLE_GATT_CHR_F_READ; } lua_getfield(L, -3, "write"); if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) { flags |= BLE_GATT_CHR_F_WRITE; } if (flags) { chr->flags = flags; } lua_pop(L, 3); // pop off value, read, write } else { lua_getfield(L, -2, "read"); if (!lua_isnoneornil (L, -1)) { luaL_checkfunction (L, -1); chr->flags |= BLE_GATT_CHR_F_READ; } lua_getfield(L, -3, "write"); if (!lua_isnoneornil (L, -1)) { luaL_checkfunction (L, -1); chr->flags |= BLE_GATT_CHR_F_WRITE; } lua_pop(L, 3); // pop off value, read, write } if (lua_getfield(L, -1, "notify") != LUA_TNIL) { chr->flags |= BLE_GATT_CHR_F_NOTIFY; handles[0]++; lua_pushinteger(L, handles[0]); lua_setfield(L, -3, "notify"); chr->val_handle = &handles[handles[0]]; } lua_pop(L, 1); // -1 is now the characteristic again chr->arg = (void *) luaL_ref(L, LUA_REGISTRYINDEX); chr->access_cb = lble_access_cb; if (lua_getfield(L, -1, "name") != LUA_TNIL) { if ((void *) (dsc + 2) > eom) { free_gatt_svcs(L, result); return luaL_error(L, "Miscalculated memory requirements"); } chr->descriptors = dsc; dsc->uuid = (const ble_uuid_t*)(ble_uuid16_t[]) { BLE_UUID16_INIT(0x2901) }; dsc->att_flags = BLE_ATT_F_READ; dsc->access_cb = lble_access_cb; dsc->arg = chr->arg; dsc += 2; } lua_pop(L, 1); } lua_pop(L, 2); chrs++; // terminate the list of characteristics for this service } lua_pop(L, 1); *resultp = result; *handlep = handles; return 0; } static int gatt_svr_init(lua_State *L) { int rc; // Now we have to build the gatt_svr_svcs data structure struct ble_gatt_svc_def *svcs = NULL; lble_build_gatt_svcs(L, &svcs, ¬ify_handles); gatt_svr_svcs = svcs; rc = ble_gatts_count_cfg(gatt_svr_svcs); if (rc != 0) { return luaL_error(L, "Failed to count gatts: %d", rc); } rc = ble_gatts_add_svcs(gatt_svr_svcs); if (rc != 0) { return luaL_error(L, "Failed to add gatts: %d", rc); } return 0; } /** * Logs information about a connection to the console. */ static void lble_print_conn_desc(struct ble_gap_conn_desc *desc) { MODLOG_DFLT(INFO, "handle=%d our_ota_addr_type=%d our_ota_addr=", desc->conn_handle, desc->our_ota_addr.type); print_addr(desc->our_ota_addr.val); MODLOG_DFLT(INFO, " our_id_addr_type=%d our_id_addr=", desc->our_id_addr.type); print_addr(desc->our_id_addr.val); MODLOG_DFLT(INFO, " peer_ota_addr_type=%d peer_ota_addr=", desc->peer_ota_addr.type); print_addr(desc->peer_ota_addr.val); MODLOG_DFLT(INFO, " peer_id_addr_type=%d peer_id_addr=", desc->peer_id_addr.type); print_addr(desc->peer_id_addr.val); MODLOG_DFLT(INFO, " conn_itvl=%d conn_latency=%d supervision_timeout=%d " "encrypted=%d authenticated=%d bonded=%d\n", desc->conn_itvl, desc->conn_latency, desc->supervision_timeout, desc->sec_state.encrypted, desc->sec_state.authenticated, desc->sec_state.bonded); } static int lble_sys_init(lua_State *L) { task_handle = task_get_id(lble_task_cb); response_queue = xQueueCreate(2, sizeof(response_message_t)); return 0; } static void lble_host_task(void *param) { nimble_port_run(); //This function will return only when nimble_port_stop() is executed. nimble_port_freertos_deinit(); } static void lble_init_stack(lua_State *L) { static char stack_inited; if (!stack_inited) { stack_inited = 1; int ret = esp_nimble_hci_and_controller_init(); if (ret != ESP_OK) { luaL_error(L, "esp_nimble_hci_and_controller_init() failed with error: %d", ret); return; } } nimble_port_init(); //Initialize the NimBLE Host configuration nimble_port_freertos_init(lble_host_task); } static int lble_gap_event(struct ble_gap_event *event, void *arg) { struct ble_gap_conn_desc desc; int rc; switch (event->type) { case BLE_GAP_EVENT_CONNECT: /* A new connection was established or a connection attempt failed. */ MODLOG_DFLT(INFO, "connection %s; status=%d ", event->connect.status == 0 ? "established" : "failed", event->connect.status); if (event->connect.status == 0) { rc = ble_gap_conn_find(event->connect.conn_handle, &desc); assert(rc == 0); lble_print_conn_desc(&desc); } MODLOG_DFLT(INFO, "\n"); if (event->connect.status != 0) { /* Connection failed; resume advertising. */ lble_start_advertising(); } return 0; case BLE_GAP_EVENT_DISCONNECT: MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason); lble_print_conn_desc(&event->disconnect.conn); MODLOG_DFLT(INFO, "\n"); /* Connection terminated; resume advertising. */ lble_start_advertising(); return 0; case BLE_GAP_EVENT_CONN_UPDATE: /* The central has updated the connection parameters. */ MODLOG_DFLT(INFO, "connection updated; status=%d ", event->conn_update.status); rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc); assert(rc == 0); lble_print_conn_desc(&desc); MODLOG_DFLT(INFO, "\n"); return 0; case BLE_GAP_EVENT_ADV_COMPLETE: MODLOG_DFLT(INFO, "advertise complete; reason=%d", event->adv_complete.reason); lble_start_advertising(); return 0; case BLE_GAP_EVENT_ENC_CHANGE: /* Encryption has been enabled or disabled for this connection. */ MODLOG_DFLT(INFO, "encryption change event; status=%d ", event->enc_change.status); rc = ble_gap_conn_find(event->connect.conn_handle, &desc); assert(rc == 0); lble_print_conn_desc(&desc); MODLOG_DFLT(INFO, "\n"); return 0; case BLE_GAP_EVENT_SUBSCRIBE: MODLOG_DFLT(INFO, "subscribe event; conn_handle=%d attr_handle=%d " "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", event->subscribe.conn_handle, event->subscribe.attr_handle, event->subscribe.reason, event->subscribe.prev_notify, event->subscribe.cur_notify, event->subscribe.prev_indicate, event->subscribe.cur_indicate); return 0; case BLE_GAP_EVENT_MTU: MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value); return 0; case BLE_GAP_EVENT_REPEAT_PAIRING: /* We already have a bond with the peer, but it is attempting to * establish a new secure link. This app sacrifices security for * convenience: just throw away the old bond and accept the new link. */ /* Delete the old bond. */ rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); assert(rc == 0); ble_store_util_delete_peer(&desc.peer_id_addr); /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should * continue with the pairing operation. */ return BLE_GAP_REPEAT_PAIRING_RETRY; } return 0; } static int lble_update_adv_fields() { struct ble_hs_adv_fields fields; const char *name = gadget_name; /** * Set the advertisement data included in our advertisements: * o Flags (indicates advertisement type and other general info). * o Advertising tx power. * o Device name. * o 16-bit service UUIDs (alert notifications). */ memset(&fields, 0, sizeof fields); /* Advertise two flags: * o Discoverability in forthcoming advertisement (general) * o BLE-only (BR/EDR unsupported). */ fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; /* Indicate that the TX power level field should be included; have the * stack fill this value automatically. This is done by assiging the * special value BLE_HS_ADV_TX_PWR_LVL_AUTO. */ fields.tx_pwr_lvl_is_present = 1; fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; fields.mfg_data = gadget_mfg; fields.mfg_data_len = gadget_mfg_len; size_t name_length = strlen(name); fields.name = (uint8_t *)name; fields.name_len = name_length; fields.name_is_complete = 1; // See if we can fit the whole name or need to truncate while (1) { int rc = ble_gap_adv_set_fields(&fields); if (!rc) { break; } if (!fields.name_len) { MODLOG_DFLT(INFO, "error setting advertisement data; rc=%d", rc); return rc; } fields.name_len = fields.name_len - 1; fields.name_is_complete = 0; } if (!fields.name_is_complete) { struct ble_hs_adv_fields scan_response_fields; memset(&scan_response_fields, 0, sizeof scan_response_fields); scan_response_fields.name = (uint8_t *)name; scan_response_fields.name_len = name_length; scan_response_fields.name_is_complete = 1; int rc = ble_gap_adv_rsp_set_fields(&scan_response_fields); if (rc) { MODLOG_DFLT(INFO, "gap_adv_rsp_set_fields failed: %d", rc); return rc; } } return 0; } static void lble_start_advertising() { uint8_t own_addr_type; struct ble_gap_adv_params adv_params; int rc; if (inited != RUNNING) { return; } /* Figure out address to use while advertising (no privacy for now) */ rc = ble_hs_id_infer_auto(0, &own_addr_type); if (rc != 0) { MODLOG_DFLT(INFO, "error determining address type; rc=%d", rc); return; } rc = lble_update_adv_fields(); if (rc != 0) { return; } /* Begin advertising. */ memset(&adv_params, 0, sizeof adv_params); adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, lble_gap_event, NULL); if (rc != 0) { MODLOG_DFLT(INFO, "error enabling advertisement; rc=%d", rc); return; } return; } static void lble_on_sync(void) { int rc; rc = ble_hs_util_ensure_addr(0); assert(rc == 0); if (synced == READY_TO_ADVERTISE) { lble_start_advertising(); synced = ADVERTISING; } if (synced == IDLE) { synced = SYNCED; } } void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { char buf[BLE_UUID_STR_LEN]; switch (ctxt->op) { case BLE_GATT_REGISTER_OP_SVC: MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n", ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), ctxt->svc.handle); break; case BLE_GATT_REGISTER_OP_CHR: MODLOG_DFLT(DEBUG, "registering characteristic %s with " "def_handle=%d val_handle=%d\n", ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), ctxt->chr.def_handle, ctxt->chr.val_handle); break; case BLE_GATT_REGISTER_OP_DSC: MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n", ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), ctxt->dsc.handle); break; default: assert(0); break; } } static int lble_update_adv_change(lua_State *L) { free((void *) gadget_mfg); gadget_mfg = NULL; gadget_mfg_len = 0; if (!lua_isnoneornil(L, -1)) { size_t len; const char *mfg = lua_tolstring(L, 1, &len); gadget_mfg = malloc(len); if (!gadget_mfg) { return luaL_error(L, "out of memory"); } gadget_mfg_len = len; memcpy(gadget_mfg, mfg, len); } return 0; } static int lble_update_adv(lua_State *L) { lua_pushvalue(L, 1); lble_update_adv_change(L); if (lble_update_adv_fields()) { return luaL_error(L, "Unable to update advertisement"); } return 0; } static int lble_notify(lua_State *L) { if (inited != RUNNING) { return luaL_error(L, "ble is not yet running"); } int handle = luaL_checkinteger(L, 1); luaL_argcheck(L, handle > 0 && handle <= notify_handles[0], 1, "handle out of range"); ble_gatts_chr_updated(notify_handles[handle]); return 0; } static int lble_init(lua_State *L) { if (inited != STOPPED) { return luaL_error(L, "ble is already running"); } if (already_inited) { return luaL_error(L, "Can only call ble.init once. Internal stack problem."); } already_inited = true; if (!struct_pack_index) { lua_getglobal(L, "struct"); lua_getfield(L, -1, "pack"); struct_pack_index = luaL_ref(L, LUA_REGISTRYINDEX); lua_getfield(L, -1, "unpack"); struct_unpack_index = luaL_ref(L, LUA_REGISTRYINDEX); lua_pop(L, 1); } // Passed the config table luaL_checktype(L, 1, LUA_TTABLE); lua_getfield(L, 1, "name"); const char *name = strdup(luaL_checkstring(L, -1)); free((void *) gadget_name); gadget_name = name; int rc; lua_getfield(L, 1, "advertisement"); rc = lble_update_adv_change(L); lua_pop(L, 1); synced = IDLE; lble_init_stack(L); /* Initialize the NimBLE host configuration. */ // ble_hs_cfg.reset_cb = bleprph_on_reset; ble_hs_cfg.sync_cb = lble_on_sync; ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; ble_hs_cfg.store_status_cb = ble_store_util_status_rr; rc = gatt_svr_init(L); if (rc) { luaL_error(L, "Failed to gatt_svr_init: %d", rc); } bool seen1800 = false; // See if we already have the 1800 service registered for (const struct ble_gatt_svc_def *svcs = gatt_svr_svcs; svcs->type; svcs++) { if (!ble_uuid_cmp(svcs->uuid, BLE_UUID16_DECLARE(0x1800))) { seen1800 = true; } } if (!seen1800) { ble_svc_gap_init(); } ble_svc_gatt_init(); ble_gatts_start(); inited = RUNNING; if (synced == SYNCED) { lble_start_advertising(); synced = ADVERTISING; } else { synced = READY_TO_ADVERTISE; } return 0; } static int lble_shutdown(lua_State *L) { // It seems that shutting down the stack corrupts some critical data structures // so, for now, don't allow it. luaL_error(L, "Shutting down the BLE stack is currently not possible"); inited = SHUTTING; ble_gap_adv_stop(); if (nimble_port_stop()) { return luaL_error(L, "Failed to stop the NIMBLE task"); } nimble_port_deinit(); if (ESP_OK != esp_nimble_hci_and_controller_deinit()) { return luaL_error(L, "Failed to shutdown the BLE controller"); } inited = STOPPED; return 0; } LROT_BEGIN(lble, NULL, 0) LROT_FUNCENTRY( init, lble_init ) LROT_FUNCENTRY( notify, lble_notify ) LROT_FUNCENTRY( advertise, lble_update_adv ) LROT_FUNCENTRY( shutdown, lble_shutdown ) LROT_END(lble, NULL, 0) NODEMCU_MODULE(BLE, "ble", lble, lble_sys_init); #endif