diff --git a/firmware/src/ble_scanner.cpp b/firmware/src/ble_scanner.cpp new file mode 100644 index 0000000..0b33e89 --- /dev/null +++ b/firmware/src/ble_scanner.cpp @@ -0,0 +1,98 @@ +// firmware/src/ble_scanner.cpp +#include "ble_scanner.h" +#include +#include +#include +#include "mbedtls/md.h" +#include + +#define RSSI_NEAR -65 +#define RSSI_MID -80 + +static portMUX_TYPE s_mux = portMUX_INITIALIZER_UNLOCKED; + +struct DeviceObs { + int rssi_sum; + int count; +}; + +static std::map s_seen; +static int s_max_concurrent = 0; + +static String sha256_prefix(const String& input) { + uint8_t hash[32]; + mbedtls_md_context_t ctx; + const mbedtls_md_info_t* info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + mbedtls_md_init(&ctx); + mbedtls_md_setup(&ctx, info, 0); + mbedtls_md_starts(&ctx); + mbedtls_md_update(&ctx, (const uint8_t*)input.c_str(), input.length()); + mbedtls_md_finish(&ctx, hash); + mbedtls_md_free(&ctx); + String hex = ""; + char buf[3]; + for (int i = 0; i < 16; i++) { snprintf(buf, 3, "%02x", hash[i]); hex += buf; } + return hex; +} + +class ScanCallback : public NimBLEAdvertisedDeviceCallbacks { + void onResult(NimBLEAdvertisedDevice* dev) override { + String mac = String(dev->getAddress().toString().c_str()); + String hash = sha256_prefix(mac); + int rssi = dev->getRSSI(); + + portENTER_CRITICAL(&s_mux); + auto it = s_seen.find(hash); + if (it == s_seen.end()) { + s_seen[hash] = {rssi, 1}; + } else { + it->second.rssi_sum += rssi; + it->second.count++; + } + int concurrent = (int)s_seen.size(); + if (concurrent > s_max_concurrent) s_max_concurrent = concurrent; + portEXIT_CRITICAL(&s_mux); + } +}; + +static ScanCallback s_callback; +static NimBLEScan* s_scan = nullptr; + +void ble_scanner_start() { + NimBLEDevice::init(""); + s_scan = NimBLEDevice::getScan(); + s_scan->setAdvertisedDeviceCallbacks(&s_callback, true); // true = allow duplicates + s_scan->setActiveScan(false); // passive + s_scan->setInterval(100); + s_scan->setWindow(99); + s_scan->setMaxResults(0); // don't store results — callback-only + s_scan->start(0, nullptr, false); // 0 = continuous +} + +void ble_scanner_pause() { if (s_scan) s_scan->stop(); } +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) { + portENTER_CRITICAL(&s_mux); + + BLEHourlyRecord rec; + rec.period_start = period_start; + rec.period_end = period_end; + rec.unique_devices = (int)s_seen.size(); + rec.max_concurrent = s_max_concurrent; + rec.near_count = 0; rec.mid_count = 0; rec.far_count = 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(); + s_max_concurrent = 0; + + portEXIT_CRITICAL(&s_mux); + return rec; +} diff --git a/firmware/src/ble_scanner.h b/firmware/src/ble_scanner.h new file mode 100644 index 0000000..6ad35ae --- /dev/null +++ b/firmware/src/ble_scanner.h @@ -0,0 +1,25 @@ +// firmware/src/ble_scanner.h +#pragma once +#include +#include + +struct BLEHourlyRecord { + uint32_t period_start; + uint32_t period_end; + int unique_devices; + int max_concurrent; + int near_count; // RSSI > -65 dBm + int mid_count; // RSSI -65 to -80 dBm + int far_count; // RSSI < -80 dBm + std::vector device_hashes; // SHA256(MAC) first 16 hex chars +}; + +// Start continuous passive BLE scan (call once at boot). +void ble_scanner_start(); + +// Pause scan for ~3s during HTTP upload. +void ble_scanner_pause(); +void ble_scanner_resume(); + +// Collect current hour's record and reset accumulators. Thread-safe. +BLEHourlyRecord ble_scanner_collect(uint32_t period_start, uint32_t period_end);