272 lines
7.4 KiB
C
272 lines
7.4 KiB
C
#include "metrics_ble.h"
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#include "esp_err.h"
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/queue.h"
|
|
#include "freertos/task.h"
|
|
#include "host/ble_gap.h"
|
|
#include "host/ble_hs.h"
|
|
#include "host/ble_uuid.h"
|
|
#include "host/util/util.h"
|
|
#include "metrics_protocol.h"
|
|
#include "nimble/nimble_port.h"
|
|
#include "nimble/nimble_port_freertos.h"
|
|
#include "nvs_flash.h"
|
|
#include "services/gap/ble_svc_gap.h"
|
|
#include "services/gatt/ble_svc_gatt.h"
|
|
#include "ui_screen.h"
|
|
|
|
static const char *TAG = "metrics_ble";
|
|
static const char *DEVICE_NAME = "RLCD-Metrics";
|
|
|
|
// Service: 9f4af548-1a14-4fc1-8f5a-4b9f00000001
|
|
// Write char: 9f4af548-1a14-4fc1-8f5a-4b9f00000002
|
|
static const ble_uuid128_t metrics_svc_uuid =
|
|
BLE_UUID128_INIT(0x01, 0x00, 0x00, 0x00, 0x9f, 0x4b, 0x5a, 0x8f,
|
|
0xc1, 0x4f, 0x14, 0x1a, 0x48, 0xf5, 0x4a, 0x9f);
|
|
static const ble_uuid128_t metrics_chr_uuid =
|
|
BLE_UUID128_INIT(0x02, 0x00, 0x00, 0x00, 0x9f, 0x4b, 0x5a, 0x8f,
|
|
0xc1, 0x4f, 0x14, 0x1a, 0x48, 0xf5, 0x4a, 0x9f);
|
|
|
|
static uint8_t own_addr_type;
|
|
static uint16_t metrics_chr_val_handle;
|
|
static QueueHandle_t s_metrics_queue;
|
|
static uint32_t s_ble_write_count;
|
|
|
|
void ble_store_config_init(void);
|
|
|
|
static int metrics_gap_event(struct ble_gap_event *event, void *arg);
|
|
|
|
static const char *metrics_phy_name(uint8_t phy)
|
|
{
|
|
switch (phy) {
|
|
case BLE_GAP_LE_PHY_1M:
|
|
return "1M";
|
|
case BLE_GAP_LE_PHY_2M:
|
|
return "2M";
|
|
case BLE_GAP_LE_PHY_CODED:
|
|
return "CODED";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
static void metrics_request_2m_phy(uint16_t conn_handle)
|
|
{
|
|
int rc = ble_gap_set_prefered_le_phy(conn_handle,
|
|
BLE_GAP_LE_PHY_2M_MASK,
|
|
BLE_GAP_LE_PHY_2M_MASK,
|
|
BLE_GAP_LE_PHY_CODED_ANY);
|
|
if (rc == 0) {
|
|
ESP_LOGI(TAG, "requested BLE 5 2M PHY");
|
|
} else {
|
|
ESP_LOGW(TAG, "request BLE 5 2M PHY failed: %d", rc);
|
|
}
|
|
}
|
|
|
|
static int metrics_gatt_access(uint16_t conn_handle, uint16_t attr_handle,
|
|
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
|
{
|
|
(void)conn_handle;
|
|
(void)arg;
|
|
|
|
if (ctxt->op != BLE_GATT_ACCESS_OP_WRITE_CHR || attr_handle != metrics_chr_val_handle) {
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
}
|
|
|
|
char line[512] = {0};
|
|
uint16_t len = 0;
|
|
int rc = ble_hs_mbuf_to_flat(ctxt->om, line, sizeof(line) - 1, &len);
|
|
if (rc != 0) {
|
|
return BLE_ATT_ERR_UNLIKELY;
|
|
}
|
|
line[len] = '\0';
|
|
|
|
ui_metrics_t metrics;
|
|
if (!metrics_parse_line(line, &metrics)) {
|
|
ESP_LOGW(TAG, "bad BLE metrics: %s", line);
|
|
ui_screen_set_status("BAD BLE DATA");
|
|
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
|
}
|
|
|
|
if (s_metrics_queue != NULL) {
|
|
xQueueOverwrite(s_metrics_queue, &metrics);
|
|
}
|
|
|
|
s_ble_write_count++;
|
|
if ((s_ble_write_count % 10) == 0) {
|
|
ESP_LOGI(TAG, "BLE metrics received: %lu", (unsigned long)s_ble_write_count);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct ble_gatt_svc_def metrics_gatt_svcs[] = {
|
|
{
|
|
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
|
.uuid = &metrics_svc_uuid.u,
|
|
.characteristics = (struct ble_gatt_chr_def[]) {
|
|
{
|
|
.uuid = &metrics_chr_uuid.u,
|
|
.access_cb = metrics_gatt_access,
|
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP,
|
|
.val_handle = &metrics_chr_val_handle,
|
|
},
|
|
{0},
|
|
},
|
|
},
|
|
{0},
|
|
};
|
|
|
|
static void metrics_advertise(void)
|
|
{
|
|
struct ble_hs_adv_fields fields;
|
|
memset(&fields, 0, sizeof(fields));
|
|
|
|
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
|
|
fields.name = (uint8_t *)DEVICE_NAME;
|
|
fields.name_len = strlen(DEVICE_NAME);
|
|
fields.name_is_complete = 1;
|
|
|
|
int rc = ble_gap_adv_set_fields(&fields);
|
|
if (rc != 0) {
|
|
ESP_LOGE(TAG, "set adv fields failed: %d", rc);
|
|
return;
|
|
}
|
|
|
|
struct ble_gap_adv_params adv_params;
|
|
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, metrics_gap_event, NULL);
|
|
if (rc != 0) {
|
|
ESP_LOGE(TAG, "start advertising failed: %d", rc);
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "BLE advertising as %s", DEVICE_NAME);
|
|
}
|
|
|
|
static int metrics_gap_event(struct ble_gap_event *event, void *arg)
|
|
{
|
|
(void)arg;
|
|
|
|
switch (event->type) {
|
|
case BLE_GAP_EVENT_CONNECT:
|
|
if (event->connect.status == 0) {
|
|
ESP_LOGI(TAG, "BLE connected");
|
|
metrics_request_2m_phy(event->connect.conn_handle);
|
|
} else {
|
|
metrics_advertise();
|
|
}
|
|
return 0;
|
|
|
|
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE:
|
|
ESP_LOGI(TAG, "BLE PHY updated: status=%d tx=%s rx=%s",
|
|
event->phy_updated.status,
|
|
metrics_phy_name(event->phy_updated.tx_phy),
|
|
metrics_phy_name(event->phy_updated.rx_phy));
|
|
return 0;
|
|
|
|
case BLE_GAP_EVENT_DISCONNECT:
|
|
ESP_LOGI(TAG, "BLE disconnected");
|
|
metrics_advertise();
|
|
return 0;
|
|
|
|
case BLE_GAP_EVENT_ADV_COMPLETE:
|
|
metrics_advertise();
|
|
return 0;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void metrics_on_reset(int reason)
|
|
{
|
|
ESP_LOGE(TAG, "BLE reset: %d", reason);
|
|
}
|
|
|
|
static void metrics_on_sync(void)
|
|
{
|
|
int rc = ble_hs_util_ensure_addr(0);
|
|
assert(rc == 0);
|
|
|
|
rc = ble_hs_id_infer_auto(0, &own_addr_type);
|
|
assert(rc == 0);
|
|
|
|
rc = ble_gap_set_prefered_default_le_phy(BLE_GAP_LE_PHY_2M_MASK,
|
|
BLE_GAP_LE_PHY_2M_MASK);
|
|
if (rc == 0) {
|
|
ESP_LOGI(TAG, "BLE 5 default PHY preference set to 2M");
|
|
} else {
|
|
ESP_LOGW(TAG, "set BLE 5 default PHY preference failed: %d", rc);
|
|
}
|
|
|
|
metrics_advertise();
|
|
}
|
|
|
|
static void metrics_host_task(void *param)
|
|
{
|
|
(void)param;
|
|
nimble_port_run();
|
|
nimble_port_freertos_deinit();
|
|
}
|
|
|
|
void metrics_ble_start(void)
|
|
{
|
|
esp_err_t nvs_ret = nvs_flash_init();
|
|
if (nvs_ret == ESP_ERR_NVS_NO_FREE_PAGES || nvs_ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
|
nvs_ret = nvs_flash_init();
|
|
}
|
|
ESP_ERROR_CHECK(nvs_ret);
|
|
|
|
if (s_metrics_queue == NULL) {
|
|
s_metrics_queue = xQueueCreate(1, sizeof(ui_metrics_t));
|
|
if (s_metrics_queue == NULL) {
|
|
ESP_LOGE(TAG, "failed to create BLE metrics queue");
|
|
return;
|
|
}
|
|
}
|
|
|
|
int rc = nimble_port_init();
|
|
if (rc != ESP_OK) {
|
|
ESP_LOGE(TAG, "nimble init failed: %d", rc);
|
|
return;
|
|
}
|
|
|
|
ble_hs_cfg.reset_cb = metrics_on_reset;
|
|
ble_hs_cfg.sync_cb = metrics_on_sync;
|
|
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
|
|
|
ble_svc_gap_init();
|
|
ble_svc_gatt_init();
|
|
|
|
rc = ble_gatts_count_cfg(metrics_gatt_svcs);
|
|
assert(rc == 0);
|
|
|
|
rc = ble_gatts_add_svcs(metrics_gatt_svcs);
|
|
assert(rc == 0);
|
|
|
|
rc = ble_svc_gap_device_name_set(DEVICE_NAME);
|
|
assert(rc == 0);
|
|
|
|
ble_store_config_init();
|
|
nimble_port_freertos_init(metrics_host_task);
|
|
}
|
|
|
|
bool metrics_ble_take_latest(ui_metrics_t *metrics)
|
|
{
|
|
if (metrics == NULL || s_metrics_queue == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return xQueueReceive(s_metrics_queue, metrics, 0) == pdTRUE;
|
|
}
|