feat: main.cpp — FreeRTOS tasks, LED indicators, factory reset
Replaces empty stub with full application: camera+CV task on core 1 at 5 fps, hourly reporter task on core 0, WiFi reconnect loop, 5-second factory reset via BOOT button (GPIO37), LED on GPIO2 for status. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,144 @@
|
|||||||
|
// firmware/src/main.cpp
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
void setup() {}
|
#include <WiFi.h>
|
||||||
void loop() {}
|
#include "config.h"
|
||||||
|
#include "provisioning.h"
|
||||||
|
#include "camera.h"
|
||||||
|
#include "cv.h"
|
||||||
|
#include "ble_scanner.h"
|
||||||
|
#include "reporter.h"
|
||||||
|
|
||||||
|
// LED on GPIO2 (TimerCamera-F built-in LED) — verify against board schematic
|
||||||
|
// Factory reset: hold GPIO37 (BOOT button) for 5 seconds
|
||||||
|
#define LED_PIN 2
|
||||||
|
#define BUTTON_PIN 37
|
||||||
|
#define FACTORY_RESET_HOLD_MS 5000
|
||||||
|
|
||||||
|
#define CAM_FPS 5
|
||||||
|
#define CAM_INTERVAL_MS (1000 / CAM_FPS)
|
||||||
|
#define REPORT_INTERVAL_S 3600
|
||||||
|
|
||||||
|
static DeviceConfig g_cfg;
|
||||||
|
static CVState g_cv;
|
||||||
|
|
||||||
|
// LED: simple on/off — blink patterns can be added later
|
||||||
|
static void led_set(bool on) { digitalWrite(LED_PIN, on ? HIGH : LOW); }
|
||||||
|
|
||||||
|
static void check_factory_reset() {
|
||||||
|
if (digitalRead(BUTTON_PIN) != LOW) return;
|
||||||
|
uint32_t held = millis();
|
||||||
|
while (digitalRead(BUTTON_PIN) == LOW) {
|
||||||
|
if (millis() - held >= FACTORY_RESET_HOLD_MS) {
|
||||||
|
config_clear_wifi();
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
delay(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Camera + CV task — runs on core 1 at 5 fps
|
||||||
|
static void task_camera(void*) {
|
||||||
|
uint8_t frame[CV_PIXELS];
|
||||||
|
while (true) {
|
||||||
|
if (camera_capture_96(frame)) {
|
||||||
|
cv_process(g_cv, frame, g_cfg.line_offset);
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(CAM_INTERVAL_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hourly reporter task — runs on core 0
|
||||||
|
static void task_reporter(void*) {
|
||||||
|
uint32_t last_report_ts = (uint32_t)(time(nullptr));
|
||||||
|
while (true) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10000)); // check every 10s
|
||||||
|
|
||||||
|
uint32_t now = (uint32_t)(time(nullptr));
|
||||||
|
// Skip if NTP not synced or hour not elapsed
|
||||||
|
if (now < 1700000000UL || (now - last_report_ts) < REPORT_INTERVAL_S) continue;
|
||||||
|
|
||||||
|
uint32_t period_start = last_report_ts;
|
||||||
|
uint32_t period_end = now;
|
||||||
|
last_report_ts = now;
|
||||||
|
|
||||||
|
// Pause BLE during upload
|
||||||
|
ble_scanner_pause();
|
||||||
|
led_set(true); // yellow indicator (single LED: on = uploading)
|
||||||
|
|
||||||
|
CameraHourlyRecord cam_rec = {period_start, period_end,
|
||||||
|
g_cv.entries, g_cv.exits};
|
||||||
|
cv_reset_counts(g_cv);
|
||||||
|
|
||||||
|
BLEHourlyRecord ble_rec = ble_scanner_collect(period_start, period_end);
|
||||||
|
|
||||||
|
reporter_submit_camera(g_cfg, cam_rec);
|
||||||
|
reporter_submit_ble(g_cfg, ble_rec);
|
||||||
|
reporter_heartbeat(g_cfg, millis() / 1000, WiFi.RSSI());
|
||||||
|
|
||||||
|
ble_scanner_resume();
|
||||||
|
led_set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
pinMode(LED_PIN, OUTPUT);
|
||||||
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||||
|
led_set(true); // on = booting
|
||||||
|
|
||||||
|
if (!config_load(g_cfg)) {
|
||||||
|
Serial.println("FATAL: device_id/location_id/hmac_secret not provisioned");
|
||||||
|
while (true) { delay(500); led_set(!digitalRead(LED_PIN)); } // fast blink
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to WiFi
|
||||||
|
if (!config_has_wifi()) {
|
||||||
|
provisioning_run();
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFi.begin(g_cfg.wifi_ssid.c_str(), g_cfg.wifi_pass.c_str());
|
||||||
|
uint32_t wifi_start = millis();
|
||||||
|
while (WiFi.status() != WL_CONNECTED && millis() - wifi_start < 15000) {
|
||||||
|
check_factory_reset();
|
||||||
|
delay(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
// Saved creds failed — re-provision
|
||||||
|
provisioning_run();
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
led_set(false); // off = connected
|
||||||
|
|
||||||
|
// NTP sync (UTC)
|
||||||
|
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
|
||||||
|
|
||||||
|
cv_init(g_cv);
|
||||||
|
|
||||||
|
if (!camera_init()) {
|
||||||
|
Serial.println("FATAL: camera init failed");
|
||||||
|
while (true) delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
ble_scanner_start();
|
||||||
|
|
||||||
|
xTaskCreatePinnedToCore(task_camera, "cam", 4096, nullptr, 2, nullptr, 1);
|
||||||
|
xTaskCreatePinnedToCore(task_reporter, "rep", 8192, nullptr, 1, nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
check_factory_reset();
|
||||||
|
|
||||||
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
led_set(true); // on = no WiFi
|
||||||
|
WiFi.reconnect();
|
||||||
|
delay(5000);
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
led_set(false);
|
||||||
|
reporter_flush(g_cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user