fix: ArduinoOTA init, reporter mutex, BLE lock scope, NVS type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,12 @@
|
|||||||
#include <NimBLEAdvertisedDevice.h>
|
#include <NimBLEAdvertisedDevice.h>
|
||||||
#include "mbedtls/md.h"
|
#include "mbedtls/md.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#define RSSI_NEAR -65
|
#define RSSI_NEAR -65
|
||||||
#define RSSI_MID -80
|
#define RSSI_MID -80
|
||||||
|
|
||||||
static portMUX_TYPE s_mux = portMUX_INITIALIZER_UNLOCKED;
|
static std::mutex s_mutex;
|
||||||
|
|
||||||
struct DeviceObs {
|
struct DeviceObs {
|
||||||
int rssi_sum;
|
int rssi_sum;
|
||||||
@@ -47,7 +48,7 @@ class ScanCallback : public NimBLEAdvertisedDeviceCallbacks {
|
|||||||
String hash = sha256_prefix(mac);
|
String hash = sha256_prefix(mac);
|
||||||
int rssi = dev->getRSSI();
|
int rssi = dev->getRSSI();
|
||||||
|
|
||||||
portENTER_CRITICAL(&s_mux);
|
std::lock_guard<std::mutex> lock(s_mutex);
|
||||||
auto it = s_seen.find(hash);
|
auto it = s_seen.find(hash);
|
||||||
if (it == s_seen.end()) {
|
if (it == s_seen.end()) {
|
||||||
s_seen[hash] = {rssi, 1};
|
s_seen[hash] = {rssi, 1};
|
||||||
@@ -57,7 +58,6 @@ class ScanCallback : public NimBLEAdvertisedDeviceCallbacks {
|
|||||||
}
|
}
|
||||||
int concurrent = (int)s_seen.size();
|
int concurrent = (int)s_seen.size();
|
||||||
if (concurrent > s_max_concurrent) s_max_concurrent = concurrent;
|
if (concurrent > s_max_concurrent) s_max_concurrent = concurrent;
|
||||||
portEXIT_CRITICAL(&s_mux);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,26 +79,30 @@ void ble_scanner_pause() { if (s_scan) s_scan->stop(); }
|
|||||||
void ble_scanner_resume() { if (s_scan) s_scan->start(0, nullptr, false); }
|
void ble_scanner_resume() { if (s_scan) s_scan->start(0, nullptr, false); }
|
||||||
|
|
||||||
BLEHourlyRecord ble_scanner_collect(uint32_t period_start, uint32_t period_end) {
|
BLEHourlyRecord ble_scanner_collect(uint32_t period_start, uint32_t period_end) {
|
||||||
portENTER_CRITICAL(&s_mux);
|
// Swap accumulators under lock — minimise time with lock held
|
||||||
|
std::map<String, DeviceObs> local_seen;
|
||||||
BLEHourlyRecord rec;
|
int local_max = 0;
|
||||||
rec.period_start = period_start;
|
{
|
||||||
rec.period_end = period_end;
|
std::lock_guard<std::mutex> lock(s_mutex);
|
||||||
rec.unique_devices = (int)s_seen.size();
|
std::swap(local_seen, s_seen);
|
||||||
rec.max_concurrent = s_max_concurrent;
|
local_max = s_max_concurrent;
|
||||||
rec.near_count = 0; rec.mid_count = 0; rec.far_count = 0;
|
s_max_concurrent = 0;
|
||||||
|
|
||||||
for (auto& kv : s_seen) {
|
|
||||||
float avg = kv.second.rssi_sum / (float)kv.second.count;
|
|
||||||
if (avg > RSSI_NEAR) rec.near_count++;
|
|
||||||
else if (avg > RSSI_MID) rec.mid_count++;
|
|
||||||
else rec.far_count++;
|
|
||||||
rec.device_hashes.push_back(kv.first);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s_seen.clear();
|
// Process outside the lock — heap allocation safe here
|
||||||
s_max_concurrent = 0;
|
BLEHourlyRecord rec;
|
||||||
|
rec.period_start = period_start;
|
||||||
|
rec.period_end = period_end;
|
||||||
|
rec.unique_devices = (int)local_seen.size();
|
||||||
|
rec.max_concurrent = local_max;
|
||||||
|
rec.near_count = 0; rec.mid_count = 0; rec.far_count = 0;
|
||||||
|
|
||||||
portEXIT_CRITICAL(&s_mux);
|
for (auto& kv : local_seen) {
|
||||||
|
float avg_rssi = kv.second.rssi_sum / (float)kv.second.count;
|
||||||
|
if (avg_rssi > RSSI_NEAR) rec.near_count++;
|
||||||
|
else if (avg_rssi > RSSI_MID) rec.mid_count++;
|
||||||
|
else rec.far_count++;
|
||||||
|
rec.device_hashes.push_back(kv.first);
|
||||||
|
}
|
||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// firmware/src/main.cpp
|
// firmware/src/main.cpp
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "provisioning.h"
|
#include "provisioning.h"
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
@@ -139,8 +140,17 @@ void setup() {
|
|||||||
while (true) delay(1000);
|
while (true) delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reporter_init();
|
||||||
|
|
||||||
ble_scanner_start();
|
ble_scanner_start();
|
||||||
|
|
||||||
|
// OTA update support
|
||||||
|
ArduinoOTA.setHostname(g_cfg.device_id.c_str());
|
||||||
|
ArduinoOTA.onStart([]() { ble_scanner_pause(); });
|
||||||
|
ArduinoOTA.onEnd([]() { ble_scanner_resume(); ESP.restart(); });
|
||||||
|
ArduinoOTA.onError([](ota_error_t e) { ble_scanner_resume(); });
|
||||||
|
ArduinoOTA.begin();
|
||||||
|
|
||||||
s_cv_mutex = xSemaphoreCreateMutex();
|
s_cv_mutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
xTaskCreatePinnedToCore(task_camera, "cam", 4096, nullptr, 2, nullptr, 1);
|
xTaskCreatePinnedToCore(task_camera, "cam", 4096, nullptr, 2, nullptr, 1);
|
||||||
@@ -148,6 +158,7 @@ void setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
ArduinoOTA.handle();
|
||||||
check_factory_reset();
|
check_factory_reset();
|
||||||
|
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
|||||||
@@ -6,9 +6,15 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
|
||||||
static std::vector<CameraHourlyRecord> s_cam_buf;
|
static std::vector<CameraHourlyRecord> s_cam_buf;
|
||||||
static std::vector<BLEHourlyRecord> s_ble_buf;
|
static std::vector<BLEHourlyRecord> s_ble_buf;
|
||||||
|
static SemaphoreHandle_t s_buf_mutex = nullptr;
|
||||||
|
|
||||||
|
void reporter_init() {
|
||||||
|
s_buf_mutex = xSemaphoreCreateMutex();
|
||||||
|
}
|
||||||
|
|
||||||
// Returns current Unix timestamp (NTP-synced via configTime in main.cpp).
|
// Returns current Unix timestamp (NTP-synced via configTime in main.cpp).
|
||||||
static uint32_t now_ts() {
|
static uint32_t now_ts() {
|
||||||
@@ -86,12 +92,19 @@ static void buf_add_ble(const BLEHourlyRecord& r) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void reporter_submit_camera(const DeviceConfig& cfg, const CameraHourlyRecord& rec) {
|
void reporter_submit_camera(const DeviceConfig& cfg, const CameraHourlyRecord& rec) {
|
||||||
if (WiFi.status() != WL_CONNECTED) { buf_add_cam(rec); return; }
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
xSemaphoreTake(s_buf_mutex, portMAX_DELAY);
|
||||||
|
buf_add_cam(rec);
|
||||||
|
xSemaphoreGive(s_buf_mutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreTake(s_buf_mutex, portMAX_DELAY);
|
||||||
std::vector<CameraHourlyRecord> batch;
|
std::vector<CameraHourlyRecord> batch;
|
||||||
batch.insert(batch.end(), s_cam_buf.begin(), s_cam_buf.end());
|
batch.insert(batch.end(), s_cam_buf.begin(), s_cam_buf.end());
|
||||||
batch.push_back(rec);
|
batch.push_back(rec);
|
||||||
s_cam_buf.clear();
|
s_cam_buf.clear();
|
||||||
|
xSemaphoreGive(s_buf_mutex);
|
||||||
|
|
||||||
// Cap to MAX_BUFFER: drop oldest to make room for newest
|
// Cap to MAX_BUFFER: drop oldest to make room for newest
|
||||||
if ((int)batch.size() > REPORTER_MAX_BUFFER) {
|
if ((int)batch.size() > REPORTER_MAX_BUFFER) {
|
||||||
@@ -101,17 +114,26 @@ void reporter_submit_camera(const DeviceConfig& cfg, const CameraHourlyRecord& r
|
|||||||
|
|
||||||
String body = build_camera_batch(cfg, batch);
|
String body = build_camera_batch(cfg, batch);
|
||||||
if (!post_json(cfg, "/api/v1/camera/events/batch", body)) {
|
if (!post_json(cfg, "/api/v1/camera/events/batch", body)) {
|
||||||
|
xSemaphoreTake(s_buf_mutex, portMAX_DELAY);
|
||||||
s_cam_buf = batch; // re-buffer the whole capped batch
|
s_cam_buf = batch; // re-buffer the whole capped batch
|
||||||
|
xSemaphoreGive(s_buf_mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void reporter_submit_ble(const DeviceConfig& cfg, const BLEHourlyRecord& rec) {
|
void reporter_submit_ble(const DeviceConfig& cfg, const BLEHourlyRecord& rec) {
|
||||||
if (WiFi.status() != WL_CONNECTED) { buf_add_ble(rec); return; }
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
xSemaphoreTake(s_buf_mutex, portMAX_DELAY);
|
||||||
|
buf_add_ble(rec);
|
||||||
|
xSemaphoreGive(s_buf_mutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreTake(s_buf_mutex, portMAX_DELAY);
|
||||||
std::vector<BLEHourlyRecord> batch;
|
std::vector<BLEHourlyRecord> batch;
|
||||||
batch.insert(batch.end(), s_ble_buf.begin(), s_ble_buf.end());
|
batch.insert(batch.end(), s_ble_buf.begin(), s_ble_buf.end());
|
||||||
batch.push_back(rec);
|
batch.push_back(rec);
|
||||||
s_ble_buf.clear();
|
s_ble_buf.clear();
|
||||||
|
xSemaphoreGive(s_buf_mutex);
|
||||||
|
|
||||||
// Cap to MAX_BUFFER: drop oldest to make room for newest
|
// Cap to MAX_BUFFER: drop oldest to make room for newest
|
||||||
if ((int)batch.size() > REPORTER_MAX_BUFFER) {
|
if ((int)batch.size() > REPORTER_MAX_BUFFER) {
|
||||||
@@ -121,7 +143,9 @@ void reporter_submit_ble(const DeviceConfig& cfg, const BLEHourlyRecord& rec) {
|
|||||||
|
|
||||||
String body = build_ble_batch(cfg, batch);
|
String body = build_ble_batch(cfg, batch);
|
||||||
if (!post_json(cfg, "/api/v1/events/batch", body)) {
|
if (!post_json(cfg, "/api/v1/events/batch", body)) {
|
||||||
|
xSemaphoreTake(s_buf_mutex, portMAX_DELAY);
|
||||||
s_ble_buf = batch; // re-buffer the whole capped batch
|
s_ble_buf = batch; // re-buffer the whole capped batch
|
||||||
|
xSemaphoreGive(s_buf_mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,12 +161,25 @@ void reporter_heartbeat(const DeviceConfig& cfg, uint32_t uptime_s, int wifi_rss
|
|||||||
}
|
}
|
||||||
|
|
||||||
void reporter_flush(const DeviceConfig& cfg) {
|
void reporter_flush(const DeviceConfig& cfg) {
|
||||||
if (!s_cam_buf.empty()) {
|
xSemaphoreTake(s_buf_mutex, portMAX_DELAY);
|
||||||
String body = build_camera_batch(cfg, s_cam_buf);
|
std::vector<CameraHourlyRecord> cam_snap = s_cam_buf;
|
||||||
if (post_json(cfg, "/api/v1/camera/events/batch", body)) s_cam_buf.clear();
|
std::vector<BLEHourlyRecord> ble_snap = s_ble_buf;
|
||||||
|
xSemaphoreGive(s_buf_mutex);
|
||||||
|
|
||||||
|
if (!cam_snap.empty()) {
|
||||||
|
String body = build_camera_batch(cfg, cam_snap);
|
||||||
|
if (post_json(cfg, "/api/v1/camera/events/batch", body)) {
|
||||||
|
xSemaphoreTake(s_buf_mutex, portMAX_DELAY);
|
||||||
|
s_cam_buf.clear();
|
||||||
|
xSemaphoreGive(s_buf_mutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!s_ble_buf.empty()) {
|
if (!ble_snap.empty()) {
|
||||||
String body = build_ble_batch(cfg, s_ble_buf);
|
String body = build_ble_batch(cfg, ble_snap);
|
||||||
if (post_json(cfg, "/api/v1/events/batch", body)) s_ble_buf.clear();
|
if (post_json(cfg, "/api/v1/events/batch", body)) {
|
||||||
|
xSemaphoreTake(s_buf_mutex, portMAX_DELAY);
|
||||||
|
s_ble_buf.clear();
|
||||||
|
xSemaphoreGive(s_buf_mutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ struct CameraHourlyRecord {
|
|||||||
static const int REPORTER_MAX_BUFFER = 24;
|
static const int REPORTER_MAX_BUFFER = 24;
|
||||||
static const char* REPORTER_API_HOST = "https://logs.research.bike";
|
static const char* REPORTER_API_HOST = "https://logs.research.bike";
|
||||||
|
|
||||||
|
void reporter_init();
|
||||||
void reporter_submit_camera(const DeviceConfig& cfg, const CameraHourlyRecord& rec);
|
void reporter_submit_camera(const DeviceConfig& cfg, const CameraHourlyRecord& rec);
|
||||||
void reporter_submit_ble(const DeviceConfig& cfg, const BLEHourlyRecord& rec);
|
void reporter_submit_ble(const DeviceConfig& cfg, const BLEHourlyRecord& rec);
|
||||||
void reporter_heartbeat(const DeviceConfig& cfg, uint32_t uptime_s, int wifi_rssi);
|
void reporter_heartbeat(const DeviceConfig& cfg, uint32_t uptime_s, int wifi_rssi);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ def build_nvs_csv(device_id, location_id, hmac_secret,
|
|||||||
f"device_id,data,string,{device_id}",
|
f"device_id,data,string,{device_id}",
|
||||||
f"location_id,data,string,{location_id}",
|
f"location_id,data,string,{location_id}",
|
||||||
f"hmac_secret,data,string,{hmac_secret}",
|
f"hmac_secret,data,string,{hmac_secret}",
|
||||||
f"line_offset,data,u8,{line_offset}",
|
f"line_offset,data,u32,{line_offset}",
|
||||||
]
|
]
|
||||||
if wifi_ssid is not None:
|
if wifi_ssid is not None:
|
||||||
rows.append(f"wifi_ssid,data,string,{wifi_ssid}")
|
rows.append(f"wifi_ssid,data,string,{wifi_ssid}")
|
||||||
|
|||||||
Reference in New Issue
Block a user