From 2cf72b4f8b44232bc1d37133877b3a6b8e5c6938 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sat, 16 Oct 2021 20:05:33 +0000 Subject: [PATCH 01/27] It compiles!! --- components/modules/CMakeLists.txt | 1 + components/modules/Kconfig | 7 + components/modules/ble.c | 1378 +++++++++++++++++++++++++++++ docs/modules/ble.md | 81 ++ install.sh | 2 - 5 files changed, 1467 insertions(+), 2 deletions(-) create mode 100644 components/modules/ble.c create mode 100644 docs/modules/ble.md diff --git a/components/modules/CMakeLists.txt b/components/modules/CMakeLists.txt index 5d96d4976..6d1b0bd5f 100644 --- a/components/modules/CMakeLists.txt +++ b/components/modules/CMakeLists.txt @@ -2,6 +2,7 @@ set(module_srcs "adc.c" "bit.c" + "ble.c" "bthci.c" "common.c" "crypto.c" diff --git a/components/modules/Kconfig b/components/modules/Kconfig index 749e01558..e8bb52c1d 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -14,6 +14,13 @@ menu "NodeMCU modules" Includes the bit module. This module provide bit manipulation functions on Lua numbers. + config NODEMCU_CMODULE_BLE + bool "BlueTooth GAP/GATT interface module" + default "n" + select BLE_ENABLED + help + Includes the simple BlueTooth GAP/GATT module. + config NODEMCU_CMODULE_BTHCI bool "BlueTooth HCI interface module" default "n" diff --git a/components/modules/ble.c b/components/modules/ble.c new file mode 100644 index 000000000..c9e041629 --- /dev/null +++ b/components/modules/ble.c @@ -0,0 +1,1378 @@ +/* + * 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 "nvs_flash.h" +#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" + +/* 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" + +static int lble_start_advertising(); +static int lble_gap_event(struct ble_gap_event *event, void *arg); + +static const char *gadget_name; + +static const struct ble_gatt_svc_def *gatt_svr_svcs; + +static task_handle_t task_handle; +static QueueHandle_t response_queue; + +static int struct_pack_index; +static int struct_unpack_index; + +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; + +#undef MODLOG_DFLT +#define MODLOG_DFLT(level, ...) printf(__VA_ARGS__) + +/** + * Utility function to log an array of bytes. + */ +static void +print_bytes(const uint8_t *bytes, int len) +{ + int i; + + for (i = 0; i < len; i++) { + MODLOG_DFLT(INFO, "%s0x%02x", i != 0 ? ":" : "", bytes[i]); + } +} + +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]); +} + + + +#if 0 + + +#include +#include +#include +#include "bsp/bsp.h" +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "bleprph.h" + +/** + * The vendor specific security test service consists of two characteristics: + * o random-number-generator: generates a random 32-bit number each time + * it is read. This characteristic can only be read over an encrypted + * connection. + * o static-value: a single-byte characteristic that can always be read, + * but can only be written over an encrypted connection. + */ + +/* 59462f12-9543-9999-12c8-58b459a2712d */ +static const ble_uuid128_t gatt_svr_svc_sec_test_uuid = + BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12, + 0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59); + +/* 5c3a659e-897e-45e1-b016-007107c96df6 */ +static const ble_uuid128_t gatt_svr_chr_sec_test_rand_uuid = + BLE_UUID128_INIT(0xf6, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, + 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); + +/* 5c3a659e-897e-45e1-b016-007107c96df7 */ +static const ble_uuid128_t gatt_svr_chr_sec_test_static_uuid = + BLE_UUID128_INIT(0xf7, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, + 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); + +static uint8_t gatt_svr_sec_test_static_val; + +static int +gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +static const struct ble_gatt_svc_def gatt_svr_svcs[] = { + { + /*** Service: Security test. */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &gatt_svr_svc_sec_test_uuid.u, + .characteristics = (struct ble_gatt_chr_def[]) { { + /*** Characteristic: Random number generator. */ + .uuid = &gatt_svr_chr_sec_test_rand_uuid.u, + .access_cb = gatt_svr_chr_access_sec_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, + }, { + /*** Characteristic: Static value. */ + .uuid = &gatt_svr_chr_sec_test_static_uuid.u, + .access_cb = gatt_svr_chr_access_sec_test, + .flags = BLE_GATT_CHR_F_READ | + BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC, + }, { + 0, /* No more characteristics in this service. */ + } }, + }, + + { + 0, /* No more services. */ + }, +}; + +static int +gatt_svr_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, + void *dst, uint16_t *len) +{ + uint16_t om_len; + int rc; + + om_len = OS_MBUF_PKTLEN(om); + if (om_len < min_len || om_len > max_len) { + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + rc = ble_hs_mbuf_to_flat(om, dst, max_len, len); + if (rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + + return 0; +} + +static int +gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg) +{ + const ble_uuid_t *uuid; + int rand_num; + int rc; + + uuid = ctxt->chr->uuid; + + /* Determine which characteristic is being accessed by examining its + * 128-bit UUID. + */ + + if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_rand_uuid.u) == 0) { + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); + + /* Respond with a 32-bit random number. */ + rand_num = rand(); + rc = os_mbuf_append(ctxt->om, &rand_num, sizeof rand_num); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_static_uuid.u) == 0) { + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + rc = os_mbuf_append(ctxt->om, &gatt_svr_sec_test_static_val, + sizeof gatt_svr_sec_test_static_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + + case BLE_GATT_ACCESS_OP_WRITE_CHR: + rc = gatt_svr_chr_write(ctxt->om, + sizeof gatt_svr_sec_test_static_val, + sizeof gatt_svr_sec_test_static_val, + &gatt_svr_sec_test_static_val, NULL); + return rc; + + default: + assert(0); + return BLE_ATT_ERR_UNLIKELY; + } + } + + /* Unknown characteristic; the nimble stack should not have called this + * function. + */ + assert(0); + return BLE_ATT_ERR_UNLIKELY; +} + +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; + } +} + +int +gatt_svr_init(void) +{ + int rc; + + rc = ble_gatts_count_cfg(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + rc = ble_gatts_add_svcs(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + return 0; +} + + +/** + * Logs information about a connection to the console. + */ +static void +bleprph_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); +} + +/** + * Enables advertising with the following parameters: + * o General discoverable mode. + * o Undirected connectable mode. + */ +static void +bleprph_advertise(void) +{ + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + const char *name; + int rc; + + /** + * 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 assigning 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; + + name = ble_svc_gap_device_name(); + fields.name = (uint8_t *)name; + fields.name_len = strlen(name); + fields.name_is_complete = 1; + + fields.uuids16 = (ble_uuid16_t[]) { + BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID) + }; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc); + 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, bleprph_gap_event, NULL); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc); + return; + } +} + +/** + * The nimble host executes this callback when a GAP event occurs. The + * application associates a GAP event callback with each connection that forms. + * bleprph uses the same callback for all connections. + * + * @param event The type of event being signalled. + * @param ctxt Various information pertaining to the event. + * @param arg Application-specified argument; unused by + * bleprph. + * + * @return 0 if the application successfully handled the + * event; nonzero on failure. The semantics + * of the return code is specific to the + * particular GAP event being signalled. + */ +static int +bleprph_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); + bleprph_print_conn_desc(&desc); + } + MODLOG_DFLT(INFO, "\n"); + + if (event->connect.status != 0) { + /* Connection failed; resume advertising. */ + bleprph_advertise(); + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason); + bleprph_print_conn_desc(&event->disconnect.conn); + MODLOG_DFLT(INFO, "\n"); + + /* Connection terminated; resume advertising. */ + bleprph_advertise(); + 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); + bleprph_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); + bleprph_advertise(); + 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->enc_change.conn_handle, &desc); + assert(rc == 0); + bleprph_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; + + case BLE_GAP_EVENT_PASSKEY_ACTION: + ESP_LOGI(tag, "PASSKEY_ACTION_EVENT started \n"); + struct ble_sm_io pkey = {0}; + int key = 0; + + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; // This is the passkey to be entered on peer + ESP_LOGI(tag, "Enter passkey %d on the peer side", pkey.passkey); + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + ESP_LOGI(tag, "Passkey on device's display: %d", event->passkey.params.numcmp); + ESP_LOGI(tag, "Accept or reject the passkey through console in this format -> key Y or key N"); + pkey.action = event->passkey.params.action; + if (scli_receive_key(&key)) { + pkey.numcmp_accept = key; + } else { + pkey.numcmp_accept = 0; + ESP_LOGE(tag, "Timeout! Rejecting the key"); + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { + static uint8_t tem_oob[16] = {0}; + pkey.action = event->passkey.params.action; + for (int i = 0; i < 16; i++) { + pkey.oob[i] = tem_oob[i]; + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { + ESP_LOGI(tag, "Enter the passkey through console in this format-> key 123456"); + pkey.action = event->passkey.params.action; + if (scli_receive_key(&key)) { + pkey.passkey = key; + } else { + pkey.passkey = 0; + ESP_LOGE(tag, "Timeout! Passing 0 as the key"); + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); + } + return 0; + } + + return 0; +} + +static void +bleprph_on_reset(int reason) +{ + MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); +} + +static void +bleprph_on_sync(void) +{ + int rc; + + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + /* 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(ERROR, "error determining address type; rc=%d\n", rc); + return; + } + + /* Printing ADDR */ + uint8_t addr_val[6] = {0}; + rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL); + + MODLOG_DFLT(INFO, "Device Address: "); + print_addr(addr_val); + MODLOG_DFLT(INFO, "\n"); + /* Begin advertising. */ + bleprph_advertise(); +} + +void bleprph_host_task(void *param) +{ + ESP_LOGI(tag, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} + +void +app_main(void) +{ + int rc; + + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init()); + + nimble_port_init(); + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = bleprph_on_reset; + ble_hs_cfg.sync_cb = bleprph_on_sync; + ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE; +#ifdef CONFIG_EXAMPLE_BONDING + ble_hs_cfg.sm_bonding = 1; +#endif +#ifdef CONFIG_EXAMPLE_MITM + ble_hs_cfg.sm_mitm = 1; +#endif +#ifdef CONFIG_EXAMPLE_USE_SC + ble_hs_cfg.sm_sc = 1; +#else + ble_hs_cfg.sm_sc = 0; +#endif +#ifdef CONFIG_EXAMPLE_BONDING + ble_hs_cfg.sm_our_key_dist = 1; + ble_hs_cfg.sm_their_key_dist = 1; +#endif + + + rc = gatt_svr_init(); + assert(rc == 0); + + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("nimble-bleprph"); + assert(rc == 0); + + /* XXX Need to have template for store */ + ble_store_config_init(); + + nimble_port_freertos_init(bleprph_host_task); + + /* Initialize command line interface to accept input from user */ + rc = scli_init(); + if (rc != ESP_OK) { + ESP_LOGE(tag, "scli_init() failed"); + } +} +#endif +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) { + // Actually the only thing we care about is the arg and the ctxt + + 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)) { + 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) (10000/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) { + 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(); + lua_rawgeti(L, LUA_REGISTRYINDEX, (int) task_block->arg); + // Now we have the characteristic table in -1 + lua_getfield(L, -1, "struct"); + // -1 is the struct mapping (if any), -2 is the table + + 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. + 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, -5); + for (int i = 1; i < nv; i++) { + lua_rawgeti(L, -4 - i, i); + } + if (lua_pcall(L, nv + 1, 1, 0)) { + 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) + } + size_t datalen; + const char *data = lua_tolstring(L, -1, &datalen); + + if (data) { + message.buffer = malloc(datalen); + if (message.buffer) { + message.length = datalen; + memcpy(message.buffer, data, datalen); + message.errcode = 0; + } + } + lua_pop(L, 1); + } + 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, -3); // dup the format + lua_pushvalue(L, -3); // dup the string + if (lua_pcall(L, 2, LUA_MULTRET, 0)) { + message.errcode = BLE_ATT_ERR_UNLIKELY; + lua_pop(L, 2); + goto cleanup; + } + int vals = lua_gettop(L) - stack_size; + + 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 + } + lua_getfield(L, -3, "write"); + if (!lua_isnoneornil(L, -1)) { + lua_pushvalue(L, -2); // the values table + if (lua_pcall(L, 1, 0, 0)) { + message.errcode = BLE_ATT_ERR_UNLIKELY; + lua_pop(L, 2); + goto cleanup; + } + } + lua_pop(L, 1); // Throw away the null write pointer + // just save the result in the value + lua_setfield(L, -3, "value"); + } + +cleanup: + lua_pop(L, 2); + 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) { + // 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"); + int ns = lua_rawlen(L, -1); + + // -1 is the services list + int nc = 0; + for (int i = 1; i <= ns; i++) { + 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"); + nc += lua_rawlen(L, -1); + lua_pop(L, 2); + } + + 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); + + printf("Computed size: %d\n", size); + + 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); + + // Now fill out the data structure + // -1 is the services list + for (int i = 1; i <= ns; i++) { + 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)); + } + if (i == 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++) { + 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; + lua_pop(L, 1); // pop off value + } else { + lua_getfield(L, -1, "read"); + if (!lua_isnoneornil (L, -1)) { + luaL_checkfunction (L, -1); + chr->flags |= BLE_GATT_CHR_F_READ; + } + + lua_getfield(L, -1, "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 + } + + // -1 is now the characteristic again + chr->arg = (void *) luaL_ref(L, LUA_REGISTRYINDEX); + chr->access_cb = lble_access_cb; + } + lua_pop(L, 2); + } + lua_pop(L, 1); + + *resultp = result; + + return 0; +} + +static int +gatt_svr_init(lua_State *L) { + int rc; + + ble_svc_gap_init(); + ble_svc_gatt_init(); + + // Now we have to build the gatt_svr_svcs data structure + + + struct ble_gatt_svc_def *svcs = NULL; + lble_build_gatt_svcs(L, &svcs); + free_gatt_svcs(L, gatt_svr_svcs); + 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) { + int rc = nvs_flash_init(); + if (rc == ESP_ERR_NVS_NO_FREE_PAGES || rc == ESP_ERR_NVS_NEW_VERSION_FOUND) { + rc = nvs_flash_erase(); + if (rc) { + return luaL_error(L, "Failed to erase flash: %d", rc); + } + rc = nvs_flash_init(); + } + if (rc) { + return luaL_error(L, "Failed to init flash: %d", rc); + } + + 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) { + 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) +{ + printf("GAP event %d\n", event->type); + 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. */ + printf("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); + + } + printf("\n"); + + if (event->connect.status != 0) { + /* Connection failed; resume advertising. */ + lble_start_advertising(); + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + printf("disconnect; reason=%d ", event->disconnect.reason); + lble_print_conn_desc(&event->disconnect.conn); + printf("\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_start_advertising() { + uint8_t own_addr_type; + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + const char *name = gadget_name; + int rc; + + /* Figure out address to use while advertising (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + return printf("error determining address type; rc=%d", rc); + } + + /** + * 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; + + size_t name_length = strlen(name); + if (name_length < 16) { + fields.name = (uint8_t *)name; + fields.name_len = name_length; + fields.name_is_complete = 1; + } else { + fields.name = (uint8_t *)name; + fields.name_len = 16; + fields.name_is_complete = 0; + + 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; + rc = ble_gap_adv_rsp_set_fields(&scan_response_fields); + if (rc) { + return printf("gap_adv_rsp_set_fields failed: %d", rc); + } + } + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + return printf("error setting advertisement data; rc=%d", rc); + } + + /* 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) { + return printf("error enabling advertisement; rc=%d", rc); + } + + return 0; +} + +static void +lble_on_sync(void) +{ + int rc; + + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + lble_start_advertising(); +} + + +static int lble_init(lua_State *L) { + 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); + + lble_init_stack(L); + + lua_getfield(L, 1, "name"); + const char *name = strdup(luaL_checkstring(L, -1)); + + free((void *) gadget_name); + gadget_name = name; + + int rc; + + /* 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); + } + + return 0; +} + +static int lble_shutdown(lua_State *L) { + return 0; +} + +LROT_BEGIN(lble, NULL, 0) + LROT_FUNCENTRY( init, lble_init ) + LROT_FUNCENTRY( shutdown, lble_shutdown ) +LROT_END(lble, NULL, 0) + +NODEMCU_MODULE(BLE, "ble", lble, lble_sys_init); +#endif diff --git a/docs/modules/ble.md b/docs/modules/ble.md new file mode 100644 index 000000000..7087871fd --- /dev/null +++ b/docs/modules/ble.md @@ -0,0 +1,81 @@ +# BT HCI Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2021-10-10 | [pjsg](https://github.com/pjsg) | [ble.c](../../components/modules/ble.c)| + +The BLE module provides a simple interface to allow implementation of a simple GAP/GATT server. +This allows you to build simple gadgets that can be interrogated and controlled over BLE. + +## ble.init(configuration) + +This initializes the BlueTooth stack and starts advertising according to the data in the +configuration table. See below for a detailed description of this table. + +#### Syntax +`ble.init(ble_config)` + +#### Parameters +- `ble_config` A table with the keys as defined below. + +##### Returns +`nil` + +#### Example +```lua +local config = {name="MyGadget=", services={{uuid="0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}}}} +ble.init(config) +``` + +## bthci.shutdown(callback) + +Shuts down the BlueTooth controller and returns it to the state where another `init` can be performed. + +#### Syntax +`ble.shutdown([callback])` + +#### Parameters +- `callback` optional function to be invoked when the shutdown completes. Its + only argument is an error code, or `nil` on success. + +#### Returns +`nil` + +#### Example +```lua +ble.shutdown(function(err) print(err or "Ok!") end) +``` + +## Conventions + +## Configuration Table + +The configuration table contains the following keys: + +- `name` The name to use to advertise the gadget + +- `services` This is a list of tables that define the individual services. The primary service is the first service. Many examples will only have a single service. + + +### Service table + +The service table contains the following keys: + +- `uuid` The UUID of the service. This is a 16 byte string (128 bits) that identifies the particular service. It can also be a two byte string for a well-known service. +- `characteristics` This is a list of tables, where each entry describes a characateristic (attribute) +- `preread` This is an optional function that is invoked just before a read of any characteristic in this service. +- `postwrite` This is an optional function that is invoked just after a write of any characteristic in this service. + +### Characteristic table + +The characteristic table contains the following keys: + +- `uuid` The UUID of the characteristics. This can be either a 16 byte string or a 2 byte string that identifies the particular characteristic. Typically, 2 byte strings are used for well-known characteristics. +- `type` This is the optional type of the value. It has the same value as a unpack code in the `struct` module. +- `value` This is the actual value of the characteristic. This will be a string of bytes unless a `type` value is set. +- `read` This is a function that will be invoked to read the value (and so does not need the `value` entry). It should return a string of bytes (unless `type` is set) +- `write` This is a function that will be invoked to write the value (and so does not need the `value` entry). It is given a string of bytes (unless `type` is set) + +The characteristics are treated as read/write unless only one of the `read` or `write` keys is present. + +The calling conventions for these functions are TBD. + diff --git a/install.sh b/install.sh index cf18df457..c93e51210 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,5 @@ #!/bin/bash -set -e - echo "Installing IDF prerequisites..." IDF_DIR=./sdk/esp32-esp-idf From 4a5539777899dc6f14650961bcee1c14a4c49964 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sat, 16 Oct 2021 22:38:07 +0000 Subject: [PATCH 02/27] Seems to sort of work. --- components/modules/ble.c | 99 ++++++++++++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 18 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index c9e041629..c39c7f9e5 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -46,6 +46,8 @@ #include "nimble/ble.h" #include "task/task.h" +#define PERROR() printf("pcall failed: %s\n", lua_tostring(L, -1)) + static int lble_start_advertising(); static int lble_gap_event(struct ble_gap_event *event, void *arg); @@ -737,6 +739,8 @@ static int lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { // Actually the only thing we care about is the arg and the ctxt + printf("access_cb called with op %d\n", ctxt->op); + size_t task_block_size = sizeof(task_block_t); if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { @@ -762,21 +766,25 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces return BLE_ATT_ERR_UNLIKELY; } } + + printf("ABout to task_post\n"); 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; + response_message_t message; while (1) { + printf("About to receive\n"); + if (xQueueReceive(response_queue, &message, (TickType_t) (10000/portTICK_PERIOD_MS) ) != pdPASS) { free(task_block); return BLE_ATT_ERR_UNLIKELY; } - if (message->seqno == task_block->seqno) { + if (message.seqno == task_block->seqno) { break; } } @@ -784,17 +792,17 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces int rc = BLE_ATT_ERR_UNLIKELY; if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { - rc = message->errcode; + rc = message.errcode; if (rc == 0) { - if (os_mbuf_append(ctxt->om, message->buffer, message->length)) { + 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; + rc = message.errcode; } - free(message->buffer); + free(message.buffer); free(task_block); return rc; @@ -811,7 +819,7 @@ lble_task_cb(task_param_t param, task_prio_t prio) { lua_State *L = lua_getstate(); lua_rawgeti(L, LUA_REGISTRYINDEX, (int) task_block->arg); // Now we have the characteristic table in -1 - lua_getfield(L, -1, "struct"); + lua_getfield(L, -1, "type"); // -1 is the struct mapping (if any), -2 is the table if (task_block->ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { @@ -821,6 +829,7 @@ lble_task_cb(task_param_t param, task_prio_t prio) { 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; @@ -832,23 +841,26 @@ lble_task_cb(task_param_t param, task_prio_t prio) { // Now we have the value (-1), struct (-2), table (-3) if (!lua_isnoneornil(L, -2)) { // need to convert value + printf("About to convert vaclue for read\n"); 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 + printf("wrapped in table\n"); } // 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, -5); - for (int i = 1; i < nv; i++) { - lua_rawgeti(L, -4 - i, i); + 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; @@ -868,6 +880,7 @@ lble_task_cb(task_param_t param, task_prio_t prio) { } } lua_pop(L, 1); + message.errcode = 0; } if (task_block->ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { // Push the value @@ -879,16 +892,19 @@ lble_task_cb(task_param_t param, task_prio_t prio) { lua_createtable(L, 0, 0); int stack_size = lua_gettop(L); lua_rawgeti(L, LUA_REGISTRYINDEX, struct_unpack_index); - lua_pushvalue(L, -3); // dup the format - lua_pushvalue(L, -3); // dup the string + 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; + printf("unpacked %d vals\n", vals - 1); - for (int i = 1; i <= vals; i++) { + // 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); } @@ -906,6 +922,7 @@ lble_task_cb(task_param_t param, task_prio_t prio) { if (!lua_isnoneornil(L, -1)) { lua_pushvalue(L, -2); // the values table if (lua_pcall(L, 1, 0, 0)) { + PERROR(); message.errcode = BLE_ATT_ERR_UNLIKELY; lua_pop(L, 2); goto cleanup; @@ -914,9 +931,11 @@ lble_task_cb(task_param_t param, task_prio_t prio) { lua_pop(L, 1); // Throw away the null write pointer // just save the result in the value lua_setfield(L, -3, "value"); + message.errcode = 0; } cleanup: + printf("Returning code %d\n", message.errcode); lua_pop(L, 2); message.seqno = task_block->seqno; @@ -930,7 +949,12 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { // + number of characteristics (nc) + ns * sizeof(ble_gatt_chr_def) // + ns + nc * sizeof(ble_uuid_any_t) + printf("build_gatt_svcs\n"); + 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 @@ -953,7 +977,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { 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); - printf("Computed size: %d\n", size); + printf("Computed size: %d (nc %d, ns %d)\n", size, nc, ns); struct ble_gatt_svc_def *svcs = malloc(size); if (!svcs) { @@ -974,7 +998,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { // -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) { + if ((void *) (uuids + 1) > eom) { free_gatt_svcs(L, result); return luaL_error(L, "Miscalculated memory requirements"); } @@ -998,7 +1022,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { // -1 is now the characteristic lua_getfield(L, -1, "uuid"); // Convert the uuid - if ((void *) (uuids + 1) >= eom) { + if ((void *) (uuids + 1) > eom) { free_gatt_svcs(L, result); return luaL_error(L, "Miscalculated memory requirements"); } @@ -1045,7 +1069,10 @@ static int gatt_svr_init(lua_State *L) { int rc; + printf("about to call gap_init\n"); + ble_svc_gap_init(); + printf("about to call gatt_init\n"); ble_svc_gatt_init(); // Now we have to build the gatt_svr_svcs data structure @@ -1057,6 +1084,7 @@ gatt_svr_init(lua_State *L) { gatt_svr_svcs = svcs; + printf("about to call count_cfg\n"); rc = ble_gatts_count_cfg(gatt_svr_svcs); if (rc != 0) { return luaL_error(L, "Failed to count gatts: %d", rc); @@ -1067,6 +1095,8 @@ gatt_svr_init(lua_State *L) { return luaL_error(L, "Failed to add gatts: %d", rc); } + ble_gatts_start(); + return 0; } @@ -1328,6 +1358,38 @@ lble_on_sync(void) lble_start_advertising(); } +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_init(lua_State *L) { if (!struct_pack_index) { @@ -1341,6 +1403,7 @@ static int lble_init(lua_State *L) { // Passed the config table luaL_checktype(L, 1, LUA_TTABLE); + printf("About to init_stack"); lble_init_stack(L); lua_getfield(L, 1, "name"); @@ -1354,8 +1417,8 @@ static int lble_init(lua_State *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; + 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) { From f8709b964471e161201b02706f22a2c8fbb4a965 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 17 Oct 2021 16:05:21 +0000 Subject: [PATCH 03/27] Fixed the docs and add support for advertising data --- components/modules/ble.c | 590 ++------------------------------------- docs/modules/ble.md | 17 +- 2 files changed, 42 insertions(+), 565 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index c39c7f9e5..ce1b33954 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -52,6 +52,8 @@ static int 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; @@ -85,16 +87,6 @@ typedef struct { /** * Utility function to log an array of bytes. */ -static void -print_bytes(const uint8_t *bytes, int len) -{ - int i; - - for (i = 0; i < len; i++) { - MODLOG_DFLT(INFO, "%s0x%02x", i != 0 ? ":" : "", bytes[i]); - } -} - static void print_addr(const void *addr) { @@ -105,550 +97,6 @@ print_addr(const void *addr) u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); } - - -#if 0 - - -#include -#include -#include -#include "bsp/bsp.h" -#include "host/ble_hs.h" -#include "host/ble_uuid.h" -#include "bleprph.h" - -/** - * The vendor specific security test service consists of two characteristics: - * o random-number-generator: generates a random 32-bit number each time - * it is read. This characteristic can only be read over an encrypted - * connection. - * o static-value: a single-byte characteristic that can always be read, - * but can only be written over an encrypted connection. - */ - -/* 59462f12-9543-9999-12c8-58b459a2712d */ -static const ble_uuid128_t gatt_svr_svc_sec_test_uuid = - BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12, - 0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59); - -/* 5c3a659e-897e-45e1-b016-007107c96df6 */ -static const ble_uuid128_t gatt_svr_chr_sec_test_rand_uuid = - BLE_UUID128_INIT(0xf6, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, - 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); - -/* 5c3a659e-897e-45e1-b016-007107c96df7 */ -static const ble_uuid128_t gatt_svr_chr_sec_test_static_uuid = - BLE_UUID128_INIT(0xf7, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, - 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); - -static uint8_t gatt_svr_sec_test_static_val; - -static int -gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle, - struct ble_gatt_access_ctxt *ctxt, - void *arg); - -static const struct ble_gatt_svc_def gatt_svr_svcs[] = { - { - /*** Service: Security test. */ - .type = BLE_GATT_SVC_TYPE_PRIMARY, - .uuid = &gatt_svr_svc_sec_test_uuid.u, - .characteristics = (struct ble_gatt_chr_def[]) { { - /*** Characteristic: Random number generator. */ - .uuid = &gatt_svr_chr_sec_test_rand_uuid.u, - .access_cb = gatt_svr_chr_access_sec_test, - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, - }, { - /*** Characteristic: Static value. */ - .uuid = &gatt_svr_chr_sec_test_static_uuid.u, - .access_cb = gatt_svr_chr_access_sec_test, - .flags = BLE_GATT_CHR_F_READ | - BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC, - }, { - 0, /* No more characteristics in this service. */ - } }, - }, - - { - 0, /* No more services. */ - }, -}; - -static int -gatt_svr_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, - void *dst, uint16_t *len) -{ - uint16_t om_len; - int rc; - - om_len = OS_MBUF_PKTLEN(om); - if (om_len < min_len || om_len > max_len) { - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - - rc = ble_hs_mbuf_to_flat(om, dst, max_len, len); - if (rc != 0) { - return BLE_ATT_ERR_UNLIKELY; - } - - return 0; -} - -static int -gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle, - struct ble_gatt_access_ctxt *ctxt, - void *arg) -{ - const ble_uuid_t *uuid; - int rand_num; - int rc; - - uuid = ctxt->chr->uuid; - - /* Determine which characteristic is being accessed by examining its - * 128-bit UUID. - */ - - if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_rand_uuid.u) == 0) { - assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); - - /* Respond with a 32-bit random number. */ - rand_num = rand(); - rc = os_mbuf_append(ctxt->om, &rand_num, sizeof rand_num); - return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; - } - - if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_static_uuid.u) == 0) { - switch (ctxt->op) { - case BLE_GATT_ACCESS_OP_READ_CHR: - rc = os_mbuf_append(ctxt->om, &gatt_svr_sec_test_static_val, - sizeof gatt_svr_sec_test_static_val); - return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; - - case BLE_GATT_ACCESS_OP_WRITE_CHR: - rc = gatt_svr_chr_write(ctxt->om, - sizeof gatt_svr_sec_test_static_val, - sizeof gatt_svr_sec_test_static_val, - &gatt_svr_sec_test_static_val, NULL); - return rc; - - default: - assert(0); - return BLE_ATT_ERR_UNLIKELY; - } - } - - /* Unknown characteristic; the nimble stack should not have called this - * function. - */ - assert(0); - return BLE_ATT_ERR_UNLIKELY; -} - -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; - } -} - -int -gatt_svr_init(void) -{ - int rc; - - rc = ble_gatts_count_cfg(gatt_svr_svcs); - if (rc != 0) { - return rc; - } - - rc = ble_gatts_add_svcs(gatt_svr_svcs); - if (rc != 0) { - return rc; - } - - return 0; -} - - -/** - * Logs information about a connection to the console. - */ -static void -bleprph_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); -} - -/** - * Enables advertising with the following parameters: - * o General discoverable mode. - * o Undirected connectable mode. - */ -static void -bleprph_advertise(void) -{ - struct ble_gap_adv_params adv_params; - struct ble_hs_adv_fields fields; - const char *name; - int rc; - - /** - * 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 assigning 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; - - name = ble_svc_gap_device_name(); - fields.name = (uint8_t *)name; - fields.name_len = strlen(name); - fields.name_is_complete = 1; - - fields.uuids16 = (ble_uuid16_t[]) { - BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID) - }; - fields.num_uuids16 = 1; - fields.uuids16_is_complete = 1; - - rc = ble_gap_adv_set_fields(&fields); - if (rc != 0) { - MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc); - 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, bleprph_gap_event, NULL); - if (rc != 0) { - MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc); - return; - } -} - -/** - * The nimble host executes this callback when a GAP event occurs. The - * application associates a GAP event callback with each connection that forms. - * bleprph uses the same callback for all connections. - * - * @param event The type of event being signalled. - * @param ctxt Various information pertaining to the event. - * @param arg Application-specified argument; unused by - * bleprph. - * - * @return 0 if the application successfully handled the - * event; nonzero on failure. The semantics - * of the return code is specific to the - * particular GAP event being signalled. - */ -static int -bleprph_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); - bleprph_print_conn_desc(&desc); - } - MODLOG_DFLT(INFO, "\n"); - - if (event->connect.status != 0) { - /* Connection failed; resume advertising. */ - bleprph_advertise(); - } - return 0; - - case BLE_GAP_EVENT_DISCONNECT: - MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason); - bleprph_print_conn_desc(&event->disconnect.conn); - MODLOG_DFLT(INFO, "\n"); - - /* Connection terminated; resume advertising. */ - bleprph_advertise(); - 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); - bleprph_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); - bleprph_advertise(); - 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->enc_change.conn_handle, &desc); - assert(rc == 0); - bleprph_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; - - case BLE_GAP_EVENT_PASSKEY_ACTION: - ESP_LOGI(tag, "PASSKEY_ACTION_EVENT started \n"); - struct ble_sm_io pkey = {0}; - int key = 0; - - if (event->passkey.params.action == BLE_SM_IOACT_DISP) { - pkey.action = event->passkey.params.action; - pkey.passkey = 123456; // This is the passkey to be entered on peer - ESP_LOGI(tag, "Enter passkey %d on the peer side", pkey.passkey); - rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); - ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); - } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { - ESP_LOGI(tag, "Passkey on device's display: %d", event->passkey.params.numcmp); - ESP_LOGI(tag, "Accept or reject the passkey through console in this format -> key Y or key N"); - pkey.action = event->passkey.params.action; - if (scli_receive_key(&key)) { - pkey.numcmp_accept = key; - } else { - pkey.numcmp_accept = 0; - ESP_LOGE(tag, "Timeout! Rejecting the key"); - } - rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); - ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); - } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { - static uint8_t tem_oob[16] = {0}; - pkey.action = event->passkey.params.action; - for (int i = 0; i < 16; i++) { - pkey.oob[i] = tem_oob[i]; - } - rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); - ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); - } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { - ESP_LOGI(tag, "Enter the passkey through console in this format-> key 123456"); - pkey.action = event->passkey.params.action; - if (scli_receive_key(&key)) { - pkey.passkey = key; - } else { - pkey.passkey = 0; - ESP_LOGE(tag, "Timeout! Passing 0 as the key"); - } - rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); - ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); - } - return 0; - } - - return 0; -} - -static void -bleprph_on_reset(int reason) -{ - MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); -} - -static void -bleprph_on_sync(void) -{ - int rc; - - rc = ble_hs_util_ensure_addr(0); - assert(rc == 0); - - /* 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(ERROR, "error determining address type; rc=%d\n", rc); - return; - } - - /* Printing ADDR */ - uint8_t addr_val[6] = {0}; - rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL); - - MODLOG_DFLT(INFO, "Device Address: "); - print_addr(addr_val); - MODLOG_DFLT(INFO, "\n"); - /* Begin advertising. */ - bleprph_advertise(); -} - -void bleprph_host_task(void *param) -{ - ESP_LOGI(tag, "BLE Host Task Started"); - /* This function will return only when nimble_port_stop() is executed */ - nimble_port_run(); - - nimble_port_freertos_deinit(); -} - -void -app_main(void) -{ - int rc; - - /* Initialize NVS — it is used to store PHY calibration data */ - esp_err_t ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); - } - ESP_ERROR_CHECK(ret); - - ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init()); - - nimble_port_init(); - /* Initialize the NimBLE host configuration. */ - ble_hs_cfg.reset_cb = bleprph_on_reset; - ble_hs_cfg.sync_cb = bleprph_on_sync; - ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; - ble_hs_cfg.store_status_cb = ble_store_util_status_rr; - - ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE; -#ifdef CONFIG_EXAMPLE_BONDING - ble_hs_cfg.sm_bonding = 1; -#endif -#ifdef CONFIG_EXAMPLE_MITM - ble_hs_cfg.sm_mitm = 1; -#endif -#ifdef CONFIG_EXAMPLE_USE_SC - ble_hs_cfg.sm_sc = 1; -#else - ble_hs_cfg.sm_sc = 0; -#endif -#ifdef CONFIG_EXAMPLE_BONDING - ble_hs_cfg.sm_our_key_dist = 1; - ble_hs_cfg.sm_their_key_dist = 1; -#endif - - - rc = gatt_svr_init(); - assert(rc == 0); - - /* Set the default device name. */ - rc = ble_svc_gap_device_name_set("nimble-bleprph"); - assert(rc == 0); - - /* XXX Need to have template for store */ - ble_store_config_init(); - - nimble_port_freertos_init(bleprph_host_task); - - /* Initialize command line interface to accept input from user */ - rc = scli_init(); - if (rc != ESP_OK) { - ESP_LOGE(tag, "scli_init() failed"); - } -} -#endif static int gethexval(char c) { if (c < '0') { @@ -918,20 +366,23 @@ lble_task_cb(task_param_t param, task_prio_t prio) { lua_rawgeti(L, -1, 1); lua_remove(L, -2); // and throw away the table } + // value, struct, chr table lua_getfield(L, -3, "write"); if (!lua_isnoneornil(L, -1)) { - lua_pushvalue(L, -2); // the values table - if (lua_pcall(L, 1, 0, 0)) { + 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 + // just save the result in the value + lua_setfield(L, -3, "value"); + message.errcode = 0; } - lua_pop(L, 1); // Throw away the null write pointer - // just save the result in the value - lua_setfield(L, -3, "value"); - message.errcode = 0; } cleanup: @@ -1308,6 +759,9 @@ lble_start_advertising() { 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); if (name_length < 16) { fields.name = (uint8_t *)name; @@ -1413,6 +867,22 @@ static int lble_init(lua_State *L) { gadget_name = name; int rc; + + free((void *) gadget_mfg); + gadget_mfg = NULL; + gadget_mfg_len = 0; + + lua_getfield(L, 1, "mfg"); + 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); + } /* Initialize the NimBLE host configuration. */ // ble_hs_cfg.reset_cb = bleprph_on_reset; diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 7087871fd..a27966624 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -22,7 +22,7 @@ configuration table. See below for a detailed description of this table. #### Example ```lua -local config = {name="MyGadget=", services={{uuid="0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}}}} +local config = {name="MyGadget=", services={{uuid="0123456789abcdef0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}}}} ble.init(config) ``` @@ -47,6 +47,10 @@ ble.shutdown(function(err) print(err or "Ok!") end) ## Conventions +## UUID + +The service and characteristic identifiers are UUIDs. These are represented in twin-hex. They must be either 4 characters, 8 characters or 32 characters long. + ## Configuration Table The configuration table contains the following keys: @@ -55,6 +59,7 @@ The configuration table contains the following keys: - `services` This is a list of tables that define the individual services. The primary service is the first service. Many examples will only have a single service. +- `mfg` This is a string to be advertised in the mfg data field. ### Service table @@ -62,8 +67,6 @@ The service table contains the following keys: - `uuid` The UUID of the service. This is a 16 byte string (128 bits) that identifies the particular service. It can also be a two byte string for a well-known service. - `characteristics` This is a list of tables, where each entry describes a characateristic (attribute) -- `preread` This is an optional function that is invoked just before a read of any characteristic in this service. -- `postwrite` This is an optional function that is invoked just after a write of any characteristic in this service. ### Characteristic table @@ -75,7 +78,11 @@ The characteristic table contains the following keys: - `read` This is a function that will be invoked to read the value (and so does not need the `value` entry). It should return a string of bytes (unless `type` is set) - `write` This is a function that will be invoked to write the value (and so does not need the `value` entry). It is given a string of bytes (unless `type` is set) -The characteristics are treated as read/write unless only one of the `read` or `write` keys is present. +The characteristics are treated as read/write unless only one of the `read` or `write` keys is present and the `value` key is not specificed. + +The calling conventions for these functions are as follows: + +- `read` This is invoked with the charactersitic table as its only argument. +- `write` This is invoked with two arguments, the characteristic table and the data to be written (after conversion by `type`) -The calling conventions for these functions are TBD. From 0a4253aa917f843c08caba40434d280f27101a1f Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 17 Oct 2021 16:59:18 +0000 Subject: [PATCH 04/27] Ensure that BT and STRUCT are enabled. Can't figure out how to enforce NIMBLE --- components/modules/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/modules/Kconfig b/components/modules/Kconfig index e8bb52c1d..b13995bec 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -17,7 +17,8 @@ menu "NodeMCU modules" config NODEMCU_CMODULE_BLE bool "BlueTooth GAP/GATT interface module" default "n" - select BLE_ENABLED + select NODEMCU_CMODULE_STRUCT + select BT_ENABLED help Includes the simple BlueTooth GAP/GATT module. From 06becd1e8a5459780ccfb6cf918add93ab1f801d Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Mon, 18 Oct 2021 02:22:10 +0000 Subject: [PATCH 05/27] Try and get shutdown to work --- components/modules/ble.c | 88 ++++++++++++++++++++++------------------ docs/modules/ble.md | 20 +++++---- 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index ce1b33954..1d6ecf471 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -187,7 +187,7 @@ static int lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { // Actually the only thing we care about is the arg and the ctxt - printf("access_cb called with op %d\n", ctxt->op); + MODLOG_DFLT(INFO, "access_cb called with op %d\n", ctxt->op); size_t task_block_size = sizeof(task_block_t); @@ -215,7 +215,7 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces } } - printf("ABout to task_post\n"); + MODLOG_DFLT(INFO, "ABout to task_post\n"); if (!task_post(TASK_PRIORITY_HIGH, task_handle, (task_param_t) task_block)) { free(task_block); @@ -225,7 +225,7 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces response_message_t message; while (1) { - printf("About to receive\n"); + MODLOG_DFLT(INFO, "About to receive\n"); if (xQueueReceive(response_queue, &message, (TickType_t) (10000/portTICK_PERIOD_MS) ) != pdPASS) { free(task_block); @@ -289,14 +289,12 @@ lble_task_cb(task_param_t param, task_prio_t prio) { // Now we have the value (-1), struct (-2), table (-3) if (!lua_isnoneornil(L, -2)) { // need to convert value - printf("About to convert vaclue for read\n"); 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 - printf("wrapped in table\n"); } // Now call struct.pack @@ -349,7 +347,6 @@ lble_task_cb(task_param_t param, task_prio_t prio) { goto cleanup; } int vals = lua_gettop(L) - stack_size; - printf("unpacked %d vals\n", vals - 1); // Note that the last entry is actually the string offset for (int i = 1; i < vals; i++) { @@ -367,6 +364,15 @@ lble_task_cb(task_param_t param, task_prio_t prio) { 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 @@ -379,14 +385,11 @@ lble_task_cb(task_param_t param, task_prio_t prio) { } } else { lua_pop(L, 1); // Throw away the null write pointer - // just save the result in the value - lua_setfield(L, -3, "value"); - message.errcode = 0; } + lua_pop(L, 1); // THrow away the value } cleanup: - printf("Returning code %d\n", message.errcode); lua_pop(L, 2); message.seqno = task_block->seqno; @@ -400,8 +403,6 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { // + number of characteristics (nc) + ns * sizeof(ble_gatt_chr_def) // + ns + nc * sizeof(ble_uuid_any_t) - printf("build_gatt_svcs\n"); - lua_getfield(L, 1, "services"); if (!lua_istable(L, -1)) { return luaL_error(L, "services entry must be a table"); @@ -428,7 +429,6 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { 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); - printf("Computed size: %d (nc %d, ns %d)\n", size, nc, ns); struct ble_gatt_svc_def *svcs = malloc(size); if (!svcs) { @@ -520,12 +520,6 @@ static int gatt_svr_init(lua_State *L) { int rc; - printf("about to call gap_init\n"); - - ble_svc_gap_init(); - printf("about to call gatt_init\n"); - ble_svc_gatt_init(); - // Now we have to build the gatt_svr_svcs data structure @@ -534,8 +528,6 @@ gatt_svr_init(lua_State *L) { free_gatt_svcs(L, gatt_svr_svcs); gatt_svr_svcs = svcs; - - printf("about to call count_cfg\n"); rc = ble_gatts_count_cfg(gatt_svr_svcs); if (rc != 0) { return luaL_error(L, "Failed to count gatts: %d", rc); @@ -607,30 +599,39 @@ lble_host_task(void *param) static void lble_init_stack(lua_State *L) { - 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; - } + 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(); + nimble_port_init(); - //Initialize the NimBLE Host configuration + //Initialize the NimBLE Host configuration - nimble_port_freertos_init(lble_host_task); + nimble_port_freertos_init(lble_host_task); + + printf("about to call gap_init\n"); + + ble_svc_gap_init(); + printf("about to call gatt_init\n"); + ble_svc_gatt_init(); + } } static int lble_gap_event(struct ble_gap_event *event, void *arg) { - printf("GAP event %d\n", event->type); 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. */ - printf("connection %s; status=%d ", + MODLOG_DFLT(INFO, "connection %s; status=%d ", event->connect.status == 0 ? "established" : "failed", event->connect.status); if (event->connect.status == 0) { @@ -639,7 +640,7 @@ lble_gap_event(struct ble_gap_event *event, void *arg) lble_print_conn_desc(&desc); } - printf("\n"); + MODLOG_DFLT(INFO, "\n"); if (event->connect.status != 0) { /* Connection failed; resume advertising. */ @@ -648,9 +649,9 @@ lble_gap_event(struct ble_gap_event *event, void *arg) return 0; case BLE_GAP_EVENT_DISCONNECT: - printf("disconnect; reason=%d ", event->disconnect.reason); + MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason); lble_print_conn_desc(&event->disconnect.conn); - printf("\n"); + MODLOG_DFLT(INFO, "\n"); /* Connection terminated; resume advertising. */ lble_start_advertising(); @@ -732,7 +733,7 @@ lble_start_advertising() { /* Figure out address to use while advertising (no privacy for now) */ rc = ble_hs_id_infer_auto(0, &own_addr_type); if (rc != 0) { - return printf("error determining address type; rc=%d", rc); + return MODLOG_DFLT(INFO, "error determining address type; rc=%d", rc); } /** @@ -779,13 +780,13 @@ lble_start_advertising() { scan_response_fields.name_is_complete = 1; rc = ble_gap_adv_rsp_set_fields(&scan_response_fields); if (rc) { - return printf("gap_adv_rsp_set_fields failed: %d", rc); + return MODLOG_DFLT(INFO, "gap_adv_rsp_set_fields failed: %d", rc); } } rc = ble_gap_adv_set_fields(&fields); if (rc != 0) { - return printf("error setting advertisement data; rc=%d", rc); + return MODLOG_DFLT(INFO, "error setting advertisement data; rc=%d", rc); } /* Begin advertising. */ @@ -795,7 +796,7 @@ lble_start_advertising() { rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, lble_gap_event, NULL); if (rc != 0) { - return printf("error enabling advertisement; rc=%d", rc); + return MODLOG_DFLT(INFO, "error enabling advertisement; rc=%d", rc); } return 0; @@ -857,7 +858,6 @@ static int lble_init(lua_State *L) { // Passed the config table luaL_checktype(L, 1, LUA_TTABLE); - printf("About to init_stack"); lble_init_stack(L); lua_getfield(L, 1, "name"); @@ -899,6 +899,16 @@ static int lble_init(lua_State *L) { } static int lble_shutdown(lua_State *L) { + 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"); + } + return 0; } diff --git a/docs/modules/ble.md b/docs/modules/ble.md index a27966624..088df0824 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -9,7 +9,10 @@ This allows you to build simple gadgets that can be interrogated and controlled ## ble.init(configuration) This initializes the BlueTooth stack and starts advertising according to the data in the -configuration table. See below for a detailed description of this table. +configuration table. See below for a detailed description of this table. + +Once the stack is initialized, another `init` can be performed and it will switch over to using +the new config. #### Syntax `ble.init(ble_config)` @@ -26,23 +29,19 @@ local config = {name="MyGadget=", services={{uuid="0123456789abcdef0123456789abc ble.init(config) ``` -## bthci.shutdown(callback) +## ble.shutdown() Shuts down the BlueTooth controller and returns it to the state where another `init` can be performed. #### Syntax -`ble.shutdown([callback])` - -#### Parameters -- `callback` optional function to be invoked when the shutdown completes. Its - only argument is an error code, or `nil` on success. +`ble.shutdown()` #### Returns `nil` #### Example ```lua -ble.shutdown(function(err) print(err or "Ok!") end) +ble.shutdown() ``` ## Conventions @@ -82,7 +81,10 @@ The characteristics are treated as read/write unless only one of the `read` or ` The calling conventions for these functions are as follows: -- `read` This is invoked with the charactersitic table as its only argument. +- `read` This is invoked with the characteristic table as its only argument. - `write` This is invoked with two arguments, the characteristic table and the data to be written (after conversion by `type`) +### Type conversions + +If the `type` value converts a single item, then that will be the value that is placed into the `value` element. If it converts multiple elements, then the elements will be placed into an array that that will be plaed into the `value` element. From 69405ce183eaebcbaa31bbe0217c1af35ea89684 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Mon, 18 Oct 2021 23:58:41 +0000 Subject: [PATCH 06/27] Paritally working --- components/modules/ble.c | 14 ++++++---- components/modules/file.c | 54 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index 1d6ecf471..277cebbbe 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -412,6 +412,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { // -1 is the services list int nc = 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) { @@ -427,6 +428,8 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { lua_pop(L, 2); } + MODLOG_DFLT(INFO, "Discovered %d services with %d characteristics\n", ns, nc); + 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); @@ -444,6 +447,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { // 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 @@ -457,9 +461,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { free_gatt_svcs(L, result); return luaL_error(L, "Unable to convert UUID: %s", lua_tostring(L, -1)); } - if (i == 1) { - svc->type = BLE_GATT_SVC_TYPE_PRIMARY; - } + svc->type = BLE_GATT_SVC_TYPE_PRIMARY; svc->uuid = (ble_uuid_t *) uuids++; svc->characteristics = chrs; lua_pop(L, 1); @@ -467,6 +469,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { 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); @@ -488,13 +491,13 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE; lua_pop(L, 1); // pop off value } else { - lua_getfield(L, -1, "read"); + lua_getfield(L, -2, "read"); if (!lua_isnoneornil (L, -1)) { luaL_checkfunction (L, -1); chr->flags |= BLE_GATT_CHR_F_READ; } - lua_getfield(L, -1, "write"); + lua_getfield(L, -3, "write"); if (!lua_isnoneornil (L, -1)) { luaL_checkfunction (L, -1); chr->flags |= BLE_GATT_CHR_F_WRITE; @@ -508,6 +511,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { chr->access_cb = lble_access_cb; } lua_pop(L, 2); + chrs++; // terminate the list of characteristics for this service } lua_pop(L, 1); diff --git a/components/modules/file.c b/components/modules/file.c index 2c579cf22..933bfea8b 100644 --- a/components/modules/file.c +++ b/components/modules/file.c @@ -534,6 +534,58 @@ static int file_fsinfo( lua_State* L ) return 3; } +// Lua: getfile(filename) +static int file_getfile( lua_State* L ) +{ + // Warning this code C calls other file_* routines to avoid duplication code. These + // use Lua stack addressing of arguments, so this does Lua stack maniplation to + // align these + int ret_cnt = 0; + lua_settop(L ,1); + // Stack [1] = FD + file_open(L); + // Stack [1] = filename; [2] = FD or nil + if (!lua_isnil(L, -1)) { + lua_remove(L, 1); // dump filename, so [1] = FD + file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj"); + ret_cnt = file_g_read(L, LUAI_MAXINT32, EOF, ud->fd); + // Stack [1] = FD; [2] = contents if ret_cnt = 1; + file_close(L); // leaves Stack unchanged if [1] = FD + lua_remove(L, 1); // Dump FD leaving contents as [1] / ToS + } + return ret_cnt; +} + +// Lua: getfile(filename) +static int file_putfile( lua_State* L ) +{ + // Warning this code C calls other file_* routines to avoid duplication code. These + // use Lua stack addressing of arguments, so this does Lua stack maniplation to + // align these + int ret_cnt = 0; + lua_settop(L, 2); + lua_pushvalue(L, 2); //dup contents onto the ToS [3] + lua_pushliteral(L, "w+"); + lua_replace(L, 2); + // Stack [1] = filename; [2] "w+" [3] contents; + file_open(L); + // Stack [1] = filename; [2] "w+" [3] contents; [4] FD or nil + + if (!lua_isnil(L, -1)) { + lua_remove(L, 2); //dump "w+" attribute literal + lua_replace(L, 1); + // Stack [1] = FD; [2] contents + file_write(L); + // Stack [1] = FD; [2] contents; [3] result status + lua_remove(L, 2); //dump contents + file_close(L); + lua_remove(L, 1); // Dump FD leaving status as ToS + } + return 1; +} + + + typedef struct { vfs_vol *vol; } volume_type; @@ -570,6 +622,8 @@ LROT_BEGIN(file, NULL, 0) LROT_FUNCENTRY( writeline, file_writeline ) LROT_FUNCENTRY( read, file_read ) LROT_FUNCENTRY( readline, file_readline ) + LROT_FUNCENTRY( getcontents, file_getfile ) + LROT_FUNCENTRY( putcontents, file_putfile ) #ifdef CONFIG_NODEMCU_BUILD_SPIFFS LROT_FUNCENTRY( format, file_format ) LROT_FUNCENTRY( fscfg, file_fscfg ) From e80361061cea262c6cf655c5ddae621c6ec6906b Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Tue, 19 Oct 2021 00:54:01 +0000 Subject: [PATCH 07/27] Now actually gets started --- components/modules/ble.c | 43 +++++++++++++++++++++++++++++++-------- components/modules/ledc.c | 2 ++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index 277cebbbe..8b5abe03f 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -48,6 +48,7 @@ #define PERROR() printf("pcall failed: %s\n", lua_tostring(L, -1)) +static volatile bool synced = false; static int lble_start_advertising(); static int lble_gap_event(struct ble_gap_event *event, void *arg); @@ -542,7 +543,6 @@ gatt_svr_init(lua_State *L) { return luaL_error(L, "Failed to add gatts: %d", rc); } - ble_gatts_start(); return 0; } @@ -618,11 +618,6 @@ lble_init_stack(lua_State *L) { nimble_port_freertos_init(lble_host_task); - printf("about to call gap_init\n"); - - ble_svc_gap_init(); - printf("about to call gatt_init\n"); - ble_svc_gatt_init(); } } @@ -814,7 +809,11 @@ lble_on_sync(void) rc = ble_hs_util_ensure_addr(0); assert(rc == 0); - lble_start_advertising(); + if (!synced) { + synced = true; + } else { + lble_start_advertising(); + } } void @@ -862,8 +861,6 @@ static int lble_init(lua_State *L) { // Passed the config table luaL_checktype(L, 1, LUA_TTABLE); - lble_init_stack(L); - lua_getfield(L, 1, "name"); const char *name = strdup(luaL_checkstring(L, -1)); @@ -888,6 +885,10 @@ static int lble_init(lua_State *L) { memcpy(gadget_mfg, mfg, len); } + synced = false; + + 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; @@ -899,6 +900,30 @@ static int lble_init(lua_State *L) { luaL_error(L, "Failed to gatt_svr_init: %d", rc); } + bool seen1800 = false; + // See if we already have the 1800 service registered + for (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) { + printf("about to call gap_init\n"); + ble_svc_gap_init(); + } + printf("about to call gatt_init\n"); + ble_svc_gatt_init(); + + printf("about to call gatts_start\n"); + ble_gatts_start(); + + if (synced) { + lble_start_advertising(); + } else { + synced = true; + } + return 0; } diff --git a/components/modules/ledc.c b/components/modules/ledc.c index add40b6db..28289aedd 100644 --- a/components/modules/ledc.c +++ b/components/modules/ledc.c @@ -30,6 +30,8 @@ static int lledc_new_channel( lua_State *L ) ledc_timer.timer_num = opt_checkint_range(L, "timer", -1, 0, LEDC_TIMER_MAX-1); + ledc_timer.clk_cfg = LEDC_AUTO_CLK; + /* Setup channel */ ledc_channel_config_t channel_config = { .speed_mode = ledc_timer.speed_mode, From 28cb8981b2e6b149199dbb8064e605e3a7c2f038 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Tue, 19 Oct 2021 22:14:32 +0000 Subject: [PATCH 08/27] Making progress on ble --- components/modules/ble.c | 31 +++++++++- components/modules/file.c | 115 ++++++++++++++++---------------------- docs/modules/ble.md | 6 +- 3 files changed, 82 insertions(+), 70 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index 8b5abe03f..fc182e3a9 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -64,6 +64,8 @@ static QueueHandle_t response_queue; 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 @@ -490,7 +492,21 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { if (lua_getfield(L, -1, "value") != LUA_TNIL) { chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE; - lua_pop(L, 1); // pop off value + + 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)) { @@ -729,6 +745,10 @@ lble_start_advertising() { const char *name = gadget_name; int rc; + if (inited != RUNNING) { + return 0; + } + /* Figure out address to use while advertising (no privacy for now) */ rc = ble_hs_id_infer_auto(0, &own_addr_type); if (rc != 0) { @@ -850,6 +870,9 @@ gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) static int lble_init(lua_State *L) { + if (inited != STOPPED) { + return luaL_error(L, "ble is already running"); + } if (!struct_pack_index) { lua_getglobal(L, "struct"); lua_getfield(L, -1, "pack"); @@ -924,10 +947,14 @@ static int lble_init(lua_State *L) { synced = true; } + inited = RUNNING; + return 0; } static int lble_shutdown(lua_State *L) { + inited = SHUTTING; + if (nimble_port_stop()) { return luaL_error(L, "Failed to stop the NIMBLE task"); } @@ -938,6 +965,8 @@ static int lble_shutdown(lua_State *L) { return luaL_error(L, "Failed to shutdown the BLE controller"); } + inited = STOPPED; + return 0; } diff --git a/components/modules/file.c b/components/modules/file.c index 933bfea8b..8ced231df 100644 --- a/components/modules/file.c +++ b/components/modules/file.c @@ -383,60 +383,40 @@ static int file_stat( lua_State* L ) // g_read() static int file_g_read( lua_State* L, int n, int16_t end_char, int fd ) { - char *heap_mem = NULL; - - if(n <= 0) - n = FILE_READ_CHUNK; - - if(end_char < 0 || end_char >255) - end_char = EOF; + int i, j; + luaL_Buffer b; + char p[LUAL_BUFFERSIZE/2]; if(!fd) return luaL_error(L, "open a file first"); - char *p; - int i; - size_t bufsize = n; + luaL_buffinit(L, &b); - if (n > LUAL_BUFFERSIZE) { - // get buffer from heap - p = heap_mem = luaM_malloc(L, bufsize); - } else { - // small chunks go onto the stack - p = alloca(bufsize); - } + for (j = 0; j < n; j += sizeof(p)) { + int nwanted = (n - j >= sizeof(p)) ? sizeof(p) : n - j; + int nread = vfs_read(fd, p, nwanted); - n = vfs_read(fd, p, n); - // bypass search if no end character provided - if (n > 0 && end_char != EOF) { - for (i = 0; i < n; ++i) - if (p[i] == end_char) - { - ++i; + if (nread == VFS_RES_ERR || nread == 0) { + if (j > 0) { break; } - } else { - i = n; - } - - int err = 0; - - if (i == 0 || n == VFS_RES_ERR) { - lua_pushnil(L); - } else { - vfs_lseek(fd, -(n - i), VFS_SEEK_CUR); - err = luaX_pushlstring(L, p, i); // On error it will return nonzero and leave a message on top of the stack. - } + lua_pushnil(L); + return 1; + } - if (heap_mem) { - luaN_freearray(L, heap_mem, bufsize); - } + for (i = 0; i < nread; ++i) { + luaL_addchar(&b, p[i]); + if (p[i] == end_char) { + vfs_lseek(fd, -nread + i + 1, VFS_SEEK_CUR); //reposition after end char found + nread = 0; // force break on outer loop + break; + } + } - if (err){ - lua_error(L); // luaX_pushlstring failed and the error message is on top of the stack. Throw it. - // never returns + if (nread < nwanted) + break; } - + luaL_pushresult(&b); return 1; } @@ -476,6 +456,29 @@ static int file_readline( lua_State* L ) return file_g_read(L, FILE_READ_CHUNK, '\n', fd); } + +// Lua: getfile(filename) +static int file_getfile( lua_State* L ) +{ + // Warning this code C calls other file_* routines to avoid duplication code. These + // use Lua stack addressing of arguments, so this does Lua stack maniplation to + // align these + int ret_cnt = 0; + lua_settop(L ,1); + // Stack [1] = FD + file_open(L); + // Stack [1] = filename; [2] = FD or nil + if (!lua_isnil(L, -1)) { + lua_remove(L, 1); // dump filename, so [1] = FD + file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj"); + ret_cnt = file_g_read(L, LUAI_MAXINT32, EOF, ud->fd); + // Stack [1] = FD; [2] = contents if ret_cnt = 1; + file_close(L); // leaves Stack unchanged if [1] = FD + lua_remove(L, 1); // Dump FD leaving contents as [1] / ToS + } + return ret_cnt; +} + // Lua: write("string") static int file_write( lua_State* L ) { @@ -534,28 +537,6 @@ static int file_fsinfo( lua_State* L ) return 3; } -// Lua: getfile(filename) -static int file_getfile( lua_State* L ) -{ - // Warning this code C calls other file_* routines to avoid duplication code. These - // use Lua stack addressing of arguments, so this does Lua stack maniplation to - // align these - int ret_cnt = 0; - lua_settop(L ,1); - // Stack [1] = FD - file_open(L); - // Stack [1] = filename; [2] = FD or nil - if (!lua_isnil(L, -1)) { - lua_remove(L, 1); // dump filename, so [1] = FD - file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj"); - ret_cnt = file_g_read(L, LUAI_MAXINT32, EOF, ud->fd); - // Stack [1] = FD; [2] = contents if ret_cnt = 1; - file_close(L); // leaves Stack unchanged if [1] = FD - lua_remove(L, 1); // Dump FD leaving contents as [1] / ToS - } - return ret_cnt; -} - // Lua: getfile(filename) static int file_putfile( lua_State* L ) { @@ -622,8 +603,6 @@ LROT_BEGIN(file, NULL, 0) LROT_FUNCENTRY( writeline, file_writeline ) LROT_FUNCENTRY( read, file_read ) LROT_FUNCENTRY( readline, file_readline ) - LROT_FUNCENTRY( getcontents, file_getfile ) - LROT_FUNCENTRY( putcontents, file_putfile ) #ifdef CONFIG_NODEMCU_BUILD_SPIFFS LROT_FUNCENTRY( format, file_format ) LROT_FUNCENTRY( fscfg, file_fscfg ) @@ -633,6 +612,8 @@ LROT_BEGIN(file, NULL, 0) LROT_FUNCENTRY( flush, file_flush ) LROT_FUNCENTRY( rename, file_rename ) LROT_FUNCENTRY( exists, file_exists ) + LROT_FUNCENTRY( getcontents, file_getfile ) + LROT_FUNCENTRY( putcontents, file_putfile ) LROT_FUNCENTRY( fsinfo, file_fsinfo ) LROT_FUNCENTRY( on, file_on ) LROT_FUNCENTRY( stat, file_stat ) diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 088df0824..149fe88df 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -74,10 +74,12 @@ The characteristic table contains the following keys: - `uuid` The UUID of the characteristics. This can be either a 16 byte string or a 2 byte string that identifies the particular characteristic. Typically, 2 byte strings are used for well-known characteristics. - `type` This is the optional type of the value. It has the same value as a unpack code in the `struct` module. - `value` This is the actual value of the characteristic. This will be a string of bytes unless a `type` value is set. -- `read` This is a function that will be invoked to read the value (and so does not need the `value` entry). It should return a string of bytes (unless `type` is set) +- `read` This is a function that will be invoked to read the value (and so does not need the `value` entry). It should return a string of bytes (unless `type` is set). - `write` This is a function that will be invoked to write the value (and so does not need the `value` entry). It is given a string of bytes (unless `type` is set) -The characteristics are treated as read/write unless only one of the `read` or `write` keys is present and the `value` key is not specificed. +If the `value` key is present, then the characteristic is read/write. However, if one or `read` or `write` is set to `true`, then it restricts access to that mode. + +The characteristics are treated as read/write unless only one of the `read` or `write` keys is present and the `value` key is not specified. The calling conventions for these functions are as follows: From 96993eff16dff7a19af407c257d39b3992ca117a Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Thu, 21 Oct 2021 00:51:16 +0000 Subject: [PATCH 09/27] FIx the advertising start --- components/modules/ble.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index fc182e3a9..94e21b915 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -48,7 +48,7 @@ #define PERROR() printf("pcall failed: %s\n", lua_tostring(L, -1)) -static volatile bool synced = false; +static volatile enum { IDLE, SYNCED, READY_TO_ADVERTISE, ADVERTISING } synced; static int lble_start_advertising(); static int lble_gap_event(struct ble_gap_event *event, void *arg); @@ -190,8 +190,6 @@ static int lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { // Actually the only thing we care about is the arg and the ctxt - MODLOG_DFLT(INFO, "access_cb called with op %d\n", ctxt->op); - size_t task_block_size = sizeof(task_block_t); if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { @@ -218,8 +216,6 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces } } - MODLOG_DFLT(INFO, "ABout to task_post\n"); - if (!task_post(TASK_PRIORITY_HIGH, task_handle, (task_param_t) task_block)) { free(task_block); return BLE_ATT_ERR_UNLIKELY; @@ -228,8 +224,6 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces response_message_t message; while (1) { - MODLOG_DFLT(INFO, "About to receive\n"); - if (xQueueReceive(response_queue, &message, (TickType_t) (10000/portTICK_PERIOD_MS) ) != pdPASS) { free(task_block); return BLE_ATT_ERR_UNLIKELY; @@ -829,10 +823,12 @@ lble_on_sync(void) rc = ble_hs_util_ensure_addr(0); assert(rc == 0); - if (!synced) { - synced = true; - } else { + if (synced == READY_TO_ADVERTISE) { lble_start_advertising(); + synced = ADVERTISING; + } + if (synced == IDLE) { + synced = SYNCED; } } @@ -908,7 +904,7 @@ static int lble_init(lua_State *L) { memcpy(gadget_mfg, mfg, len); } - synced = false; + synced = IDLE; lble_init_stack(L); @@ -925,7 +921,7 @@ static int lble_init(lua_State *L) { bool seen1800 = false; // See if we already have the 1800 service registered - for (struct ble_gatt_svc_def *svcs = gatt_svr_svcs; svcs->type; svcs++) { + 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; } @@ -941,14 +937,15 @@ static int lble_init(lua_State *L) { printf("about to call gatts_start\n"); ble_gatts_start(); - if (synced) { + inited = RUNNING; + + if (synced == SYNCED) { lble_start_advertising(); + synced = ADVERTISING; } else { - synced = true; + synced = READY_TO_ADVERTISE; } - inited = RUNNING; - return 0; } From d204d33d3d6f271b1fbbb28422360c293e21f0e6 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Thu, 21 Oct 2021 01:32:46 +0000 Subject: [PATCH 10/27] Use the hardware random number generator --- components/lua/lua-5.3/luaconf.h | 5 +++++ components/modules/ble.c | 3 --- components/modules/file.c | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/lua/lua-5.3/luaconf.h b/components/lua/lua-5.3/luaconf.h index cb5f9b413..8081cf7d3 100644 --- a/components/lua/lua-5.3/luaconf.h +++ b/components/lua/lua-5.3/luaconf.h @@ -74,6 +74,11 @@ # define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT #endif +#define L_RANDMAX 2147483647 +extern uint32_t esp_random(void); +#define l_rand() (esp_random() & L_RANDMAX) +#define l_srand(x) + /* ** Configuration for Paths. ** diff --git a/components/modules/ble.c b/components/modules/ble.c index 94e21b915..e197981df 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -928,13 +928,10 @@ static int lble_init(lua_State *L) { } if (!seen1800) { - printf("about to call gap_init\n"); ble_svc_gap_init(); } - printf("about to call gatt_init\n"); ble_svc_gatt_init(); - printf("about to call gatts_start\n"); ble_gatts_start(); inited = RUNNING; diff --git a/components/modules/file.c b/components/modules/file.c index 8ced231df..fd45c3640 100644 --- a/components/modules/file.c +++ b/components/modules/file.c @@ -543,7 +543,6 @@ static int file_putfile( lua_State* L ) // Warning this code C calls other file_* routines to avoid duplication code. These // use Lua stack addressing of arguments, so this does Lua stack maniplation to // align these - int ret_cnt = 0; lua_settop(L, 2); lua_pushvalue(L, 2); //dup contents onto the ToS [3] lua_pushliteral(L, "w+"); From 52562a641252778717b7a1f0af5a683fb9fd054b Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 24 Oct 2021 18:04:04 +0000 Subject: [PATCH 11/27] Got rid of the random printfs --- components/modules/ble.c | 57 +++++++++++++++++++++++++--------------- docs/modules/ble.md | 30 +++++++++++++++++---- 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index e197981df..e20958f19 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -46,10 +46,18 @@ #include "nimble/ble.h" #include "task/task.h" -#define PERROR() printf("pcall failed: %s\n", lua_tostring(L, -1)) +#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)) + +extern void ble_hs_lock(); +extern void ble_hs_unlock(); static volatile enum { IDLE, SYNCED, READY_TO_ADVERTISE, ADVERTISING } synced; -static int lble_start_advertising(); +static void lble_start_advertising(); static int lble_gap_event(struct ble_gap_event *event, void *arg); static const char *gadget_name; @@ -61,6 +69,8 @@ static const struct ble_gatt_svc_def *gatt_svr_svcs; 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; @@ -84,9 +94,6 @@ typedef struct { size_t length; } task_block_t; -#undef MODLOG_DFLT -#define MODLOG_DFLT(level, ...) printf(__VA_ARGS__) - /** * Utility function to log an array of bytes. */ @@ -540,9 +547,9 @@ gatt_svr_init(lua_State *L) { struct ble_gatt_svc_def *svcs = NULL; lble_build_gatt_svcs(L, &svcs); - free_gatt_svcs(L, gatt_svr_svcs); + //free_gatt_svcs(L, gatt_svr_svcs); 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); @@ -553,7 +560,6 @@ gatt_svr_init(lua_State *L) { return luaL_error(L, "Failed to add gatts: %d", rc); } - return 0; } @@ -621,14 +627,13 @@ lble_init_stack(lua_State *L) { 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_init(); - nimble_port_freertos_init(lble_host_task); + //Initialize the NimBLE Host configuration - } + nimble_port_freertos_init(lble_host_task); } static int @@ -731,7 +736,7 @@ lble_gap_event(struct ble_gap_event *event, void *arg) return 0; } -static int +static void lble_start_advertising() { uint8_t own_addr_type; struct ble_gap_adv_params adv_params; @@ -740,13 +745,14 @@ lble_start_advertising() { int rc; if (inited != RUNNING) { - return 0; + 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) { - return MODLOG_DFLT(INFO, "error determining address type; rc=%d", rc); + MODLOG_DFLT(INFO, "error determining address type; rc=%d", rc); + return; } /** @@ -793,13 +799,15 @@ lble_start_advertising() { scan_response_fields.name_is_complete = 1; rc = ble_gap_adv_rsp_set_fields(&scan_response_fields); if (rc) { - return MODLOG_DFLT(INFO, "gap_adv_rsp_set_fields failed: %d", rc); + MODLOG_DFLT(INFO, "gap_adv_rsp_set_fields failed: %d", rc); + return; } } rc = ble_gap_adv_set_fields(&fields); if (rc != 0) { - return MODLOG_DFLT(INFO, "error setting advertisement data; rc=%d", rc); + MODLOG_DFLT(INFO, "error setting advertisement data; rc=%d", rc); + return; } /* Begin advertising. */ @@ -809,10 +817,11 @@ lble_start_advertising() { rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, lble_gap_event, NULL); if (rc != 0) { - return MODLOG_DFLT(INFO, "error enabling advertisement; rc=%d", rc); + MODLOG_DFLT(INFO, "error enabling advertisement; rc=%d", rc); + return; } - return 0; + return; } static void @@ -869,6 +878,10 @@ 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"); @@ -887,7 +900,7 @@ static int lble_init(lua_State *L) { gadget_name = name; int rc; - + free((void *) gadget_mfg); gadget_mfg = NULL; gadget_mfg_len = 0; @@ -949,6 +962,8 @@ static int lble_init(lua_State *L) { static int lble_shutdown(lua_State *L) { inited = SHUTTING; + ble_gap_adv_stop(); + if (nimble_port_stop()) { return luaL_error(L, "Failed to stop the NIMBLE task"); } diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 149fe88df..e16889f2a 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -1,7 +1,7 @@ # BT HCI Module | Since | Origin / Contributor | Maintainer | Source | | :----- | :-------------------- | :---------- | :------ | -| 2021-10-10 | [pjsg](https://github.com/pjsg) | [ble.c](../../components/modules/ble.c)| +| 2021-10-10 | [pjsg](https://github.com/pjsg) | [pjsg](https://github.com/pjsg) | [ble.c](../../components/modules/ble.c)| The BLE module provides a simple interface to allow implementation of a simple GAP/GATT server. This allows you to build simple gadgets that can be interrogated and controlled over BLE. @@ -11,8 +11,9 @@ This allows you to build simple gadgets that can be interrogated and controlled This initializes the BlueTooth stack and starts advertising according to the data in the configuration table. See below for a detailed description of this table. -Once the stack is initialized, another `init` can be performed and it will switch over to using -the new config. +At the present time, you can only call the `init` function once. There is some problem +in the underlying implementation of the BLE stack that prevents a `init`, `shutdown`, `init` +sequence from working. #### Syntax `ble.init(ble_config)` @@ -25,13 +26,19 @@ the new config. #### Example ```lua -local config = {name="MyGadget=", services={{uuid="0123456789abcdef0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}}}} +function read_battery_level() + -- This ought to do something better! + return 50 +end +local battery = { uuid="180f", characteristics={ {uuid="2a19", type='B', read=read_battery_level} } } +local myservice = {uuid="0123456789abcdef0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}} +local config = {name="MyGadget=", services={ myservice, battery } ble.init(config) ``` ## ble.shutdown() -Shuts down the BlueTooth controller and returns it to the state where another `init` can be performed. +Shuts down the BlueTooth controller and returns it to the state where another `init` ought to work (but currently doesn't). #### Syntax `ble.shutdown()` @@ -86,6 +93,19 @@ The calling conventions for these functions are as follows: - `read` This is invoked with the characteristic table as its only argument. - `write` This is invoked with two arguments, the characteristic table and the data to be written (after conversion by `type`) +#### Example + +``` +function read_attribute(t) + return something +end + +function write_attribute(t, val) + -- Just store the written value in the table. + t.value = val +end +``` + ### Type conversions From 7b6a85b931c73c87c348c74d620069914a5d48b3 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 24 Oct 2021 18:06:04 +0000 Subject: [PATCH 12/27] Remove trailing spaces --- components/modules/ble.c | 28 ++++++++++++++-------------- docs/modules/ble.md | 6 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index e20958f19..63d42d626 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -78,7 +78,7 @@ static enum { STOPPED, RUNNING, SHUTTING } inited; static int seqno; -// Note that the buffer should be freed +// Note that the buffer should be freed typedef struct { int seqno; int errcode; @@ -127,7 +127,7 @@ gethexval(char c) { return -1; } -static int +static int decodehex(const char *s) { // two characters int v1 = gethexval(s[0]); @@ -156,7 +156,7 @@ convert_uuid(ble_uuid_any_t *uuid, const char *s) { continue; } sptr -= 2; - + int val = decodehex(sptr); if (val < 0) { return false; @@ -260,7 +260,7 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces return rc; } -static void +static void lble_task_cb(task_param_t param, task_prio_t prio) { task_block_t *task_block = (task_block_t *) param; @@ -297,7 +297,7 @@ lble_task_cb(task_param_t param, task_prio_t prio) { // 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_rawseti(L, -2, 1); lua_remove(L, -2); // now have table, struct, chr table } @@ -359,7 +359,7 @@ lble_task_cb(task_param_t param, task_prio_t prio) { } lua_pop(L, vals); lua_remove(L, -2); - // Now have table, struct, chrtable + // Now have table, struct, chrtable } // If the value is a table of a single value, then // treat as value @@ -400,7 +400,7 @@ lble_task_cb(task_param_t param, task_prio_t prio) { xQueueSend(response_queue, &message, (TickType_t) 0); } -static int +static int lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { // 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) @@ -506,7 +506,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { if (flags) { chr->flags = flags; } - + lua_pop(L, 3); // pop off value, read, write } else { lua_getfield(L, -2, "read"); @@ -534,7 +534,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { lua_pop(L, 1); *resultp = result; - + return 0; } @@ -544,12 +544,12 @@ gatt_svr_init(lua_State *L) { // Now we have to build the gatt_svr_svcs data structure - + struct ble_gatt_svc_def *svcs = NULL; lble_build_gatt_svcs(L, &svcs); //free_gatt_svcs(L, gatt_svr_svcs); 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); @@ -610,14 +610,14 @@ lble_sys_init(lua_State *L) { return 0; } -static void +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 +static void lble_init_stack(lua_State *L) { static char stack_inited; if (!stack_inited) { @@ -969,7 +969,7 @@ static int lble_shutdown(lua_State *L) { } nimble_port_deinit(); - + if (ESP_OK != esp_nimble_hci_and_controller_deinit()) { return luaL_error(L, "Failed to shutdown the BLE controller"); } diff --git a/docs/modules/ble.md b/docs/modules/ble.md index e16889f2a..3d6df01ad 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -9,7 +9,7 @@ This allows you to build simple gadgets that can be interrogated and controlled ## ble.init(configuration) This initializes the BlueTooth stack and starts advertising according to the data in the -configuration table. See below for a detailed description of this table. +configuration table. See below for a detailed description of this table. At the present time, you can only call the `init` function once. There is some problem in the underlying implementation of the BLE stack that prevents a `init`, `shutdown`, `init` @@ -26,7 +26,7 @@ sequence from working. #### Example ```lua -function read_battery_level() +function read_battery_level() -- This ought to do something better! return 50 end @@ -96,7 +96,7 @@ The calling conventions for these functions are as follows: #### Example ``` -function read_attribute(t) +function read_attribute(t) return something end From 3f330273f3b29a609fa6f8cedee4e38846fa39e4 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 24 Oct 2021 19:15:29 +0000 Subject: [PATCH 13/27] See if this fixes the cross compiles --- components/lua/lua-5.3/luaconf.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/lua/lua-5.3/luaconf.h b/components/lua/lua-5.3/luaconf.h index 8081cf7d3..2fb0ab94c 100644 --- a/components/lua/lua-5.3/luaconf.h +++ b/components/lua/lua-5.3/luaconf.h @@ -74,10 +74,12 @@ # define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT #endif +#ifdef LUA_USE_ESP #define L_RANDMAX 2147483647 extern uint32_t esp_random(void); #define l_rand() (esp_random() & L_RANDMAX) #define l_srand(x) +#endif /* ** Configuration for Paths. From b5c4082988955646b9c196bc186ca6827a158506 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Tue, 26 Oct 2021 00:41:47 +0000 Subject: [PATCH 14/27] Allow dynamic update of advertisements --- components/modules/ble.c | 122 +++++++++++++++++++++++++-------------- docs/modules/ble.md | 21 ++++++- 2 files changed, 99 insertions(+), 44 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index 63d42d626..fa29cf97d 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -736,25 +736,10 @@ lble_gap_event(struct ble_gap_event *event, void *arg) return 0; } -static void -lble_start_advertising() { - uint8_t own_addr_type; - struct ble_gap_adv_params adv_params; +static int +lble_update_adv_fields() { struct ble_hs_adv_fields fields; const char *name = gadget_name; - 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; - } - /** * Set the advertisement data included in our advertisements: * o Flags (indicates advertisement type and other general info). @@ -783,30 +768,58 @@ lble_start_advertising() { fields.mfg_data_len = gadget_mfg_len; size_t name_length = strlen(name); - if (name_length < 16) { - fields.name = (uint8_t *)name; - fields.name_len = name_length; - fields.name_is_complete = 1; - } else { - fields.name = (uint8_t *)name; - fields.name_len = 16; - fields.name_is_complete = 0; + 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; - rc = ble_gap_adv_rsp_set_fields(&scan_response_fields); + 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; + 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 = ble_gap_adv_set_fields(&fields); + rc = lble_update_adv_fields(); if (rc != 0) { - MODLOG_DFLT(INFO, "error setting advertisement data; rc=%d", rc); return; } @@ -873,6 +886,38 @@ gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) } } +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_init(lua_State *L) { if (inited != STOPPED) { @@ -901,21 +946,11 @@ static int lble_init(lua_State *L) { int rc; - free((void *) gadget_mfg); - gadget_mfg = NULL; - gadget_mfg_len = 0; + lua_getfield(L, 1, "advertisement"); - lua_getfield(L, 1, "mfg"); - 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); - } + rc = lble_update_adv_change(L); + + lua_pop(L, 1); synced = IDLE; @@ -981,6 +1016,7 @@ static int lble_shutdown(lua_State *L) { LROT_BEGIN(lble, NULL, 0) LROT_FUNCENTRY( init, lble_init ) + LROT_FUNCENTRY( advertise, lble_update_adv ) LROT_FUNCENTRY( shutdown, lble_shutdown ) LROT_END(lble, NULL, 0) diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 3d6df01ad..24082a8a1 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -36,6 +36,25 @@ local config = {name="MyGadget=", services={ myservice, battery } ble.init(config) ``` +## ble.advertise() + +Updates the advertising data field for future advertising frames. + +#### Syntax +`ble.advertise(advertisement)` + +#### Parameters + +- `advertisement` This string will be placed in future advertising frames as the manufacturer data field. This overrides the a`advertisement` value from the config block. + +#### Returns +`nil` + +#### Example +```lua +ble.advertise("foo") +``` + ## ble.shutdown() Shuts down the BlueTooth controller and returns it to the state where another `init` ought to work (but currently doesn't). @@ -65,7 +84,7 @@ The configuration table contains the following keys: - `services` This is a list of tables that define the individual services. The primary service is the first service. Many examples will only have a single service. -- `mfg` This is a string to be advertised in the mfg data field. +- `advertisement` This is a string to be advertised in the mfg data field. ### Service table From 4109e3053f49f04c68f621b8ebde78a2da92228f Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Fri, 5 Nov 2021 23:50:48 +0000 Subject: [PATCH 15/27] Prevent ble.shutdown() as it corrupts something... --- components/modules/ble.c | 4 ++++ docs/modules/ble.md | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index fa29cf97d..8bb16df7c 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -995,6 +995,10 @@ static int lble_init(lua_State *L) { } 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(); diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 24082a8a1..7b7d8535b 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -57,7 +57,8 @@ ble.advertise("foo") ## ble.shutdown() -Shuts down the BlueTooth controller and returns it to the state where another `init` ought to work (but currently doesn't). +Shuts down the BlueTooth controller and returns it to the state where another `init` ought to work (but currently doesn't). And, at the moment, shutting +it down doesn't work either -- it appears to corrupt some deep data structures. #### Syntax `ble.shutdown()` From 7e123b215ed764a2766b7dafc841025d62f1b10c Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Mon, 8 Nov 2021 20:07:12 -0500 Subject: [PATCH 16/27] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix capitalization of Bluetooth Co-authored-by: Marcel Stör --- components/modules/Kconfig | 6 +++--- docs/modules/ble.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/modules/Kconfig b/components/modules/Kconfig index b13995bec..c6c9082f6 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -15,15 +15,15 @@ menu "NodeMCU modules" functions on Lua numbers. config NODEMCU_CMODULE_BLE - bool "BlueTooth GAP/GATT interface module" + bool "Bluetooth GAP/GATT interface module" default "n" select NODEMCU_CMODULE_STRUCT select BT_ENABLED help - Includes the simple BlueTooth GAP/GATT module. + Includes the simple Bluetooth GAP/GATT module. config NODEMCU_CMODULE_BTHCI - bool "BlueTooth HCI interface module" + bool "Bluetooth HCI interface module" default "n" select BT_ENABLED help diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 7b7d8535b..71bb58cdd 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -1,4 +1,4 @@ -# BT HCI Module +# Bluetooth GAP/GATT Module | Since | Origin / Contributor | Maintainer | Source | | :----- | :-------------------- | :---------- | :------ | | 2021-10-10 | [pjsg](https://github.com/pjsg) | [pjsg](https://github.com/pjsg) | [ble.c](../../components/modules/ble.c)| @@ -8,7 +8,7 @@ This allows you to build simple gadgets that can be interrogated and controlled ## ble.init(configuration) -This initializes the BlueTooth stack and starts advertising according to the data in the +This initializes the Bluetooth stack and starts advertising according to the data in the configuration table. See below for a detailed description of this table. At the present time, you can only call the `init` function once. There is some problem @@ -57,7 +57,7 @@ ble.advertise("foo") ## ble.shutdown() -Shuts down the BlueTooth controller and returns it to the state where another `init` ought to work (but currently doesn't). And, at the moment, shutting +Shuts down the Bluetooth controller and returns it to the state where another `init` ought to work (but currently doesn't). And, at the moment, shutting it down doesn't work either -- it appears to corrupt some deep data structures. #### Syntax From 7aa22335acf672be8fbf3d443a8a8f57f268f4d1 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Thu, 6 Jan 2022 02:10:53 +0000 Subject: [PATCH 17/27] Add support for notify (untested) --- components/modules/ble.c | 51 +++++++++++++++++++++++++++++++++------- docs/modules/ble.md | 21 +++++++++++++++++ 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index 8bb16df7c..4d4d09184 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -65,6 +65,7 @@ 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; @@ -401,7 +402,7 @@ lble_task_cb(task_param_t param, task_prio_t prio) { } static int -lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { +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) @@ -434,7 +435,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { MODLOG_DFLT(INFO, "Discovered %d services with %d characteristics\n", ns, nc); - 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); + 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); struct ble_gatt_svc_def *svcs = malloc(size); @@ -447,6 +448,9 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { 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); + uint16_t *handles = (uint16_t *) (uuids + ns + nc); + + handles[0] = 0; // number of slots used // Now fill out the data structure // -1 is the services list @@ -496,11 +500,11 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { int flags = 0; lua_getfield(L, -2, "read"); - if (lua_isboolean(L, 1) && lua_toboolean(L, -1)) { + 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)) { + if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) { flags |= BLE_GATT_CHR_F_WRITE; } if (flags) { @@ -524,6 +528,16 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { 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, -2, "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; @@ -534,6 +548,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp) { lua_pop(L, 1); *resultp = result; + *handlep = handles; return 0; } @@ -546,7 +561,7 @@ gatt_svr_init(lua_State *L) { struct ble_gatt_svc_def *svcs = NULL; - lble_build_gatt_svcs(L, &svcs); + lble_build_gatt_svcs(L, &svcs, ¬ify_handles); //free_gatt_svcs(L, gatt_svr_svcs); gatt_svr_svcs = svcs; @@ -736,7 +751,7 @@ lble_gap_event(struct ble_gap_event *event, void *arg) return 0; } -static int +static int lble_update_adv_fields() { struct ble_hs_adv_fields fields; const char *name = gadget_name; @@ -785,7 +800,7 @@ lble_update_adv_fields() { 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); @@ -886,7 +901,7 @@ gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) } } -static int +static int lble_update_adv_change(lua_State *L) { free((void *) gadget_mfg); gadget_mfg = NULL; @@ -906,7 +921,7 @@ lble_update_adv_change(lua_State *L) { return 0; } -static int +static int lble_update_adv(lua_State *L) { lua_pushvalue(L, 1); lble_update_adv_change(L); @@ -918,6 +933,23 @@ lble_update_adv(lua_State *L) { 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]); + +/* + if (rc) { + return luaL_error(L, "Must supply a valid handle"); + } +*/ + return 0; +} static int lble_init(lua_State *L) { if (inited != STOPPED) { @@ -1020,6 +1052,7 @@ static int lble_shutdown(lua_State *L) { 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) diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 71bb58cdd..1d9984637 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -36,6 +36,26 @@ local config = {name="MyGadget=", services={ myservice, battery } ble.init(config) ``` +## ble.notify() + +This notifies the Bluetooth stack that a new value is available to be read from the characteristic. + +#### Syntax +`ble.notify(characteristic)` + +#### Parameters + +- `characteristic` This is the table that was passed into the `init` method for the particular characteristic. + +#### Returns +`nil` + +#### Example + +```lua +ble.notify(config.services[1].characteristics[1]) +``` + ## ble.advertise() Updates the advertising data field for future advertising frames. @@ -103,6 +123,7 @@ The characteristic table contains the following keys: - `value` This is the actual value of the characteristic. This will be a string of bytes unless a `type` value is set. - `read` This is a function that will be invoked to read the value (and so does not need the `value` entry). It should return a string of bytes (unless `type` is set). - `write` This is a function that will be invoked to write the value (and so does not need the `value` entry). It is given a string of bytes (unless `type` is set) +- `notify` If this attribute is present with the value `true` then notifications are supported on this characteristic. If the `value` key is present, then the characteristic is read/write. However, if one or `read` or `write` is set to `true`, then it restricts access to that mode. From 13e1dada4d23fb36f96de1917546fbbc2226125d Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Thu, 6 Jan 2022 02:13:13 +0000 Subject: [PATCH 18/27] Update the docs to match the code --- docs/modules/ble.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 1d9984637..4572747db 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -41,11 +41,11 @@ ble.init(config) This notifies the Bluetooth stack that a new value is available to be read from the characteristic. #### Syntax -`ble.notify(characteristic)` +`ble.notify(notifyvalue)` #### Parameters -- `characteristic` This is the table that was passed into the `init` method for the particular characteristic. +- `notifyvalue` This is the value of the `notify` table entry from the particular characteristic. #### Returns `nil` @@ -53,7 +53,7 @@ This notifies the Bluetooth stack that a new value is available to be read from #### Example ```lua -ble.notify(config.services[1].characteristics[1]) +ble.notify(config.services[1].characteristics[1].notify) ``` ## ble.advertise() @@ -123,7 +123,7 @@ The characteristic table contains the following keys: - `value` This is the actual value of the characteristic. This will be a string of bytes unless a `type` value is set. - `read` This is a function that will be invoked to read the value (and so does not need the `value` entry). It should return a string of bytes (unless `type` is set). - `write` This is a function that will be invoked to write the value (and so does not need the `value` entry). It is given a string of bytes (unless `type` is set) -- `notify` If this attribute is present with the value `true` then notifications are supported on this characteristic. +- `notify` If this attribute is present then notifications are supported on this characteristic. The value of the `notify` attribute is updated to be an integer which is the value to be passed into `ble.notify()` If the `value` key is present, then the characteristic is read/write. However, if one or `read` or `write` is set to `true`, then it restricts access to that mode. From 4d24232105f8c2bcc61da2dd81a545ddd82af540 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Fri, 7 Jan 2022 20:59:38 -0500 Subject: [PATCH 19/27] Notify seems to work now. --- components/modules/ble.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index 4d4d09184..2d3251195 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -392,6 +392,7 @@ lble_task_cb(task_param_t param, task_prio_t prio) { lua_pop(L, 1); // Throw away the null write pointer } lua_pop(L, 1); // THrow away the value + message.errcode = 0; } cleanup: @@ -533,7 +534,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp, const uint handles[0]++; lua_pushinteger(L, handles[0]); - lua_setfield(L, -2, "notify"); + lua_setfield(L, -3, "notify"); chr->val_handle = &handles[handles[0]]; } lua_pop(L, 1); @@ -939,7 +940,7 @@ static int lble_notify(lua_State *L) { } int handle = luaL_checkinteger(L, 1); - luaL_argcheck(L, handle <= 0 || handle > notify_handles[0], 1, "handle out of range"); + luaL_argcheck(L, handle > 0 && handle <= notify_handles[0], 1, "handle out of range"); ble_gatts_chr_updated(notify_handles[handle]); From 225217cfeaca936fed7a9ea2ea80ddde92407747 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 20 Feb 2022 21:31:12 +0000 Subject: [PATCH 20/27] Updated the Kconfig to note that you have to enable the Nimble module as well. --- components/modules/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/modules/Kconfig b/components/modules/Kconfig index 6e5558066..4a34195d4 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -20,7 +20,7 @@ menu "NodeMCU modules" select NODEMCU_CMODULE_STRUCT select BT_ENABLED help - Includes the simple Bluetooth GAP/GATT module. + Includes the simple Bluetooth GAP/GATT module. You must enable the Nimble BLE module as well. config NODEMCU_CMODULE_BTHCI bool "Bluetooth HCI interface module" From 891cf01b87556c4cae3adbb5cd0150d3469b28ad Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sat, 5 Mar 2022 22:25:12 +0000 Subject: [PATCH 21/27] Review comments --- components/modules/ble.c | 22 ++++++++++++---------- components/modules/ledc.c | 2 -- docs/modules/ble.md | 4 +++- install.sh | 2 ++ 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index 2d3251195..e8551dd87 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -34,6 +34,10 @@ #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" @@ -196,7 +200,8 @@ free_gatt_svcs(lua_State *L, const struct ble_gatt_svc_def * svcs) { static int lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { - // Actually the only thing we care about is the arg and the ctxt + UNUSED(conn_handle); + UNUSED(attr_handle); size_t task_block_size = sizeof(task_block_t); @@ -220,6 +225,7 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces 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; } } @@ -232,7 +238,7 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces response_message_t message; while (1) { - if (xQueueReceive(response_queue, &message, (TickType_t) (10000/portTICK_PERIOD_MS) ) != pdPASS) { + if (xQueueReceive(response_queue, &message, (TickType_t) (2000/portTICK_PERIOD_MS) ) != pdPASS) { free(task_block); return BLE_ATT_ERR_UNLIKELY; } @@ -270,6 +276,8 @@ lble_task_cb(task_param_t param, task_prio_t prio) { 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"); @@ -391,12 +399,12 @@ lble_task_cb(task_param_t param, task_prio_t prio) { } else { lua_pop(L, 1); // Throw away the null write pointer } - lua_pop(L, 1); // THrow away the value + lua_pop(L, 1); // Throw away the value message.errcode = 0; } cleanup: - lua_pop(L, 2); + lua_settop(L, top); message.seqno = task_block->seqno; xQueueSend(response_queue, &message, (TickType_t) 0); @@ -563,7 +571,6 @@ gatt_svr_init(lua_State *L) { struct ble_gatt_svc_def *svcs = NULL; lble_build_gatt_svcs(L, &svcs, ¬ify_handles); - //free_gatt_svcs(L, gatt_svr_svcs); gatt_svr_svcs = svcs; rc = ble_gatts_count_cfg(gatt_svr_svcs); @@ -944,11 +951,6 @@ static int lble_notify(lua_State *L) { ble_gatts_chr_updated(notify_handles[handle]); -/* - if (rc) { - return luaL_error(L, "Must supply a valid handle"); - } -*/ return 0; } diff --git a/components/modules/ledc.c b/components/modules/ledc.c index 28289aedd..add40b6db 100644 --- a/components/modules/ledc.c +++ b/components/modules/ledc.c @@ -30,8 +30,6 @@ static int lledc_new_channel( lua_State *L ) ledc_timer.timer_num = opt_checkint_range(L, "timer", -1, 0, LEDC_TIMER_MAX-1); - ledc_timer.clk_cfg = LEDC_AUTO_CLK; - /* Setup channel */ ledc_channel_config_t channel_config = { .speed_mode = ledc_timer.speed_mode, diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 4572747db..561cd5f06 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -120,11 +120,13 @@ The characteristic table contains the following keys: - `uuid` The UUID of the characteristics. This can be either a 16 byte string or a 2 byte string that identifies the particular characteristic. Typically, 2 byte strings are used for well-known characteristics. - `type` This is the optional type of the value. It has the same value as a unpack code in the `struct` module. -- `value` This is the actual value of the characteristic. This will be a string of bytes unless a `type` value is set. +- `value` This is the actual value of the characteristic. This will be a string of bytes (unless `type` is set). - `read` This is a function that will be invoked to read the value (and so does not need the `value` entry). It should return a string of bytes (unless `type` is set). - `write` This is a function that will be invoked to write the value (and so does not need the `value` entry). It is given a string of bytes (unless `type` is set) - `notify` If this attribute is present then notifications are supported on this characteristic. The value of the `notify` attribute is updated to be an integer which is the value to be passed into `ble.notify()` +In the above functions, the value is that passed to/from the write/read functions is of the type specified by the `type` key. If this key is missing, then the default type is a string of bytes. For example, if `type` is `'B'` then the value is an integer (in the range 0 - 255) and the bluetooth client will see a single byte containing that value. + If the `value` key is present, then the characteristic is read/write. However, if one or `read` or `write` is set to `true`, then it restricts access to that mode. The characteristics are treated as read/write unless only one of the `read` or `write` keys is present and the `value` key is not specified. diff --git a/install.sh b/install.sh index c93e51210..cf18df457 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + echo "Installing IDF prerequisites..." IDF_DIR=./sdk/esp32-esp-idf From 3e5ba2843c04c8543377047fb3ab25c8e70b710c Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 6 Mar 2022 14:21:05 +0000 Subject: [PATCH 22/27] Moved the flash init logic --- components/base_nodemcu/user_main.c | 8 +++++++- components/modules/ble.c | 13 ------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/components/base_nodemcu/user_main.c b/components/base_nodemcu/user_main.c index adb8832fd..0735c2a4a 100644 --- a/components/base_nodemcu/user_main.c +++ b/components/base_nodemcu/user_main.c @@ -160,7 +160,13 @@ void __attribute__((noreturn)) app_main(void) nodemcu_init (); - nvs_flash_init (); + // This is the standard flash init sequence + int rc = nvs_flash_init(); + if (rc == ESP_ERR_NVS_NO_FREE_PAGES || rc == ESP_ERR_NVS_NEW_VERSION_FOUND) { + nvs_flash_erase(); + nvs_flash_init(); + } + esp_netif_init (); start_lua (); diff --git a/components/modules/ble.c b/components/modules/ble.c index e8551dd87..3d465abe4 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -19,7 +19,6 @@ #include "sdkconfig.h" #ifdef CONFIG_NODEMCU_CMODULE_BLE -#include "nvs_flash.h" #include #include @@ -615,18 +614,6 @@ lble_print_conn_desc(struct ble_gap_conn_desc *desc) static int lble_sys_init(lua_State *L) { - int rc = nvs_flash_init(); - if (rc == ESP_ERR_NVS_NO_FREE_PAGES || rc == ESP_ERR_NVS_NEW_VERSION_FOUND) { - rc = nvs_flash_erase(); - if (rc) { - return luaL_error(L, "Failed to erase flash: %d", rc); - } - rc = nvs_flash_init(); - } - if (rc) { - return luaL_error(L, "Failed to init flash: %d", rc); - } - task_handle = task_get_id(lble_task_cb); response_queue = xQueueCreate(2, sizeof(response_message_t)); From 1c26ba35d04a5393da687dfe169fed5d88514146 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 6 Mar 2022 15:09:19 -0500 Subject: [PATCH 23/27] FIx missing } in an example --- docs/modules/ble.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 561cd5f06..59faff8a8 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -30,9 +30,9 @@ function read_battery_level() -- This ought to do something better! return 50 end -local battery = { uuid="180f", characteristics={ {uuid="2a19", type='B', read=read_battery_level} } } -local myservice = {uuid="0123456789abcdef0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}} -local config = {name="MyGadget=", services={ myservice, battery } +battery = { uuid="180f", characteristics={ {uuid="2a19", type='B', read=read_battery_level} } } +myservice = {uuid="0123456789abcdef0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}} +config = {name="MyGadget", services={ myservice, battery } } ble.init(config) ``` From aaaa440d164f0da5bb68c42774603e2e7ac32983 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Mon, 15 Jan 2024 09:31:03 -0500 Subject: [PATCH 24/27] First attempt to add name support to characteristics --- components/modules/ble.c | 88 +++++++++++++++++++++++++++++++--------- docs/modules/ble.md | 3 +- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index 3d465abe4..825a24deb 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -56,6 +56,16 @@ #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(); @@ -249,7 +259,8 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces int rc = BLE_ATT_ERR_UNLIKELY; - if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { + 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)) { @@ -282,6 +293,8 @@ lble_task_cb(task_param_t param, task_prio_t prio) { 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"); @@ -326,20 +339,15 @@ lble_task_cb(task_param_t param, task_prio_t prio) { lua_remove(L, -2); // remove the old value // now have string (-1), struct(-2), chrtable (-3) } - size_t datalen; - const char *data = lua_tolstring(L, -1, &datalen); - - if (data) { - message.buffer = malloc(datalen); - if (message.buffer) { - message.length = datalen; - memcpy(message.buffer, data, datalen); - message.errcode = 0; - } - } - lua_pop(L, 1); - message.errcode = 0; + 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); @@ -398,10 +406,19 @@ lble_task_cb(task_param_t param, task_prio_t prio) { } else { lua_pop(L, 1); // Throw away the null write pointer } - lua_pop(L, 1); // Throw away the value - message.errcode = 0; } + 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; @@ -424,6 +441,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp, const uint // -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); @@ -437,13 +455,23 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp, const uint lua_pop(L, 1); // -1 is the service again lua_getfield(L, -1, "characteristics"); - nc += lua_rawlen(L, -1); + 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\n", ns, nc); + 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); + 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); @@ -456,7 +484,8 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp, const uint 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); - uint16_t *handles = (uint16_t *) (uuids + 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 @@ -549,6 +578,25 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp, const uint // -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 diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 59faff8a8..9f80204b0 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -30,7 +30,7 @@ function read_battery_level() -- This ought to do something better! return 50 end -battery = { uuid="180f", characteristics={ {uuid="2a19", type='B', read=read_battery_level} } } +battery = { uuid="180f", characteristics={ {uuid="2a19", type='B', read=read_battery_level, name="Battery percentage"} } } myservice = {uuid="0123456789abcdef0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}} config = {name="MyGadget", services={ myservice, battery } } ble.init(config) @@ -124,6 +124,7 @@ The characteristic table contains the following keys: - `read` This is a function that will be invoked to read the value (and so does not need the `value` entry). It should return a string of bytes (unless `type` is set). - `write` This is a function that will be invoked to write the value (and so does not need the `value` entry). It is given a string of bytes (unless `type` is set) - `notify` If this attribute is present then notifications are supported on this characteristic. The value of the `notify` attribute is updated to be an integer which is the value to be passed into `ble.notify()` +- `name` If this attribute is present, then an additional descriptor is added containing the name of the characteristic. In the above functions, the value is that passed to/from the write/read functions is of the type specified by the `type` key. If this key is missing, then the default type is a string of bytes. For example, if `type` is `'B'` then the value is an integer (in the range 0 - 255) and the bluetooth client will see a single byte containing that value. From 55ab2fa7e9dd68accce968c5e634eb3fdc2289a1 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Mon, 15 Jan 2024 11:44:00 -0500 Subject: [PATCH 25/27] Now correctly supports adding User Description desriptors --- components/modules/ble.c | 11 ++++++----- docs/modules/ble.md | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index 825a24deb..f165974b5 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -260,7 +260,7 @@ lble_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_acces int rc = BLE_ATT_ERR_UNLIKELY; if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR || - ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { + ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) { rc = message.errcode; if (rc == 0) { if (os_mbuf_append(ctxt->om, message.buffer, message.length)) { @@ -343,7 +343,7 @@ lble_task_cb(task_param_t param, task_prio_t prio) { } if (task_block->ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) { - lua_getfield(L, -2, "name"); + lua_getfield(L, -2, "description"); // Now we have the name (-1), struct (-2), table (-3) data = lua_tolstring(L, -1, &datalen); } @@ -460,7 +460,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp, const uint // 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) { + if (lua_getfield(L, -1, "description") != LUA_TNIL) { nd++; } lua_pop(L, 2); @@ -575,11 +575,12 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp, const uint } lua_pop(L, 1); + lua_pushvalue(L, -1); // duplicate the characteristic // -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 (lua_getfield(L, -1, "description") != LUA_TNIL) { if ((void *) (dsc + 2) > eom) { free_gatt_svcs(L, result); return luaL_error(L, "Miscalculated memory requirements"); @@ -596,7 +597,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp, const uint dsc += 2; } - lua_pop(L, 1); + lua_pop(L, 2); } lua_pop(L, 2); chrs++; // terminate the list of characteristics for this service diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 9f80204b0..8fab2bd96 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -30,8 +30,8 @@ function read_battery_level() -- This ought to do something better! return 50 end -battery = { uuid="180f", characteristics={ {uuid="2a19", type='B', read=read_battery_level, name="Battery percentage"} } } -myservice = {uuid="0123456789abcdef0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}} +battery = { uuid="180f", characteristics={ {uuid="2a19", type='B', read=read_battery_level, description="Battery percentage"} } } +myservice = {uuid="0123456789abcdef0123456789abcdef", characteristics={{uuid="1234", value=0, type='c', description='Testing123'}}} config = {name="MyGadget", services={ myservice, battery } } ble.init(config) ``` @@ -124,7 +124,7 @@ The characteristic table contains the following keys: - `read` This is a function that will be invoked to read the value (and so does not need the `value` entry). It should return a string of bytes (unless `type` is set). - `write` This is a function that will be invoked to write the value (and so does not need the `value` entry). It is given a string of bytes (unless `type` is set) - `notify` If this attribute is present then notifications are supported on this characteristic. The value of the `notify` attribute is updated to be an integer which is the value to be passed into `ble.notify()` -- `name` If this attribute is present, then an additional descriptor is added containing the name of the characteristic. +- `description` If this attribute is present, then an additional descriptor is added containing the description of the characteristic. In the above functions, the value is that passed to/from the write/read functions is of the type specified by the `type` key. If this key is missing, then the default type is a string of bytes. For example, if `type` is `'B'` then the value is an integer (in the range 0 - 255) and the bluetooth client will see a single byte containing that value. From 3e4dd23f90f9214b0faca6e1071eafd08ca46d0b Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Wed, 31 Jan 2024 13:14:42 -0500 Subject: [PATCH 26/27] Now runs on idf5 and you can start and stop the stack! --- components/modules/ble.c | 31 +++++-------------------------- docs/modules/ble.md | 11 +++-------- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index f165974b5..64f628f17 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -77,14 +77,14 @@ static const char *gadget_name; static uint8_t *gadget_mfg; static size_t gadget_mfg_len; +static ble_uuid_t *cud_uuid = BLE_UUID16_DECLARE(0x2901); + 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; @@ -588,9 +588,7 @@ lble_build_gatt_svcs(lua_State *L, struct ble_gatt_svc_def **resultp, const uint chr->descriptors = dsc; - dsc->uuid = (const ble_uuid_t*)(ble_uuid16_t[]) { - BLE_UUID16_INIT(0x2901) - }; + dsc->uuid = cud_uuid; dsc->att_flags = BLE_ATT_F_READ; dsc->access_cb = lble_access_cb; dsc->arg = chr->arg; @@ -666,6 +664,8 @@ lble_sys_init(lua_State *L) { task_handle = task_get_id(lble_task_cb); response_queue = xQueueCreate(2, sizeof(response_message_t)); + esp_log_level_set("NimBLE", ESP_LOG_WARN); + return 0; } @@ -678,15 +678,6 @@ lble_host_task(void *param) 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(); @@ -994,10 +985,6 @@ 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"); @@ -1066,10 +1053,6 @@ static int lble_init(lua_State *L) { } 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(); @@ -1080,10 +1063,6 @@ static int lble_shutdown(lua_State *L) { 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; diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 8fab2bd96..d6cd31c80 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -11,10 +11,6 @@ This allows you to build simple gadgets that can be interrogated and controlled This initializes the Bluetooth stack and starts advertising according to the data in the configuration table. See below for a detailed description of this table. -At the present time, you can only call the `init` function once. There is some problem -in the underlying implementation of the BLE stack that prevents a `init`, `shutdown`, `init` -sequence from working. - #### Syntax `ble.init(ble_config)` @@ -65,7 +61,7 @@ Updates the advertising data field for future advertising frames. #### Parameters -- `advertisement` This string will be placed in future advertising frames as the manufacturer data field. This overrides the a`advertisement` value from the config block. +- `advertisement` This string will be placed in future advertising frames as the manufacturer data field. This overrides the `advertisement` value from the config block. #### Returns `nil` @@ -77,8 +73,7 @@ ble.advertise("foo") ## ble.shutdown() -Shuts down the Bluetooth controller and returns it to the state where another `init` ought to work (but currently doesn't). And, at the moment, shutting -it down doesn't work either -- it appears to corrupt some deep data structures. +Shuts down the Bluetooth controller and returns it to the state where another `init` ought to work. #### Syntax `ble.shutdown()` @@ -153,4 +148,4 @@ end ### Type conversions -If the `type` value converts a single item, then that will be the value that is placed into the `value` element. If it converts multiple elements, then the elements will be placed into an array that that will be plaed into the `value` element. +If the `type` value converts a single item, then that will be the value that is placed into the `value` element. If it converts multiple elements, then the elements will be placed into an array that that will be placed into the `value` element. From 0b6205b9e7a62ca8e2031af37ea096e4e031d10e Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Wed, 31 Jan 2024 19:45:18 -0500 Subject: [PATCH 27/27] Fix memory leak now that we can shutdown the stack --- components/modules/ble.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/modules/ble.c b/components/modules/ble.c index 64f628f17..1a7c59098 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -1063,6 +1063,9 @@ static int lble_shutdown(lua_State *L) { nimble_port_deinit(); + free_gatt_svcs(L, gatt_svr_svcs); + gatt_svr_svcs = NULL; + inited = STOPPED; return 0;