317 lines
11 KiB
C
317 lines
11 KiB
C
#include "config_store.h"
|
|
#include "provisioning.h"
|
|
#include "ble_scanner.h"
|
|
#include "mqtt_publisher.h"
|
|
#include "led_indicator.h"
|
|
#include "ota_manager.h"
|
|
#include "esp_ota_ops.h"
|
|
#include "esp_coexist.h"
|
|
|
|
#include "esp_wifi.h"
|
|
#include "esp_event.h"
|
|
#include "esp_netif.h"
|
|
#include "esp_log.h"
|
|
#include "mdns.h"
|
|
#include "driver/gpio.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/event_groups.h"
|
|
#include "freertos/task.h"
|
|
#include "driver/gpio.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#define TAG "main"
|
|
|
|
#define WIFI_CONNECTED_BIT BIT8 /* must not clash with mqtt_publisher bits (BIT0-BIT7) */
|
|
|
|
#define DEFAULT_MQTT_PORT 1883
|
|
#define MDNS_QUERY_TIMEOUT_MS 3000
|
|
#define MDNS_RETRY_INTERVAL_MS 30000
|
|
|
|
static EventGroupHandle_t s_evt;
|
|
|
|
static void reset_button_task(void *arg)
|
|
{
|
|
gpio_config_t io_conf = {
|
|
.pin_bit_mask = 1ULL << CONFIG_SENSOR_RESET_GPIO,
|
|
.mode = GPIO_MODE_INPUT,
|
|
.pull_up_en = GPIO_PULLUP_ENABLE,
|
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
.intr_type = GPIO_INTR_DISABLE,
|
|
};
|
|
gpio_config(&io_conf);
|
|
|
|
TickType_t held_since = 0;
|
|
const TickType_t threshold = pdMS_TO_TICKS(CONFIG_SENSOR_RESET_HOLD_MS);
|
|
|
|
for (;;) {
|
|
if (gpio_get_level(CONFIG_SENSOR_RESET_GPIO) == 0) {
|
|
if (held_since == 0) held_since = xTaskGetTickCount();
|
|
if ((xTaskGetTickCount() - held_since) >= threshold) {
|
|
ESP_LOGI(TAG, "Reset button held %d ms — factory resetting", CONFIG_SENSOR_RESET_HOLD_MS);
|
|
led_indicator_set(LED_ERROR);
|
|
vTaskDelay(pdMS_TO_TICKS(500));
|
|
config_store_erase();
|
|
esp_restart();
|
|
}
|
|
} else {
|
|
held_since = 0;
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(50));
|
|
}
|
|
}
|
|
|
|
static void wifi_event_handler(void *arg, esp_event_base_t base,
|
|
int32_t id, void *data)
|
|
{
|
|
if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
|
|
ESP_LOGW(TAG, "WiFi disconnected, reconnecting...");
|
|
led_indicator_set(LED_CONNECTING);
|
|
xEventGroupClearBits(s_evt, WIFI_CONNECTED_BIT);
|
|
esp_wifi_connect();
|
|
} else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
|
|
ip_event_got_ip_t *evt = (ip_event_got_ip_t *)data;
|
|
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&evt->ip_info.ip));
|
|
xEventGroupSetBits(s_evt, WIFI_CONNECTED_BIT);
|
|
}
|
|
}
|
|
|
|
static const char *beacon_type_str(ble_beacon_type_t t)
|
|
{
|
|
switch (t) {
|
|
case BLE_BEACON_IBEACON: return "ibeacon";
|
|
case BLE_BEACON_ALTBEACON: return "altbeacon";
|
|
case BLE_BEACON_EDDYSTONE_UID: return "eddystone_uid";
|
|
case BLE_BEACON_EDDYSTONE_URL: return "eddystone_url";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
static void on_ble_scan_result(const ble_beacon_t *beacon)
|
|
{
|
|
mqtt_publisher_send_beacon(beacon_type_str(beacon->type),
|
|
beacon->id, beacon->tx_power, beacon->rssi);
|
|
}
|
|
|
|
static void wifi_init_sta(void)
|
|
{
|
|
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL);
|
|
esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL);
|
|
|
|
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
|
esp_netif_ip_info_t ip_info = {0};
|
|
if (netif && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK && ip_info.ip.addr != 0) {
|
|
ESP_LOGI(TAG, "WiFi already connected (IP: " IPSTR ")", IP2STR(&ip_info.ip));
|
|
xEventGroupSetBits(s_evt, WIFI_CONNECTED_BIT);
|
|
} else {
|
|
ESP_ERROR_CHECK(esp_wifi_connect());
|
|
}
|
|
}
|
|
|
|
static bool resolve_mqtt_broker_mdns(char *host_out, size_t host_len, uint16_t *port_out)
|
|
{
|
|
ESP_ERROR_CHECK(mdns_init());
|
|
|
|
mdns_result_t *results = NULL;
|
|
bool found = false;
|
|
|
|
esp_err_t err = mdns_query_ptr("_mqtt", "_tcp", MDNS_QUERY_TIMEOUT_MS, 4, &results);
|
|
if (err != ESP_OK || !results) {
|
|
ESP_LOGW(TAG, "mDNS query for _mqtt._tcp failed");
|
|
goto free_results;
|
|
}
|
|
|
|
mdns_result_t *r = results;
|
|
while (r) {
|
|
if (r->port) {
|
|
if (r->addr) {
|
|
snprintf(host_out, host_len, IPSTR, IP2STR(&r->addr->addr.u_addr.ip4));
|
|
*port_out = r->port;
|
|
ESP_LOGI(TAG, "mDNS found broker: %s:%u", host_out, *port_out);
|
|
found = true;
|
|
goto free_results;
|
|
} else if (r->hostname) {
|
|
esp_ip4_addr_t ip4;
|
|
if (mdns_query_a(r->hostname, MDNS_QUERY_TIMEOUT_MS, &ip4) == ESP_OK) {
|
|
snprintf(host_out, host_len, IPSTR, IP2STR(&ip4));
|
|
*port_out = r->port;
|
|
ESP_LOGI(TAG, "mDNS found broker: %s:%u", host_out, *port_out);
|
|
found = true;
|
|
goto free_results;
|
|
}
|
|
ESP_LOGW(TAG, "mDNS A query for %s failed", r->hostname);
|
|
}
|
|
}
|
|
r = r->next;
|
|
}
|
|
|
|
free_results:
|
|
mdns_query_results_free(results);
|
|
|
|
mdns_free();
|
|
return found;
|
|
}
|
|
|
|
void app_main(void)
|
|
{
|
|
ESP_ERROR_CHECK(config_store_init());
|
|
|
|
led_indicator_init();
|
|
led_indicator_set(LED_CONNECTING);
|
|
|
|
xTaskCreate(reset_button_task, "reset_btn", 4096, NULL, 1, NULL);
|
|
|
|
ESP_ERROR_CHECK(esp_netif_init());
|
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
|
|
|
s_evt = xEventGroupCreate();
|
|
|
|
/* WiFi hardware must be up before the provisioning manager runs */
|
|
esp_netif_create_default_wifi_sta();
|
|
wifi_init_config_t wcfg = WIFI_INIT_CONFIG_DEFAULT();
|
|
ESP_ERROR_CHECK(esp_wifi_init(&wcfg));
|
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
|
ESP_ERROR_CHECK(esp_wifi_start());
|
|
|
|
if (!config_store_is_provisioned()) {
|
|
ESP_LOGI(TAG, "Not provisioned - starting BLE provisioning");
|
|
provisioning_run(); /* blocks until complete */
|
|
/* BT memory released by NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM */
|
|
}
|
|
|
|
// connect to wifi
|
|
ESP_LOGI(TAG, "Connecting to WiFi...");
|
|
led_indicator_set(LED_CONNECTING);
|
|
|
|
// wifi_init_sta registers events and calls connect; skip if already inited
|
|
wifi_init_sta();
|
|
|
|
xEventGroupWaitBits(s_evt, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
|
|
ESP_LOGI(TAG, "WiFi connected");
|
|
|
|
// derive sensor_id from WiFi MAC
|
|
uint8_t mac[6];
|
|
esp_wifi_get_mac(WIFI_IF_STA, mac);
|
|
char sensor_id[32];
|
|
snprintf(sensor_id, sizeof(sensor_id),
|
|
"anchor_%02x%02x%02x", mac[3], mac[4], mac[5]);
|
|
ESP_LOGI(TAG, "Sensor ID: %s", sensor_id);
|
|
|
|
// resolve MQTT broker
|
|
char broker_host[MQTT_HOST_MAX_LEN] = {0};
|
|
uint16_t broker_port = DEFAULT_MQTT_PORT;
|
|
char broker_uri[160];
|
|
|
|
bool resolved = false;
|
|
while (!resolved) {
|
|
// favour config store override over mdns
|
|
resolved = (config_store_get_mqtt_override(broker_host, &broker_port) == ESP_OK);
|
|
if (resolved) {
|
|
ESP_LOGI(TAG, "Using NVS MQTT override: %s:%u", broker_host, broker_port);
|
|
} else {
|
|
resolved = resolve_mqtt_broker_mdns(broker_host, sizeof(broker_host), &broker_port);
|
|
}
|
|
|
|
if (!resolved) {
|
|
ESP_LOGW(TAG, "No broker found, retrying in %ds...", MDNS_RETRY_INTERVAL_MS / 1000);
|
|
led_indicator_set(LED_ERROR);
|
|
vTaskDelay(pdMS_TO_TICKS(MDNS_RETRY_INTERVAL_MS));
|
|
led_indicator_set(LED_CONNECTING);
|
|
}
|
|
}
|
|
snprintf(broker_uri, sizeof(broker_uri), "mqtt://%s:%u", broker_host, broker_port);
|
|
|
|
// init MQTT
|
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
|
esp_app_desc_t app_desc;
|
|
const char *fw_version = (esp_ota_get_partition_description(running, &app_desc) == ESP_OK)
|
|
? app_desc.version : "unknown";
|
|
ESP_ERROR_CHECK(mqtt_publisher_init(sensor_id, broker_uri, s_evt, fw_version));
|
|
xEventGroupWaitBits(s_evt, MQTT_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
|
|
|
|
/* Confirm this firmware is healthy so the bootloader won't roll back */
|
|
esp_ota_img_states_t ota_state;
|
|
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK &&
|
|
ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
|
|
esp_ota_mark_app_valid_cancel_rollback();
|
|
ESP_LOGI(TAG, "OTA rollback cancelled - firmware validated");
|
|
}
|
|
|
|
esp_coex_preference_set(ESP_COEX_PREFER_BALANCE);
|
|
|
|
// init BLE scanner
|
|
ble_scanner_init(on_ble_scan_result);
|
|
ble_scanner_start();
|
|
|
|
led_indicator_set(LED_SCANNING);
|
|
ESP_LOGI(TAG, "Anchor running - scanning BLE and publishing RSSI");
|
|
|
|
// main event loop
|
|
const EventBits_t cmd_bits =
|
|
MQTT_CALIBRATE_START | MQTT_CALIBRATE_STOP |
|
|
MQTT_SELECTED_BIT | MQTT_DESELECTED_BIT |
|
|
MQTT_FACTORY_RESET_BIT | MQTT_RECONFIGURE_SETTINGS_BIT |
|
|
MQTT_OTA_BIT;
|
|
|
|
bool calibrating = false;
|
|
bool selected = false;
|
|
|
|
for (;;) {
|
|
EventBits_t bits = xEventGroupWaitBits(
|
|
s_evt, cmd_bits, pdTRUE, pdFALSE, pdMS_TO_TICKS(100));
|
|
|
|
if (bits & MQTT_CALIBRATE_START) {
|
|
ESP_LOGI(TAG, "Calibration started");
|
|
calibrating = true;
|
|
led_indicator_set(LED_CALIBRATING);
|
|
}
|
|
if (bits & MQTT_CALIBRATE_STOP) {
|
|
ESP_LOGI(TAG, "Calibration stopped");
|
|
calibrating = false;
|
|
led_indicator_set(selected ? LED_SELECTED : LED_SCANNING);
|
|
}
|
|
if (bits & MQTT_SELECTED_BIT) {
|
|
ESP_LOGI(TAG, "Anchor selected");
|
|
selected = true;
|
|
if (!calibrating) led_indicator_set(LED_SELECTED);
|
|
}
|
|
if (bits & MQTT_DESELECTED_BIT) {
|
|
ESP_LOGI(TAG, "Anchor deselected");
|
|
selected = false;
|
|
if (!calibrating) led_indicator_set(LED_SCANNING);
|
|
}
|
|
if (bits & MQTT_OTA_BIT) {
|
|
const mqtt_ota_data_t *ota = mqtt_publisher_get_ota_data();
|
|
ESP_LOGI(TAG, "OTA requested: url=%s version=%s", ota->url, ota->version);
|
|
ota_manager_start(ota->url, ota->version);
|
|
}
|
|
if (bits & MQTT_FACTORY_RESET_BIT) {
|
|
// factory reset requested - erase NVS and reboot
|
|
ESP_LOGI(TAG, "Factory reset requested");
|
|
config_store_erase();
|
|
esp_restart();
|
|
}
|
|
if (bits & MQTT_RECONFIGURE_SETTINGS_BIT) {
|
|
ESP_LOGI(TAG, "Reconfigure settings requested");
|
|
const mqtt_reconfigure_data_t *cfg = mqtt_publisher_get_reconfigure_data();
|
|
|
|
if (cfg->ssid[0] != '\0') {
|
|
wifi_config_t wifi_cfg = {0};
|
|
strncpy((char *)wifi_cfg.sta.ssid, cfg->ssid, sizeof(wifi_cfg.sta.ssid) - 1);
|
|
strncpy((char *)wifi_cfg.sta.password, cfg->password, sizeof(wifi_cfg.sta.password) - 1);
|
|
esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg);
|
|
ESP_LOGI(TAG, "Updated WiFi SSID: %s", cfg->ssid);
|
|
}
|
|
|
|
if (cfg->mqtt_host[0] != '\0') {
|
|
uint16_t port = cfg->mqtt_port ? cfg->mqtt_port : DEFAULT_MQTT_PORT;
|
|
config_store_set_mqtt_override(cfg->mqtt_host, port);
|
|
ESP_LOGI(TAG, "Updated MQTT broker: %s:%u", cfg->mqtt_host, port);
|
|
}
|
|
|
|
ESP_LOGI(TAG, "Rebooting to apply new settings");
|
|
esp_restart();
|
|
}
|
|
}
|
|
}
|