init: initial commit
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "src/ble_scanner.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES bt esp_event
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Called for every unique BLE advertisement received.
|
||||
* tag_id is a null-terminated string: "aa:bb:cc:dd:ee:ff"
|
||||
* rssi is in dBm (negative).
|
||||
*/
|
||||
typedef void (*ble_scanner_cb_t)(const char *tag_id, int8_t rssi);
|
||||
|
||||
/**
|
||||
* Initialise the Bluedroid BLE stack and register the scan callback.
|
||||
* Must be called once after esp_bt_controller_init / esp_bluedroid_init.
|
||||
*/
|
||||
void ble_scanner_init(ble_scanner_cb_t cb);
|
||||
|
||||
/** Start passive BLE scanning. */
|
||||
void ble_scanner_start(void);
|
||||
|
||||
/** Stop BLE scanning. */
|
||||
void ble_scanner_stop(void);
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "ble_scanner.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_log.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "ble_scanner"
|
||||
|
||||
static ble_scanner_cb_t s_cb = NULL;
|
||||
|
||||
/* Passive scan: 100ms interval, 50ms window (50% duty cycle) */
|
||||
static esp_ble_scan_params_t s_scan_params = {
|
||||
.scan_type = BLE_SCAN_TYPE_PASSIVE,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_interval = 0xA0, /* 100ms: 0xA0 * 0.625ms */
|
||||
.scan_window = 0x50, /* 50ms: 0x50 * 0.625ms */
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
|
||||
};
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event,
|
||||
esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||
if (param->scan_param_cmpl.status == ESP_BT_STATUS_SUCCESS) {
|
||||
esp_ble_gap_start_scanning(0); /* 0 = scan indefinitely */
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Scan param set failed: %d", param->scan_param_cmpl.status);
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(TAG, "Scan start failed: %d", param->scan_start_cmpl.status);
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
|
||||
esp_ble_gap_cb_param_t *p = param;
|
||||
if (p->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT && s_cb) {
|
||||
char tag_id[18];
|
||||
uint8_t *a = p->scan_rst.bda;
|
||||
snprintf(tag_id, sizeof(tag_id),
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
a[0], a[1], a[2], a[3], a[4], a[5]);
|
||||
s_cb(tag_id, p->scan_rst.rssi);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Scan stopped");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ble_scanner_init(ble_scanner_cb_t cb)
|
||||
{
|
||||
s_cb = cb;
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));
|
||||
ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));
|
||||
ESP_ERROR_CHECK(esp_bluedroid_init());
|
||||
ESP_ERROR_CHECK(esp_bluedroid_enable());
|
||||
|
||||
ESP_ERROR_CHECK(esp_ble_gap_register_callback(gap_event_handler));
|
||||
ESP_LOGI(TAG, "BLE scanner initialised");
|
||||
}
|
||||
|
||||
void ble_scanner_start(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Starting BLE scan");
|
||||
ESP_ERROR_CHECK(esp_ble_gap_set_scan_params(&s_scan_params));
|
||||
/* Scanning begins in the param-set callback once confirmed */
|
||||
}
|
||||
|
||||
void ble_scanner_stop(void)
|
||||
{
|
||||
esp_ble_gap_stop_scanning();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "src/config_store.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES nvs_flash
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#define MQTT_HOST_MAX_LEN 128
|
||||
#define MQTT_URI_MAX_LEN 256
|
||||
|
||||
esp_err_t config_store_init(void);
|
||||
bool config_store_is_provisioned(void);
|
||||
esp_err_t config_store_set_provisioned(void);
|
||||
esp_err_t config_store_clear_provisioned(void);
|
||||
|
||||
/* Store an optional manual MQTT broker override (used if mDNS fails). */
|
||||
esp_err_t config_store_set_mqtt_override(const char *host, uint16_t port);
|
||||
|
||||
/* Returns ESP_ERR_NVS_NOT_FOUND if no override is stored. */
|
||||
esp_err_t config_store_get_mqtt_override(char host_out[MQTT_HOST_MAX_LEN], uint16_t *port_out);
|
||||
|
||||
/* Returns ESP_ERR_NVS_NOT_FOUND if no override is stored. */
|
||||
esp_err_t config_store_get_mqtt_override_uri(char *uri_out, size_t uri_max_len);
|
||||
@@ -0,0 +1,98 @@
|
||||
#include "config_store.h"
|
||||
#include "esp_err.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
#include "esp_log.h"
|
||||
#include <string.h>
|
||||
|
||||
#define NS "anchor_cfg"
|
||||
#define KEY_PROV "provisioned"
|
||||
#define KEY_HOST "mqtt_host"
|
||||
#define KEY_PORT "mqtt_port"
|
||||
|
||||
static const char *TAG = "config_store";
|
||||
|
||||
esp_err_t config_store_init(void)
|
||||
{
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_LOGW(TAG, "NVS needs erase, erasing...");
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
bool config_store_is_provisioned(void)
|
||||
{
|
||||
nvs_handle_t h;
|
||||
if (nvs_open(NS, NVS_READONLY, &h) != ESP_OK) return false;
|
||||
uint8_t val = 0;
|
||||
nvs_get_u8(h, KEY_PROV, &val);
|
||||
nvs_close(h);
|
||||
return val == 1;
|
||||
}
|
||||
|
||||
esp_err_t config_store_set_provisioned(void)
|
||||
{
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open(NS, NVS_READWRITE, &h);
|
||||
if (err != ESP_OK) return err;
|
||||
err = nvs_set_u8(h, KEY_PROV, 1);
|
||||
if (err == ESP_OK) err = nvs_commit(h);
|
||||
nvs_close(h);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t config_store_clear_provisioned(void)
|
||||
{
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open(NS, NVS_READWRITE, &h);
|
||||
if (err != ESP_OK) return err;
|
||||
nvs_erase_key(h, KEY_PROV);
|
||||
nvs_erase_key(h, KEY_HOST);
|
||||
nvs_erase_key(h, KEY_PORT);
|
||||
err = nvs_commit(h);
|
||||
nvs_close(h);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t config_store_set_mqtt_override(const char *host, uint16_t port)
|
||||
{
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open(NS, NVS_READWRITE, &h);
|
||||
if (err != ESP_OK) return err;
|
||||
err = nvs_set_str(h, KEY_HOST, host);
|
||||
if (err == ESP_OK) err = nvs_set_u16(h, KEY_PORT, port);
|
||||
if (err == ESP_OK) err = nvs_commit(h);
|
||||
nvs_close(h);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t config_store_get_mqtt_override(char host_out[MQTT_HOST_MAX_LEN], uint16_t *port_out)
|
||||
{
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open(NS, NVS_READONLY, &h);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
size_t len = MQTT_HOST_MAX_LEN;
|
||||
err = nvs_get_str(h, KEY_HOST, host_out, &len);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_get_u16(h, KEY_PORT, port_out);
|
||||
}
|
||||
nvs_close(h);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t config_store_get_mqtt_override_uri(char *uri_out, size_t uri_max_len)
|
||||
{
|
||||
char broker_host[MQTT_HOST_MAX_LEN] = {0};
|
||||
uint16_t broker_port;
|
||||
|
||||
bool resolved
|
||||
= (config_store_get_mqtt_override(broker_host, &broker_port) == ESP_OK);
|
||||
if(!resolved) return ESP_ERR_NVS_NOT_FOUND;
|
||||
|
||||
snprintf(uri_out, uri_max_len, "mqtt://%s:%u", broker_host, broker_port);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "src/led_indicator.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES driver esp_timer
|
||||
PRIV_REQUIRES esp_driver_gpio
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
LED_OFF,
|
||||
LED_PROVISIONING, /* 200ms toggle — awaiting provisioning */
|
||||
LED_CONNECTING, /* 1000ms toggle — connecting to WiFi/MQTT */
|
||||
LED_SCANNING, /* solid on — normal BLE scanning */
|
||||
LED_CALIBRATING, /* 500ms toggle — calibration in progress */
|
||||
LED_SELECTED, /* triple-flash loop — physical identification */
|
||||
LED_ERROR, /* 50ms rapid blink */
|
||||
} led_state_t;
|
||||
|
||||
void led_indicator_init(void);
|
||||
void led_indicator_set(led_state_t state);
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "led_indicator.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define LED_GPIO GPIO_NUM_2
|
||||
|
||||
static esp_timer_handle_t s_timer;
|
||||
static led_state_t s_state = LED_OFF;
|
||||
static int s_phase = 0; /* generic phase counter for multi-step patterns */
|
||||
|
||||
static void set_level(int lvl) { gpio_set_level(LED_GPIO, lvl); }
|
||||
|
||||
static void IRAM_ATTR timer_cb(void *arg)
|
||||
{
|
||||
switch (s_state) {
|
||||
case LED_OFF:
|
||||
set_level(0);
|
||||
break;
|
||||
|
||||
case LED_PROVISIONING:
|
||||
set_level(s_phase ^= 1);
|
||||
esp_timer_start_once(s_timer, 200 * 1000);
|
||||
break;
|
||||
|
||||
case LED_CONNECTING:
|
||||
set_level(s_phase ^= 1);
|
||||
esp_timer_start_once(s_timer, 1000 * 1000);
|
||||
break;
|
||||
|
||||
case LED_SCANNING:
|
||||
set_level(1);
|
||||
break;
|
||||
|
||||
case LED_CALIBRATING:
|
||||
set_level(s_phase ^= 1);
|
||||
esp_timer_start_once(s_timer, 500 * 1000);
|
||||
break;
|
||||
|
||||
case LED_SELECTED: {
|
||||
/* Triple flash: on/off/on/off/on/off, then 1s dark. s_phase 0-5 = flashes, 6 = pause */
|
||||
static const uint64_t us[] = {100000,100000,100000,100000,100000,100000,1000000};
|
||||
if (s_phase < 6) {
|
||||
set_level(s_phase % 2 == 0 ? 1 : 0);
|
||||
} else {
|
||||
set_level(0);
|
||||
}
|
||||
uint64_t delay = us[s_phase];
|
||||
s_phase = (s_phase + 1) % 7;
|
||||
esp_timer_start_once(s_timer, delay);
|
||||
break;
|
||||
}
|
||||
|
||||
case LED_ERROR:
|
||||
set_level(s_phase ^= 1);
|
||||
esp_timer_start_once(s_timer, 50 * 1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void led_indicator_init(void)
|
||||
{
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = (1ULL << LED_GPIO),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io);
|
||||
|
||||
esp_timer_create_args_t args = {
|
||||
.callback = timer_cb,
|
||||
.name = "led",
|
||||
};
|
||||
esp_timer_create(&args, &s_timer);
|
||||
}
|
||||
|
||||
void led_indicator_set(led_state_t state)
|
||||
{
|
||||
esp_timer_stop(s_timer);
|
||||
s_state = state;
|
||||
s_phase = 0;
|
||||
|
||||
if (state == LED_SCANNING) {
|
||||
set_level(1);
|
||||
} else if (state == LED_OFF) {
|
||||
set_level(0);
|
||||
} else {
|
||||
/* Kick off the timer-driven pattern immediately */
|
||||
esp_timer_start_once(s_timer, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "src/mqtt_publisher.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES mqtt esp_event
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
dependencies:
|
||||
espressif/cjson: "*"
|
||||
idf:
|
||||
version: ">=5.1.0"
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
/* Event bits set by the MQTT publisher into the shared event group */
|
||||
#define MQTT_CONNECTED_BIT BIT0
|
||||
#define MQTT_CALIBRATE_START BIT1
|
||||
#define MQTT_CALIBRATE_STOP BIT2
|
||||
#define MQTT_SELECTED_BIT BIT3
|
||||
#define MQTT_DESELECTED_BIT BIT4
|
||||
|
||||
/**
|
||||
* Initialise and connect the MQTT client.
|
||||
*
|
||||
* sensor_id — stable anchor ID string (e.g. "anchor_a1b2c3")
|
||||
* broker_uri — e.g. "mqtt://192.168.1.100:1883"
|
||||
* evt_group — FreeRTOS event group; publisher sets bits above on events
|
||||
*/
|
||||
esp_err_t mqtt_publisher_init(const char *sensor_id,
|
||||
const char *broker_uri,
|
||||
EventGroupHandle_t evt_group);
|
||||
|
||||
/** Publish an RSSI reading. Non-blocking (QoS 1). */
|
||||
void mqtt_publisher_send_rssi(const char *tag_id, int8_t rssi);
|
||||
|
||||
/** Publish the announce message (empty payload). */
|
||||
void mqtt_publisher_announce(void);
|
||||
@@ -0,0 +1,122 @@
|
||||
#include "mqtt_publisher.h"
|
||||
#include "mqtt_client.h"
|
||||
#include "esp_log.h"
|
||||
#include "cJSON.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define TAG "mqtt_publisher"
|
||||
#define TOPIC_PREFIX "localiser/sensor"
|
||||
|
||||
static esp_mqtt_client_handle_t s_client = NULL;
|
||||
static char s_sensor_id[32];
|
||||
static EventGroupHandle_t s_evt = NULL;
|
||||
|
||||
/* Pre-built topic strings */
|
||||
static char s_topic_rssi[96];
|
||||
static char s_topic_announce[96];
|
||||
static char s_topic_cmd[96];
|
||||
|
||||
static void build_topics(void)
|
||||
{
|
||||
snprintf(s_topic_rssi, sizeof(s_topic_rssi),
|
||||
"%s/%s/rssi", TOPIC_PREFIX, s_sensor_id);
|
||||
snprintf(s_topic_announce, sizeof(s_topic_announce),
|
||||
"%s/%s/announce", TOPIC_PREFIX, s_sensor_id);
|
||||
snprintf(s_topic_cmd, sizeof(s_topic_cmd),
|
||||
"%s/%s/cmd", TOPIC_PREFIX, s_sensor_id);
|
||||
}
|
||||
|
||||
static void handle_cmd(const char *data, int data_len)
|
||||
{
|
||||
char *buf = strndup(data, data_len);
|
||||
if (!buf) return;
|
||||
|
||||
cJSON *root = cJSON_Parse(buf);
|
||||
free(buf);
|
||||
if (!root) return;
|
||||
|
||||
cJSON *action = cJSON_GetObjectItemCaseSensitive(root, "action");
|
||||
if (cJSON_IsString(action)) {
|
||||
const char *a = action->valuestring;
|
||||
if (strcmp(a, "calibrate_start") == 0) xEventGroupSetBits(s_evt, MQTT_CALIBRATE_START);
|
||||
else if (strcmp(a, "calibrate_stop") == 0) xEventGroupSetBits(s_evt, MQTT_CALIBRATE_STOP);
|
||||
else if (strcmp(a, "selected") == 0) xEventGroupSetBits(s_evt, MQTT_SELECTED_BIT);
|
||||
else if (strcmp(a, "deselected") == 0) xEventGroupSetBits(s_evt, MQTT_DESELECTED_BIT);
|
||||
else ESP_LOGW(TAG, "Unknown cmd action: %s", a);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
static void mqtt_event_handler(void *arg, esp_event_base_t base,
|
||||
int32_t id, void *data)
|
||||
{
|
||||
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)data;
|
||||
switch ((esp_mqtt_event_id_t)id) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "Connected to broker");
|
||||
esp_mqtt_client_subscribe(s_client, s_topic_cmd, 1);
|
||||
xEventGroupSetBits(s_evt, MQTT_CONNECTED_BIT);
|
||||
mqtt_publisher_announce();
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
ESP_LOGW(TAG, "Disconnected from broker");
|
||||
xEventGroupClearBits(s_evt, MQTT_CONNECTED_BIT);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DATA:
|
||||
if (strncmp(event->topic, s_topic_cmd, event->topic_len) == 0) {
|
||||
handle_cmd(event->data, event->data_len);
|
||||
}
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_ERROR:
|
||||
ESP_LOGE(TAG, "MQTT error");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t mqtt_publisher_init(const char *sensor_id,
|
||||
const char *broker_uri,
|
||||
EventGroupHandle_t evt_group)
|
||||
{
|
||||
strncpy(s_sensor_id, sensor_id, sizeof(s_sensor_id) - 1);
|
||||
s_evt = evt_group;
|
||||
build_topics();
|
||||
|
||||
esp_mqtt_client_config_t cfg = {
|
||||
.broker.address.uri = broker_uri,
|
||||
.credentials.client_id = sensor_id,
|
||||
.session.keepalive = 30,
|
||||
.network.reconnect_timeout_ms = 5000,
|
||||
};
|
||||
|
||||
s_client = esp_mqtt_client_init(&cfg);
|
||||
if (!s_client) return ESP_FAIL;
|
||||
|
||||
ESP_ERROR_CHECK(esp_mqtt_client_register_event(
|
||||
s_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL));
|
||||
|
||||
return esp_mqtt_client_start(s_client);
|
||||
}
|
||||
|
||||
void mqtt_publisher_announce(void)
|
||||
{
|
||||
if (!s_client) return;
|
||||
esp_mqtt_client_publish(s_client, s_topic_announce, "", 0, 1, 0);
|
||||
ESP_LOGI(TAG, "Announced on %s", s_topic_announce);
|
||||
}
|
||||
|
||||
void mqtt_publisher_send_rssi(const char *tag_id, int8_t rssi)
|
||||
{
|
||||
if (!s_client) return;
|
||||
|
||||
char payload[80];
|
||||
int len = snprintf(payload, sizeof(payload),
|
||||
"{\"tag_id\":\"%s\",\"rssi\":%d}", tag_id, (int)rssi);
|
||||
esp_mqtt_client_publish(s_client, s_topic_rssi, payload, len, 1, 0);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
idf_component_register(
|
||||
SRCS "src/provisioning.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES
|
||||
network_provisioning
|
||||
esp_wifi
|
||||
esp_event
|
||||
nvs_flash
|
||||
config_store
|
||||
led_indicator
|
||||
bt
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencies:
|
||||
espressif/network_provisioning: "^1.2.4"
|
||||
espressif/cjson: "*"
|
||||
idf:
|
||||
version: ">=5.1.0"
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* Start BLE provisioning mode. Blocks until provisioning is complete,
|
||||
* then deinits the provisioning manager so BLE memory can be reclaimed.
|
||||
*
|
||||
* WiFi credentials are stored automatically by wifi_prov_mgr.
|
||||
* MQTT broker override (if sent by companion app) is stored via config_store.
|
||||
*/
|
||||
esp_err_t provisioning_run(void);
|
||||
@@ -0,0 +1,133 @@
|
||||
#include "provisioning.h"
|
||||
#include "config_store.h"
|
||||
#include "led_indicator.h"
|
||||
|
||||
#include "network_provisioning/manager.h"
|
||||
#include "network_provisioning/scheme_ble.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "cJSON.h"
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "provisioning"
|
||||
|
||||
/* Custom GATT endpoint name for MQTT settings. The companion app sends:
|
||||
* {"host":"192.168.1.100","port":1883} */
|
||||
#define MQTT_ENDPOINT "custom-mqtt-config"
|
||||
|
||||
static EventGroupHandle_t s_prov_evt;
|
||||
#define PROV_DONE_BIT BIT0
|
||||
|
||||
/* Called by wifi_prov_mgr when data arrives on the custom endpoint. */
|
||||
static esp_err_t mqtt_endpoint_handler(uint32_t session_id,
|
||||
const uint8_t *inbuf, ssize_t inlen,
|
||||
uint8_t **outbuf, ssize_t *outlen,
|
||||
void *priv)
|
||||
{
|
||||
if (!inbuf || inlen <= 0) return ESP_OK;
|
||||
|
||||
char *json = strndup((const char *)inbuf, inlen);
|
||||
if (!json) return ESP_ERR_NO_MEM;
|
||||
|
||||
cJSON *root = cJSON_Parse(json);
|
||||
free(json);
|
||||
if (!root) {
|
||||
ESP_LOGW(TAG, "Failed to parse MQTT config JSON");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
cJSON *host_j = cJSON_GetObjectItemCaseSensitive(root, "host");
|
||||
cJSON *port_j = cJSON_GetObjectItemCaseSensitive(root, "port");
|
||||
|
||||
if (cJSON_IsString(host_j) && cJSON_IsNumber(port_j)) {
|
||||
uint16_t port = (uint16_t)port_j->valuedouble;
|
||||
esp_err_t err = config_store_set_mqtt_override(host_j->valuestring, port);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "MQTT override stored: %s:%u", host_j->valuestring, port);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to store MQTT override: %s", esp_err_to_name(err));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "MQTT config missing 'host' or 'port'");
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
|
||||
/* Respond with a simple ack */
|
||||
const char *ack = "{\"status\":\"ok\"}";
|
||||
*outlen = strlen(ack);
|
||||
*outbuf = (uint8_t *)strdup(ack);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void prov_event_handler(void *arg, esp_event_base_t base,
|
||||
int32_t id, void *data)
|
||||
{
|
||||
if (base == NETWORK_PROV_EVENT) {
|
||||
switch (id) {
|
||||
case NETWORK_PROV_START:
|
||||
ESP_LOGI(TAG, "Provisioning started");
|
||||
break;
|
||||
case NETWORK_PROV_WIFI_CRED_RECV:
|
||||
ESP_LOGI(TAG, "WiFi credentials received");
|
||||
break;
|
||||
case NETWORK_PROV_WIFI_CRED_FAIL: {
|
||||
network_prov_wifi_sta_fail_reason_t *reason = (network_prov_wifi_sta_fail_reason_t *)data;
|
||||
ESP_LOGE(TAG, "Provisioning failed: %s",
|
||||
(*reason == NETWORK_PROV_WIFI_STA_AUTH_ERROR) ? "auth error" : "AP not found");
|
||||
break;
|
||||
}
|
||||
case NETWORK_PROV_WIFI_CRED_SUCCESS:
|
||||
ESP_LOGI(TAG, "WiFi credentials applied successfully");
|
||||
break;
|
||||
case NETWORK_PROV_END:
|
||||
ESP_LOGI(TAG, "Provisioning complete");
|
||||
network_prov_mgr_deinit();
|
||||
xEventGroupSetBits(s_prov_evt, PROV_DONE_BIT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t provisioning_run(void)
|
||||
{
|
||||
s_prov_evt = xEventGroupCreate();
|
||||
|
||||
esp_event_handler_register(NETWORK_PROV_EVENT, ESP_EVENT_ANY_ID,
|
||||
prov_event_handler, NULL);
|
||||
|
||||
network_prov_mgr_config_t config = {
|
||||
.scheme = network_prov_scheme_ble,
|
||||
.scheme_event_handler = NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM,
|
||||
};
|
||||
ESP_ERROR_CHECK(network_prov_mgr_init(config));
|
||||
|
||||
/* Register custom endpoint for MQTT broker config */
|
||||
network_prov_mgr_endpoint_create(MQTT_ENDPOINT);
|
||||
|
||||
/* Derive device name and PoP from WiFi MAC */
|
||||
uint8_t mac[6];
|
||||
esp_wifi_get_mac(WIFI_IF_STA, mac);
|
||||
char device_name[32];
|
||||
snprintf(device_name, sizeof(device_name),
|
||||
"anchor_%02x%02x%02x", mac[3], mac[4], mac[5]);
|
||||
|
||||
led_indicator_set(LED_PROVISIONING);
|
||||
|
||||
ESP_ERROR_CHECK(network_prov_mgr_start_provisioning(
|
||||
NETWORK_PROV_SECURITY_0, NULL, device_name, NULL));
|
||||
|
||||
network_prov_mgr_endpoint_register(MQTT_ENDPOINT, mqtt_endpoint_handler, NULL);
|
||||
|
||||
/* Block until provisioning completes */
|
||||
xEventGroupWaitBits(s_prov_evt, PROV_DONE_BIT, pdTRUE, pdTRUE, portMAX_DELAY);
|
||||
vEventGroupDelete(s_prov_evt);
|
||||
|
||||
config_store_set_provisioned();
|
||||
ESP_LOGI(TAG, "Provisioning done, NVS flag set");
|
||||
return ESP_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user