diff --git a/.gitignore b/.gitignore
index 381fa0e..a60ac9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,10 @@ vesper/CLAUDE.md
vesper/flutter/
vesper/docs_manual/
Doxyfile
-vesper/.claude/
\ No newline at end of file
+vesper/.claude/
+
+# PlatformIO β build output and downloaded libraries (never commit these)
+vesper/.pio/
+
+# Claude Code memory/session files
+.claude/
\ No newline at end of file
diff --git a/vesper/documentation/HEARTBEAT_FEATURE.md b/vesper/documentation/HEARTBEAT_FEATURE.md
new file mode 100644
index 0000000..f9abfe6
--- /dev/null
+++ b/vesper/documentation/HEARTBEAT_FEATURE.md
@@ -0,0 +1,139 @@
+# π MQTT Heartbeat Feature
+
+## Overview
+Implemented a **retained MQTT heartbeat** system that sends periodic status updates every 30 seconds when the controller is connected to MQTT.
+
+## What It Does
+
+### Heartbeat Message
+Every 30 seconds, the controller publishes a **retained** message to:
+```
+vesper/{deviceID}/status/heartbeat
+```
+
+### Message Format
+```json
+{
+ "status": "INFO",
+ "type": "heartbeat",
+ "payload": {
+ "device_id": "VESPER-ABC123",
+ "firmware_version": "130",
+ "timestamp": "Uptime: 5h 23m 45s",
+ "ip_address": "192.168.1.100",
+ "gateway": "192.168.1.1",
+ "uptime_ms": 19425000
+ }
+}
+```
+
+### Key Features
+β
**Retained Message** - Only the LAST heartbeat stays on the broker
+β
**Auto-Start** - Begins when MQTT connects
+β
**Auto-Stop** - Stops when MQTT disconnects
+β
**30-Second Interval** - Periodic updates
+β
**First Beat Immediate** - Sends first heartbeat right after connecting
+β
**QoS 1** - Reliable delivery
+
+## Why This is Awesome
+
+### For Your Flutter App
+1. **Immediate Status** - Any new connection gets the last known status instantly
+2. **Stale Detection** - Can detect if controller went offline (timestamp too old)
+3. **Device Discovery** - Apps can subscribe to `vesper/+/status/heartbeat` to find all controllers
+4. **No Polling** - Just subscribe once and get automatic updates
+
+### Example App Logic
+```dart
+// Subscribe to heartbeat
+mqtt.subscribe('vesper/DEVICE-123/status/heartbeat');
+
+// On message received
+if (heartbeat.uptime_ms > lastSeen.uptime_ms + 120000) {
+ // No heartbeat for 2+ minutes = controller offline
+ showOfflineWarning();
+}
+```
+
+## Implementation Details
+
+### Files Modified
+1. **MQTTAsyncClient.hpp** - Added heartbeat timer and methods
+2. **MQTTAsyncClient.cpp** - Implemented heartbeat logic
+3. **Networking.hpp** - Added `getGateway()` method
+4. **Networking.cpp** - Implemented `getGateway()` method
+
+### New Methods Added
+```cpp
+void startHeartbeat(); // Start 30s periodic timer
+void stopHeartbeat(); // Stop timer
+void publishHeartbeat(); // Build and publish message
+void heartbeatTimerCallback(); // Timer callback handler
+```
+
+### Timer Configuration
+- **Type**: FreeRTOS Software Timer
+- **Mode**: Auto-reload (repeating)
+- **Period**: 30,000 ms (30 seconds)
+- **Core**: Runs on Core 0 (MQTT task core)
+
+## Testing
+
+### How to Test
+1. Flash the firmware
+2. Subscribe to the heartbeat topic:
+ ```bash
+ mosquitto_sub -h YOUR_BROKER -t "vesper/+/status/heartbeat" -v
+ ```
+3. You should see heartbeats every 30 seconds
+4. Disconnect the controller - the last message stays retained
+5. Reconnect - you'll immediately see the last retained message, then new ones every 30s
+
+### Expected Serial Output
+```
+π Starting MQTT heartbeat (every 30 seconds)
+π Published heartbeat (retained) - IP: 192.168.1.100, Uptime: 45000ms
+π Published heartbeat (retained) - IP: 192.168.1.100, Uptime: 75000ms
+β€οΈ Stopped MQTT heartbeat (when MQTT disconnects)
+```
+
+## Future Enhancements (Optional)
+
+### Possible Additions:
+- Add actual RTC timestamp (instead of just uptime)
+- Add WiFi signal strength (RSSI) for WiFi connections
+- Add free heap memory
+- Add current playback status
+- Add bell configuration version/hash
+
+### Implementation Example:
+```cpp
+// In publishHeartbeat()
+payload["rssi"] = WiFi.RSSI(); // WiFi signal strength
+payload["free_heap"] = ESP.getFreeHeap();
+payload["playback_active"] = player.isPlaying;
+```
+
+## Configuration
+
+### Current Settings (can be changed in MQTTAsyncClient.hpp):
+```cpp
+static const unsigned long HEARTBEAT_INTERVAL = 30000; // 30 seconds
+```
+
+To change interval to 60 seconds:
+```cpp
+static const unsigned long HEARTBEAT_INTERVAL = 60000; // 60 seconds
+```
+
+## Notes
+- Message is published with **QoS 1** (at least once delivery)
+- Message is **retained** (broker keeps last message)
+- Timer starts automatically when MQTT connects
+- Timer stops automatically when MQTT disconnects
+- First heartbeat is sent immediately upon connection (no 30s wait)
+
+---
+**Feature Implemented**: January 2025
+**Version**: Firmware v130+
+**Status**: β
Production Ready
diff --git a/vesper/documentation/project-vesper-plan.md b/vesper/documentation/project-vesper-plan.md
new file mode 100644
index 0000000..18ecfa2
--- /dev/null
+++ b/vesper/documentation/project-vesper-plan.md
@@ -0,0 +1,463 @@
+# Project Vesper β Manufacturing Automation Master Plan
+
+> **How to use this document:** Work through each Phase in order. Each Phase has self-contained tasks you can hand directly to Claude Code. Phases 1β3 are foundational; don't skip ahead. Phases 4β6 build on top of them.
+
+---
+
+## Current Stack (Reference)
+
+| Layer | Technology |
+|---|---|
+| Microcontroller | ESP32 (ESP8266 on older models, STM32 possible future) |
+| MCU Firmware | Arduino / C++ (moving to PlatformIO) |
+| Tablet App | Flutter / FlutterFlow (Android) |
+| Phone App | Flutter / FlutterFlow (Android + iOS) |
+| Admin Console Backend | Python 3.11+ / FastAPI |
+| Admin Console Frontend | React + Tailwind CSS |
+| Primary Database | Firebase Firestore (via Admin SDK) |
+| Secondary Database | Local SQLite (MQTT logs, etc.) |
+| File Storage | Firebase Storage |
+| MQTT Broker | Mosquitto on VPS |
+| Web Server | NGINX on VPS (OTA files, reverse proxy) |
+| Deployment | Gitea β git pull on VPS, Docker Compose locally |
+
+---
+
+## Serial Number Format (Locked In)
+
+```
+PV-YYMMM-BBTTR-XXXXX
+
+PV = Project Vesper prefix
+YY = 2-digit year (e.g. 26)
+MMM = 3-char date block = 2-digit month letter + 2-digit day (e.g. A18 = Jan 18)
+BB = Board Type code (e.g. BC = BellCore, BP = BellPRO)
+TT = Board Type revision (e.g. 02)
+R = Literal "R"
+XXXXX = 5-char random suffix (A-Z, 0-9, excluding 0/O/1/I for label clarity)
+
+Example: PV-26A18-BC02R-X7KQA
+```
+
+**Month Letter Codes:**
+A=Jan, B=Feb, C=Mar, D=Apr, E=May, F=Jun, G=Jul, H=Aug, I=Sep, J=Oct, K=Nov, L=Dec
+
+---
+
+## Phase 1 β PlatformIO Migration (Firmware Side)
+
+> **Goal:** Replace Arduino IDE with PlatformIO. All future firmware work happens here. This unlocks scripted builds and the ability to produce `.bin` files on demand.
+>
+> **Tell Claude Code:** *"Help me migrate my existing Arduino IDE ESP32 project to PlatformIO with multiple board environments."*
+
+### Tasks
+
+- [ ] Install PlatformIO extension in VS Code
+- [ ] Create `platformio.ini` with one `[env]` block per hardware variant. Example:
+
+```ini
+[env:bellcore-v2]
+platform = espressif32
+board = esp32dev
+board_build.partitions = partitions/custom_4mb.csv
+board_build.flash_mode = dio
+board_upload.flash_size = 4MB
+build_flags =
+ -DBOARD_TYPE="BC"
+ -DBOARD_VERSION="02"
+ -DPSRAM_ENABLED=1
+
+[env:bellcore-v1]
+platform = espressif32
+board = esp32dev
+board_build.partitions = partitions/custom_2mb.csv
+board_build.flash_mode = dout
+board_upload.flash_size = 2MB
+build_flags =
+ -DBOARD_TYPE="BC"
+ -DBOARD_VERSION="01"
+ -DPSRAM_ENABLED=0
+```
+
+- [ ] Move all `#include` library dependencies into `lib_deps` in `platformio.ini` (no more manual library manager)
+- [ ] Verify `pio run -e bellcore-v2` compiles clean
+- [ ] Confirm `.pio/build/bellcore-v2/firmware.bin` is produced
+- [ ] Create a `/firmware` directory structure on the server (NGINX already serves this):
+
+```
+/srv/ota/
+ bellcore-v1/
+ latest.bin
+ v1.0.0.bin
+ v1.0.1.bin
+ bellcore-v2/
+ latest.bin
+ ...
+```
+
+- [ ] Update your OTA logic in firmware to pull from `/ota/{board_type}/latest.bin`
+- [ ] Add a `scripts/build_and_upload.sh` that compiles + copies the new `.bin` to the right NGINX folder (run this after every release)
+
+### NVS Partition Generator Setup
+
+- [ ] Install `esptool` and `esp-idf` NVS tool: `pip install esptool`
+- [ ] Grab `nvs_partition_gen.py` from ESP-IDF tools (or install via `idf-component-manager`)
+- [ ] Test generating a `.bin` from a CSV manually:
+
+```csv
+key,type,encoding,value
+serial_number,data,string,PV-26A18-BC02R-X7KQA
+hw_type,data,string,BC
+hw_version,data,string,02
+```
+
+```bash
+python nvs_partition_gen.py generate nvs_data.csv nvs_data.bin 0x6000
+```
+
+- [ ] Confirm the ESP32 reads NVS values correctly on boot with this pre-flashed partition
+- [ ] Note the NVS partition address for your board (check your partition table CSV β typically `0x9000`)
+
+---
+
+## Phase 2 β MQTT Dynamic Auth (Backend Side)
+
+> **Goal:** Replace `mosquitto_passwd` manual SSH with automatic credential management. New devices are live on MQTT the moment they exist in your database. Per-device topic isolation enforced automatically.
+>
+> **Tell Claude Code:** *"Help me set up mosquitto-go-auth on my VPS with a FastAPI backend for dynamic MQTT authentication and ACL enforcement."*
+
+### Tasks
+
+#### 2a. Install mosquitto-go-auth on VPS
+
+- [ ] Install Go on VPS (required to build the plugin)
+- [ ] Clone and build `mosquitto-go-auth`:
+ ```bash
+ git clone https://github.com/iegomez/mosquitto-go-auth
+ cd mosquitto-go-auth && make
+ ```
+- [ ] Update `mosquitto.conf` to load the plugin:
+ ```
+ auth_plugin /path/to/go-auth.so
+ auth_opt_backends http
+ auth_opt_http_host localhost
+ auth_opt_http_port 8000
+ auth_opt_http_getuser_uri /mqtt/auth/user
+ auth_opt_http_aclcheck_uri /mqtt/auth/acl
+ auth_opt_cache true
+ auth_opt_cache_host localhost
+ auth_opt_cache_reset true
+ auth_opt_auth_cache_seconds 300
+ auth_opt_acl_cache_seconds 300
+ ```
+
+#### 2b. Add MQTT Auth Endpoints to FastAPI
+
+- [ ] Create `/mqtt/auth/user` endpoint β Mosquitto calls this on CONNECT:
+ - Receives: `username` (= device SN), `password`
+ - Checks Firestore/SQLite for device record + hashed password
+ - Returns: `200` (allow) or `403` (deny)
+
+- [ ] Create `/mqtt/auth/acl` endpoint β Mosquitto calls this on SUBSCRIBE/PUBLISH:
+ - Receives: `username`, `topic`, `acc` (1=sub, 2=pub)
+ - Rule: username must match the SN segment in the topic
+ - Topic pattern: `/vesper/{SN}/data` or `/vesper/{SN}/control`
+ - Extract `{SN}` from topic, compare to `username`
+ - Returns: `200` or `403`
+
+- [ ] **For user phone app clients:** Add a separate user auth flow
+ - Users authenticate with their Firebase UID as MQTT username
+ - ACL check: look up which devices this UID owns in Firestore, permit only those SNs in topics
+
+#### 2c. MQTT Password Strategy
+
+Use **HMAC-derived passwords** so you never have to store or manually set them:
+
+```python
+import hmac, hashlib
+
+MQTT_SECRET = os.getenv("MQTT_SECRET") # Keep in .env, never commit
+
+def derive_mqtt_password(serial_number: str) -> str:
+ return hmac.new(
+ MQTT_SECRET.encode(),
+ serial_number.encode(),
+ hashlib.sha256
+ ).hexdigest()[:32]
+```
+
+- Device SN is known β password is deterministic β firmware can compute it at boot
+- No password storage needed in DB (just re-derive on auth check)
+- Changing `MQTT_SECRET` rotates all passwords at once if ever needed
+
+- [ ] Add `MQTT_SECRET` to your `.env` and Docker Compose secrets
+- [ ] Update firmware to derive its own MQTT password using the same HMAC logic (port to C++)
+- [ ] Remove all existing `mosquitto_passwd` file entries and disable static auth
+
+#### 2d. Test
+
+- [ ] New device connects with correct SN + derived password β allowed
+- [ ] Device tries to sub/pub on another device's topic β denied (403)
+- [ ] Wrong password β denied
+- [ ] Confirm cache is working (check logs, only 1-2 auth calls per session)
+
+---
+
+## Phase 3 β Serial Number & Batch Management in Admin Console
+
+> **Goal:** SN generation, DB registration, and MQTT credential provisioning all happen in one flow in the React Console. The Flutter admin app is retired.
+>
+> **Tell Claude Code:** *"Add a Manufacturing / Batch Management section to our React+FastAPI admin console with the following features..."*
+
+### Tasks
+
+#### 3a. Backend β New API Routes in FastAPI
+
+- [ ] `POST /manufacturing/batch` β Create a new batch:
+ - Input: `board_type`, `board_version`, `quantity`, `subscription_plan`, `available_outputs`
+ - Generate N serial numbers using the `PV-YYMMM-BBTTR-XXXXX` format
+ - Check Firestore for collisions, regenerate if collision found
+ - Write N device documents to Firestore collection `devices`:
+ ```json
+ {
+ "serial_number": "PV-26A18-BC02R-X7KQA",
+ "hw_type": "BC",
+ "hw_version": "02",
+ "status": "manufactured",
+ "subscription_plan": "standard",
+ "available_outputs": 8,
+ "created_at": "...",
+ "owner": null,
+ "users_list": []
+ }
+ ```
+ - Returns: list of created SNs
+
+- [ ] `GET /manufacturing/batch/{batch_id}` β List devices in a batch with status
+- [ ] `GET /manufacturing/devices` β List all devices with filters (status, hw_type, date range)
+- [ ] `POST /manufacturing/devices/{sn}/assign` β Pre-assign device to a customer email
+- [ ] `GET /manufacturing/firmware/{hw_type}/{hw_version}` β Return download URL for the correct `.bin`
+
+#### 3b. SN Generator Utility (Python)
+
+```python
+# utils/serial_number.py
+import random, string
+from datetime import datetime
+
+MONTH_CODES = "ABCDEFGHIJKL"
+SAFE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" # No 0,O,1,I
+
+def generate_serial(board_type: str, board_version: str) -> str:
+ now = datetime.utcnow()
+ year = now.strftime("%y")
+ month = MONTH_CODES[now.month - 1]
+ day = now.strftime("%d")
+ random_suffix = "".join(random.choices(SAFE_CHARS, k=5))
+ return f"PV-{year}{month}{day}-{board_type}{board_version}R-{random_suffix}"
+```
+
+#### 3c. NVS Binary Generation in FastAPI
+
+- [ ] Copy `nvs_partition_gen.py` into your FastAPI project
+- [ ] Add endpoint `GET /manufacturing/devices/{sn}/nvs.bin`:
+ - Generates a temp CSV for this SN
+ - Runs `nvs_partition_gen.py` to produce the `.bin`
+ - Returns the binary file as a download
+- [ ] Add endpoint `GET /manufacturing/devices/{sn}/firmware.bin`:
+ - Looks up device's `hw_type` and `hw_version` from Firestore
+ - Returns the correct firmware `.bin` from the NGINX folder (or redirects to NGINX URL)
+
+#### 3d. Label Sheet Generation
+
+- [ ] Add `POST /manufacturing/batch/{batch_id}/labels` endpoint
+ - Returns a PDF with one label per device
+ - Each label contains: SN (human readable), QR code of SN, HW Type, HW Version
+ - Use `reportlab` or `fpdf2` Python library for PDF generation
+ - QR code: use `qrcode` Python library
+
+#### 3e. Frontend β Manufacturing Section in React Console
+
+- [ ] New route: `/manufacturing`
+- [ ] **Batch Creator:** Form with board type selector, quantity, subscription plan β calls `POST /manufacturing/batch` β shows created SNs + download label PDF button
+- [ ] **Device List:** Filterable table of all devices with status badges (manufactured / sold / claimed / active)
+- [ ] **Device Detail Page:** Shows all fields, allows status update, shows assignment history
+
+---
+
+## Phase 4 β Browser-Based Flashing (The Provisioning Wizard)
+
+> **Goal:** A single browser tab handles the entire provisioning flow. Plug in ESP32, click through wizard, done. No Arduino IDE, no esptool CLI, no SSH.
+>
+> **Tell Claude Code:** *"Add a Device Provisioning Wizard to our React admin console using esptool-js and the Web Serial API."*
+>
+> **Browser requirement:** Chrome or Edge only (Web Serial API). Firefox not supported. This is fine for an internal manufacturing tool.
+
+### Tasks
+
+#### 4a. Add esptool-js to React Console
+
+- [ ] `npm install esptool-js` (or use the CDN build)
+- [ ] Confirm Chrome is used on the manufacturing bench laptop
+
+#### 4b. Provisioning Wizard UI (React Component)
+
+Build a step-by-step wizard. Steps:
+
+**Step 1 β Select or Create Device**
+- Search existing unprovisioned device by SN, OR
+- Quick-create single device (calls `POST /manufacturing/batch` with qty=1)
+- Displays SN, HW Type, HW Version
+
+**Step 2 β Flash Device**
+- "Connect Device" button β triggers Web Serial port picker
+- Fetches `nvs.bin` and `firmware.bin` from your FastAPI backend for this SN
+- Shows two progress bars: NVS partition flash + Firmware flash
+- Flash addresses (example for standard ESP32):
+ - NVS: `0x9000` (verify against your partition table)
+ - Firmware: `0x10000`
+- On completion: updates device status to `flashed` in Firestore via API call
+
+**Step 3 β Verify**
+- Prompt: "Power cycle device and wait for it to connect"
+- Poll Firestore (or MQTT) for first heartbeat/connection from this SN
+- Show green checkmark when device phone home
+- Updates status to `provisioned`
+
+**Step 4 β Done**
+- Show summary
+- Option: "Provision next device" (loops back to Step 1 with same batch settings)
+- Option: "Print label" (downloads single-device PDF label)
+
+#### 4c. esptool-js Flash Logic (Skeleton)
+
+```javascript
+import { ESPLoader, Transport } from "esptool-js";
+
+async function flashDevice(serialPort, nvsArrayBuffer, firmwareArrayBuffer) {
+ const transport = new Transport(serialPort);
+ const loader = new ESPLoader({ transport, baudrate: 460800 });
+
+ await loader.main_fn();
+ await loader.flash_id();
+
+ await loader.write_flash({
+ fileArray: [
+ { data: nvsArrayBuffer, address: 0x9000 },
+ { data: firmwareArrayBuffer, address: 0x10000 },
+ ],
+ flashSize: "keep",
+ flashMode: "keep",
+ flashFreq: "keep",
+ eraseAll: false,
+ compress: true,
+ });
+
+ await transport.disconnect();
+}
+```
+
+#### 4d. NGINX CORS Headers
+
+Add to your NGINX config so the browser can fetch `.bin` files:
+
+```nginx
+location /ota/ {
+ add_header Access-Control-Allow-Origin "https://your-console-domain.com";
+ add_header Access-Control-Allow-Methods "GET";
+}
+```
+
+---
+
+## Phase 5 β Email Notifications
+
+> **Goal:** Admin Console can send transactional emails (device assignment invites, alerts, etc.)
+>
+> **Tell Claude Code:** *"Add email sending capability to our FastAPI backend using Resend (or SMTP)."*
+
+### Tasks
+
+- [ ] Sign up for [Resend](https://resend.com) (free tier: 3000 emails/month, 100/day)
+- [ ] Add `RESEND_API_KEY` to `.env`
+- [ ] Install: `pip install resend`
+- [ ] Create `utils/email.py`:
+
+```python
+import resend
+import os
+
+resend.api_key = os.getenv("RESEND_API_KEY")
+
+def send_device_invite(customer_email: str, serial_number: str, customer_name: str = None):
+ resend.Emails.send({
+ "from": "noreply@yourcompany.com",
+ "to": customer_email,
+ "subject": "Your Vesper device is ready",
+ "html": f"""
+
Your device has been registered
+ Serial Number: {serial_number}
+ Open the Vesper app and enter this serial number to get started.
+ """
+ })
+```
+
+- [ ] Hook into `POST /manufacturing/devices/{sn}/assign` to send invite automatically
+- [ ] Add basic email templates for: device assignment, welcome, error alerts
+
+---
+
+## Phase 6 β Polish & Retire Legacy Tools
+
+> **Goal:** Clean up. Everything lives in the Console. Nothing is done manually.
+
+### Tasks
+
+- [ ] **Retire Flutter admin app** β confirm every function it had is now in the React Console
+- [ ] **Remove static mosquitto password file** β all auth is dynamic now
+- [ ] **Add device status dashboard** to Console home: counts by status, recent provisioning activity
+- [ ] **Add audit log** β every manufacturing action (batch created, device flashed, device assigned) logged to SQLite with timestamp and admin user
+- [ ] **Document your `platformio.ini` environments** β add a `FIRMWARE_VARIANTS.md` to the firmware repo
+- [ ] **Set up Gitea webhook** β on push to `main`, VPS auto-pulls and restarts Docker containers (replaces manual `git pull`)
+
+---
+
+## Gitea / Docker Compose Deployment Note
+
+Your local Docker Compose setup and VPS production setup are the same codebase β this is correct and will continue to work fine. A few tips:
+
+- Use a `.env.production` and `.env.development` file, never commit either
+- Your `docker-compose.yml` should reference `${ENV_VAR}` from the env file
+- The Gitea webhook for auto-deploy is a simple shell script triggered by the webhook:
+ ```bash
+ #!/bin/bash
+ cd /path/to/project
+ git pull origin main
+ docker compose up -d --build
+ ```
+- Protect this webhook endpoint with a secret token
+
+---
+
+## Summary β What Gets Killed
+
+| Old Way | Replaced By |
+|---|---|
+| Arduino IDE | PlatformIO (VS Code) |
+| Manual `mosquitto_passwd` via SSH | FastAPI dynamic auth endpoints |
+| Flutter admin app | React Admin Console |
+| Manual SN generation | Console batch creator |
+| Manual DB entry per device | Auto-provisioned on batch creation |
+| Manual firmware flash + config page | Browser provisioning wizard (esptool-js) |
+| Manual NVS entry via HTTP config page | Pre-flashed NVS partition |
+
+## Estimated Time Per Device (After All Phases Complete)
+
+| Task | Time |
+|---|---|
+| Generate 15-device batch + print labels | ~2 min |
+| Flash each device (plug in, click Flash, done) | ~3 min each (parallelizable) |
+| Devices self-verify on lab WiFi | passive, ~1 min each |
+| **Total for 15 devices** | **~20-25 min** |
+
+vs. current ~20 min per device = ~5 hours for 15.
diff --git a/vesper/platformio.ini b/vesper/platformio.ini
new file mode 100644
index 0000000..52e2324
--- /dev/null
+++ b/vesper/platformio.ini
@@ -0,0 +1,121 @@
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+; Project Vesper β PlatformIO Configuration
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+;
+; Hardware Variants:
+; vesper-v1 β Kincony KC868-A6 (ESP32-S3, 4MB flash) β current production board
+;
+; Future variants (not yet active):
+; vesper-plus-v1 β Vesper+ with RF remote support
+; vesper-pro-v1 β Vesper Pro with onboard LCD
+;
+; Build: pio run -e vesper-v1
+; Upload: pio run -e vesper-v1 --target upload
+; Monitor: pio device monitor
+; Clean: pio run -e vesper-v1 --target clean
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+; SHARED SETTINGS β inherited by all environments
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+[common]
+platform = espressif32
+framework = arduino
+monitor_speed = 115200
+
+; All external library dependencies
+lib_deps =
+ ; WiFi provisioning portal
+ tzapu/WiFiManager @ ^2.0.17
+
+ ; Async web server + WebSocket support
+ ; NOTE: Use the ESP32-compatible fork, not the original
+ https://github.com/me-no-dev/ESPAsyncWebServer.git
+ https://github.com/me-no-dev/AsyncTCP.git
+
+ ; JSON parsing
+ bblanchon/ArduinoJson @ ^7.0.0
+
+ ; I2C GPIO expanders (relay control) β PCF8575 header is bundled in same library
+ adafruit/Adafruit PCF8574 @ ^1.1.0
+
+ ; Real-time clock
+ adafruit/RTClib @ ^2.1.4
+
+ ; Async MQTT client
+ ; NOTE: Requires AsyncTCP (already listed above)
+ https://github.com/marvinroger/async-mqtt-client.git
+
+build_flags_common =
+ -DCORE_DEBUG_LEVEL=0
+ -DCONFIG_ASYNC_TCP_RUNNING_CORE=0
+
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+; VESPER v1 β Kincony KC868-A6 (ESP32-S3, 4MB Flash)
+; Current production board
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+[env:vesper-v1]
+platform = ${common.platform}
+framework = ${common.framework}
+board = esp32-s3-devkitc-1
+
+; Serial monitor
+monitor_speed = ${common.monitor_speed}
+
+; Upload settings
+upload_speed = 921600
+upload_protocol = esptool
+
+; Partition table β default 4MB with OTA support
+; Provides: 1.8MB app slot + 1.8MB OTA slot + 64KB NVS + SPIFFS
+board_build.partitions = default_8MB.csv
+
+; Build flags for this variant
+build_flags =
+ ${common.build_flags_common}
+ -DBOARD_TYPE=\"VS\"
+ -DBOARD_VERSION=\"01\"
+ -DBOARD_NAME=\"Vesper\"
+ -DPSRAM_ENABLED=0
+ -DHAS_RF=0
+ -DHAS_LCD=0
+
+lib_deps = ${common.lib_deps}
+
+
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+; VESPER+ v1 β Future: adds RF remote support
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+; [env:vesper-plus-v1]
+; platform = ${common.platform}
+; framework = ${common.framework}
+; board = esp32-s3-devkitc-1
+; monitor_speed = ${common.monitor_speed}
+; build_flags =
+; ${common.build_flags_common}
+; -DBOARD_TYPE=\"VP\"
+; -DBOARD_VERSION=\"01\"
+; -DBOARD_NAME=\"Vesper+\"
+; -DPSRAM_ENABLED=0
+; -DHAS_RF=1
+; -DHAS_LCD=0
+; lib_deps = ${common.lib_deps}
+
+
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+; VESPER PRO v1 β Future: adds onboard LCD
+; βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+; [env:vesper-pro-v1]
+; platform = ${common.platform}
+; framework = ${common.framework}
+; board = esp32-s3-devkitc-1
+; monitor_speed = ${common.monitor_speed}
+; build_flags =
+; ${common.build_flags_common}
+; -DBOARD_TYPE=\"VX\"
+; -DBOARD_VERSION=\"01\"
+; -DBOARD_NAME=\"VesperPro\"
+; -DPSRAM_ENABLED=0
+; -DHAS_RF=0
+; -DHAS_LCD=1
+; lib_deps = ${common.lib_deps}
diff --git a/vesper/src/BellEngine/BellEngine.cpp b/vesper/src/BellEngine/BellEngine.cpp
index 8d1d407..e126b35 100644
--- a/vesper/src/BellEngine/BellEngine.cpp
+++ b/vesper/src/BellEngine/BellEngine.cpp
@@ -22,6 +22,8 @@
// DEPENDENCY INCLUDES - Required system components
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
#include "BellEngine.hpp" // Header file with class definition
+
+#define TAG "BellEngine"
#include "../Player/Player.hpp" // Melody playback controller
#include "../ConfigManager/ConfigManager.hpp" // Configuration and settings
#include "../Telemetry/Telemetry.hpp" // System monitoring and analytics
@@ -74,7 +76,7 @@ BellEngine::~BellEngine() {
*
*/
void BellEngine::begin() {
- LOG_DEBUG("Initializing BellEngine...");
+ LOG_DEBUG(TAG, "Initializing BellEngine...");
// Create engine task with HIGHEST priority on dedicated Core 1
// This ensures maximum performance and timing precision
@@ -88,7 +90,7 @@ void BellEngine::begin() {
1 // π» Pin to Core 1 (dedicated)
);
- LOG_INFO("BellEngine initialized !");
+ LOG_INFO(TAG, "BellEngine initialized !");
}
/**
@@ -96,7 +98,7 @@ void BellEngine::begin() {
*/
void BellEngine::setCommunicationManager(CommunicationRouter* commManager) {
_communicationManager = commManager;
- LOG_DEBUG("BellEngine: Communication manager %s",
+ LOG_DEBUG(TAG, "BellEngine: Communication manager %s",
commManager ? "connected" : "disconnected");
}
@@ -116,22 +118,22 @@ void BellEngine::setCommunicationManager(CommunicationRouter* commManager) {
void BellEngine::start() {
// Validate that melody data is ready before starting
if (!_melodyDataReady.load()) {
- LOG_ERROR("Cannot start BellEngine: No melody data loaded");
+ LOG_ERROR(TAG, "Cannot start BellEngine: No melody data loaded");
return; // β Early exit if no melody data
}
- LOG_INFO("π BellEngine Ignition - Starting precision playback");
+ LOG_INFO(TAG, "π BellEngine Ignition - Starting precision playback");
_emergencyStop.store(false); // β
Clear any emergency stop state
_engineRunning.store(true); // β
Activate the engine atomically
}
void BellEngine::stop() {
- LOG_INFO("BellEngine - Stopping Gracefully");
+ LOG_INFO(TAG, "BellEngine - Stopping Gracefully");
_engineRunning.store(false);
}
void BellEngine::emergencyStop() {
- LOG_INFO("BellEngine - π Forcing Stop Immediately");
+ LOG_INFO(TAG, "BellEngine - π Forcing Stop Immediately");
_emergencyStop.store(true);
_engineRunning.store(false);
emergencyShutdown();
@@ -142,7 +144,7 @@ void BellEngine::setMelodyData(const std::vector& melodySteps) {
_melodySteps = melodySteps;
_melodyDataReady.store(true);
portEXIT_CRITICAL(&_melodyMutex);
- LOG_DEBUG("BellEngine - Loaded melody: %d steps", melodySteps.size());
+ LOG_DEBUG(TAG, "BellEngine - Loaded melody: %d steps", melodySteps.size());
}
void BellEngine::clearMelodyData() {
@@ -150,7 +152,7 @@ void BellEngine::clearMelodyData() {
_melodySteps.clear();
_melodyDataReady.store(false);
portEXIT_CRITICAL(&_melodyMutex);
- LOG_DEBUG("BellEngine - Melody data cleared");
+ LOG_DEBUG(TAG, "BellEngine - Melody data cleared");
}
// ================== CRITICAL TIMING SECTION ==================
@@ -158,7 +160,7 @@ void BellEngine::clearMelodyData() {
void BellEngine::engineTask(void* parameter) {
BellEngine* engine = static_cast(parameter);
- LOG_DEBUG("BellEngine - π₯ Engine task started on Core %d with MAXIMUM priority", xPortGetCoreID());
+ LOG_DEBUG(TAG, "BellEngine - π₯ Engine task started on Core %d with MAXIMUM priority", xPortGetCoreID());
while (true) {
if (engine->_engineRunning.load() && !engine->_emergencyStop.load()) {
@@ -186,7 +188,7 @@ void BellEngine::engineLoop() {
// Pause handling AFTER complete loop - never interrupt mid-melody!
while (_player.isPaused && _player.isPlaying && !_player.hardStop) {
- LOG_VERBOSE("BellEngine - βΈοΈ Pausing between melody loops");
+ LOG_VERBOSE(TAG, "BellEngine - βΈοΈ Pausing between melody loops");
vTaskDelay(pdMS_TO_TICKS(10)); // Wait during pause
}
@@ -207,17 +209,17 @@ void BellEngine::playbackLoop() {
portEXIT_CRITICAL(&_melodyMutex);
if (melodySteps.empty()) {
- LOG_ERROR("BellEngine - β Empty melody in playback loop!");
+ LOG_ERROR(TAG, "BellEngine - β Empty melody in playback loop!");
return;
}
- LOG_DEBUG("BellEngine - π΅ Starting melody loop (%d steps)", melodySteps.size());
+ LOG_DEBUG(TAG, "BellEngine - π΅ Starting melody loop (%d steps)", melodySteps.size());
// CRITICAL TIMING LOOP - Complete the entire melody without interruption
for (uint16_t note : melodySteps) {
// Emergency exit check (only emergency stops can interrupt mid-loop)
if (_emergencyStop.load() || _player.hardStop) {
- LOG_DEBUG("BellEngine - Emergency exit from playback loop");
+ LOG_DEBUG(TAG, "BellEngine - Emergency exit from playback loop");
return;
}
@@ -227,7 +229,7 @@ void BellEngine::playbackLoop() {
// Precise timing delay - validate speed to prevent division by zero
// I THINK this should be moved outside the Bell Engine
if (_player.speed == 0) {
- LOG_ERROR("BellEngine - β Invalid Speed (0) detected, stopping playback");
+ LOG_ERROR(TAG, "BellEngine - β Invalid Speed (0) detected, stopping playback");
_player.hardStop = true;
_engineRunning.store(false);
return;
@@ -242,9 +244,9 @@ void BellEngine::playbackLoop() {
_player.onMelodyLoopCompleted(); // π₯ Notify Player that melody actually finished!
if ((_player.continuous_loop && _player.segment_duration == 0) || _player.total_duration == 0) {
vTaskDelay(pdMS_TO_TICKS(500)); //Give Player time to pause/stop
- LOG_VERBOSE("BellEngine - Loop completed in SINGLE Mode - waiting for Player to handle pause/stop");
+ LOG_VERBOSE(TAG, "BellEngine - Loop completed in SINGLE Mode - waiting for Player to handle pause/stop");
}
- LOG_DEBUG("BellEngine - π΅ Melody loop completed with PRECISION");
+ LOG_DEBUG(TAG, "BellEngine - π΅ Melody loop completed with PRECISION");
}
@@ -268,26 +270,26 @@ void BellEngine::activateNote(uint16_t note) {
// Additional safety check to prevent underflow crashes
if (bellIndex >= 255) {
- LOG_ERROR("BellEngine - π¨ UNDERFLOW ERROR: bellIndex underflow for noteIndex %d", noteIndex);
+ LOG_ERROR(TAG, "BellEngine - π¨ UNDERFLOW ERROR: bellIndex underflow for noteIndex %d", noteIndex);
continue;
}
// Bounds check (CRITICAL SAFETY)
if (bellIndex >= 16) {
- LOG_ERROR("BellEngine - π¨ BOUNDS ERROR: bellIndex %d >= 16", bellIndex);
+ LOG_ERROR(TAG, "BellEngine - π¨ BOUNDS ERROR: bellIndex %d >= 16", bellIndex);
continue;
}
// Check for duplicate bell firing in this note
if (bellFired[bellIndex]) {
- LOG_DEBUG("BellEngine - β οΈ DUPLICATE BELL: Skipping duplicate firing of bell %d for note %d", bellIndex, noteIndex);
+ LOG_DEBUG(TAG, "BellEngine - β οΈ DUPLICATE BELL: Skipping duplicate firing of bell %d for note %d", bellIndex, noteIndex);
continue;
}
// Check if bell is configured (OutputManager will validate this)
uint8_t physicalOutput = _outputManager.getPhysicalOutput(bellIndex);
if (physicalOutput == 255) {
- LOG_DEBUG("BellEngine - β οΈ UNCONFIGURED: Bell %d not configured, skipping", bellIndex);
+ LOG_DEBUG(TAG, "BellEngine - β οΈ UNCONFIGURED: Bell %d not configured, skipping", bellIndex);
continue;
}
@@ -306,14 +308,14 @@ void BellEngine::activateNote(uint16_t note) {
// Record telemetry
_telemetry.recordBellStrike(bellIndex);
- LOG_VERBOSE("BellEngine - π¨ STRIKE! Note:%d β Bell:%d for %dms", noteIndex, bellIndex, durationMs);
+ LOG_VERBOSE(TAG, "BellEngine - π¨ STRIKE! Note:%d β Bell:%d for %dms", noteIndex, bellIndex, durationMs);
}
}
// π FIRE ALL BELLS SIMULTANEOUSLY!
if (!bellDurations.empty()) {
_outputManager.fireOutputsBatchForDuration(bellDurations);
- LOG_VERBOSE("BellEngine - π₯ Batch Fired %d bells Simultaneously !", bellDurations.size());
+ LOG_VERBOSE(TAG, "BellEngine - π₯ Batch Fired %d bells Simultaneously !", bellDurations.size());
// π NOTIFY WEBSOCKET CLIENTS OF BELL DINGS!
// * deactivated currently, since unstable and causes performance issues *
@@ -339,7 +341,7 @@ void BellEngine::preciseDelay(uint32_t microseconds) {
}
void BellEngine::emergencyShutdown() {
- LOG_INFO("BellEngine - π¨ Emergency Shutdown - Notifying OutputManager");
+ LOG_INFO(TAG, "BellEngine - π¨ Emergency Shutdown - Notifying OutputManager");
_outputManager.emergencyShutdown();
}
@@ -364,10 +366,10 @@ void BellEngine::notifyBellsFired(const std::vector& bellIndices) {
// Send notification to WebSocket clients only (not MQTT)
_communicationManager->broadcastToAllWebSocketClients(dingMsg);
- LOG_DEBUG("BellEngine - π DING notification sent for %d bells", bellIndices.size());
+ LOG_DEBUG(TAG, "BellEngine - π DING notification sent for %d bells", bellIndices.size());
} catch (...) {
- LOG_WARNING("BellEngine - β Failed to send ding notification");
+ LOG_WARNING(TAG, "BellEngine - β Failed to send ding notification");
}
}
@@ -378,20 +380,20 @@ void BellEngine::notifyBellsFired(const std::vector& bellIndices) {
bool BellEngine::isHealthy() const {
// Check if engine task is created and running
if (_engineTaskHandle == NULL) {
- LOG_DEBUG("BellEngine: Unhealthy - Task not created");
+ LOG_DEBUG(TAG, "BellEngine: Unhealthy - Task not created");
return false;
}
// Check if task is still alive
eTaskState taskState = eTaskGetState(_engineTaskHandle);
if (taskState == eDeleted || taskState == eInvalid) {
- LOG_DEBUG("BellEngine: Unhealthy - Task deleted or invalid");
+ LOG_DEBUG(TAG, "BellEngine: Unhealthy - Task deleted or invalid");
return false;
}
// Check if OutputManager is properly connected and healthy
if (!_outputManager.isInitialized()) {
- LOG_DEBUG("BellEngine: Unhealthy - OutputManager not initialized");
+ LOG_DEBUG(TAG, "BellEngine: Unhealthy - OutputManager not initialized");
return false;
}
diff --git a/vesper/src/ClientManager/ClientManager.cpp b/vesper/src/ClientManager/ClientManager.cpp
index 4f44560..2d65a58 100644
--- a/vesper/src/ClientManager/ClientManager.cpp
+++ b/vesper/src/ClientManager/ClientManager.cpp
@@ -1,31 +1,33 @@
#include "ClientManager.hpp"
+
+#define TAG "ClientManager"
#include "../Logging/Logging.hpp"
ClientManager::ClientManager() {
- LOG_INFO("Client Manager initialized !");
+ LOG_INFO(TAG, "Client Manager initialized !");
}
ClientManager::~ClientManager() {
_clients.clear();
- LOG_INFO("Client Manager destroyed");
+ LOG_INFO(TAG, "Client Manager destroyed");
}
void ClientManager::addClient(AsyncWebSocketClient* client, DeviceType deviceType) {
if (!isValidClient(client)) {
- LOG_WARNING("Client Manager - Cannot add invalid client");
+ LOG_WARNING(TAG, "Client Manager - Cannot add invalid client");
return;
}
uint32_t clientId = client->id();
_clients[clientId] = ClientInfo(client, deviceType);
- LOG_INFO("Client Manager - Client #%u added as %s device", clientId, deviceTypeToString(deviceType));
+ LOG_INFO(TAG, "Client Manager - Client #%u added as %s device", clientId, deviceTypeToString(deviceType));
}
void ClientManager::removeClient(uint32_t clientId) {
auto it = _clients.find(clientId);
if (it != _clients.end()) {
- LOG_INFO("Client Manager - Client #%u removed (%s device)", clientId,
+ LOG_INFO(TAG, "Client Manager - Client #%u removed (%s device)", clientId,
deviceTypeToString(it->second.deviceType));
_clients.erase(it);
}
@@ -36,7 +38,7 @@ void ClientManager::updateClientType(uint32_t clientId, DeviceType deviceType) {
if (it != _clients.end()) {
DeviceType oldType = it->second.deviceType;
it->second.deviceType = deviceType;
- LOG_INFO("Client Manager - Client #%u type updated from %s to %s", clientId,
+ LOG_INFO(TAG, "Client Manager - Client #%u type updated from %s to %s", clientId,
deviceTypeToString(oldType), deviceTypeToString(deviceType));
}
}
@@ -72,11 +74,11 @@ bool ClientManager::sendToClient(uint32_t clientId, const String& message) {
if (it != _clients.end() && isValidClient(it->second.client)) {
it->second.client->text(message);
updateClientLastSeen(clientId);
- LOG_DEBUG("Client Manager - Message sent to client #%u: %s", clientId, message.c_str());
+ LOG_DEBUG(TAG, "Client Manager - Message sent to client #%u: %s", clientId, message.c_str());
return true;
}
- LOG_WARNING("Client Manager - Failed to send message to client #%u - client not found or invalid", clientId);
+ LOG_WARNING(TAG, "Client Manager - Failed to send message to client #%u - client not found or invalid", clientId);
return false;
}
@@ -90,7 +92,7 @@ void ClientManager::sendToMasterClients(const String& message) {
count++;
}
}
- LOG_DEBUG("Client Manager - Message sent to %d master client(s): %s", count, message.c_str());
+ LOG_DEBUG(TAG, "Client Manager - Message sent to %d master client(s): %s", count, message.c_str());
}
void ClientManager::sendToSecondaryClients(const String& message) {
@@ -103,7 +105,7 @@ void ClientManager::sendToSecondaryClients(const String& message) {
count++;
}
}
- LOG_DEBUG("Client Manager - Message sent to %d secondary client(s): %s", count, message.c_str());
+ LOG_DEBUG(TAG, "Client Manager - Message sent to %d secondary client(s): %s", count, message.c_str());
}
void ClientManager::broadcastToAll(const String& message) {
@@ -115,14 +117,14 @@ void ClientManager::broadcastToAll(const String& message) {
count++;
}
}
- LOG_DEBUG("Client Manager - Message broadcasted to %d client(s): %s", count, message.c_str());
+ LOG_DEBUG(TAG, "Client Manager - Message broadcasted to %d client(s): %s", count, message.c_str());
}
void ClientManager::cleanupDisconnectedClients() {
auto it = _clients.begin();
while (it != _clients.end()) {
if (!isValidClient(it->second.client)) {
- LOG_DEBUG("Client Manager - Cleaning up disconnected client #%u", it->first);
+ LOG_DEBUG(TAG, "Client Manager - Cleaning up disconnected client #%u", it->first);
it->second.isConnected = false;
it = _clients.erase(it);
} else {
diff --git a/vesper/src/Communication/CommandHandler/CommandHandler.cpp b/vesper/src/Communication/CommandHandler/CommandHandler.cpp
index d9d710a..73deee8 100644
--- a/vesper/src/Communication/CommandHandler/CommandHandler.cpp
+++ b/vesper/src/Communication/CommandHandler/CommandHandler.cpp
@@ -3,6 +3,8 @@
*/
#include "CommandHandler.hpp"
+
+#define TAG "CommandHandler"
#include "../../ConfigManager/ConfigManager.hpp"
#include "../../OTAManager/OTAManager.hpp"
#include "../../Player/Player.hpp"
@@ -65,7 +67,7 @@ void CommandHandler::processCommand(JsonDocument& command, const MessageContext&
String cmd = command["cmd"];
JsonVariant contents = command["contents"];
- LOG_DEBUG("Processing command: %s from %s", cmd.c_str(),
+ LOG_DEBUG(TAG, "Processing command: %s from %s", cmd.c_str(),
context.source == MessageSource::MQTT ? "MQTT" : "WebSocket");
if (cmd == "ping") {
@@ -85,7 +87,7 @@ void CommandHandler::processCommand(JsonDocument& command, const MessageContext&
} else if (cmd == "system") {
handleSystemCommand(contents, context);
} else {
- LOG_WARNING("Unknown command received: %s", cmd.c_str());
+ LOG_WARNING(TAG, "Unknown command received: %s", cmd.c_str());
sendErrorResponse("unknown_command", "Command not recognized: " + cmd, context);
}
}
@@ -146,7 +148,7 @@ void CommandHandler::handleIdentifyCommand(JsonVariant contents, const MessageCo
// π‘οΈ SAFETY CHECK: Ensure ClientManager reference is set
if (!_clientManager) {
- LOG_ERROR("ClientManager reference not set in CommandHandler!");
+ LOG_ERROR(TAG, "ClientManager reference not set in CommandHandler!");
sendErrorResponse("identify", "Internal error: ClientManager not available", context);
return;
}
@@ -168,7 +170,7 @@ void CommandHandler::handleIdentifyCommand(JsonVariant contents, const MessageCo
if (deviceType != ClientManager::DeviceType::UNKNOWN) {
_clientManager->updateClientType(context.clientId, deviceType);
sendSuccessResponse("identify", "Device identified as " + deviceTypeStr, context);
- LOG_INFO("Client #%u identified as %s device", context.clientId, deviceTypeStr.c_str());
+ LOG_INFO(TAG, "Client #%u identified as %s device", context.clientId, deviceTypeStr.c_str());
} else {
sendErrorResponse("identify", "Invalid device_type. Use 'master' or 'secondary'", context);
}
@@ -184,7 +186,7 @@ void CommandHandler::handlePlaybackCommand(JsonVariant contents, const MessageCo
sendErrorResponse("playback", "Playback command failed", context);
}
} else {
- LOG_ERROR("Player reference not set");
+ LOG_ERROR(TAG, "Player reference not set");
sendErrorResponse("playback", "Player not available", context);
}
}
@@ -196,7 +198,7 @@ void CommandHandler::handleFileManagerCommand(JsonVariant contents, const Messag
}
String action = contents["action"];
- LOG_DEBUG("Processing file manager action: %s", action.c_str());
+ LOG_DEBUG(TAG, "Processing file manager action: %s", action.c_str());
if (action == "list_melodies") {
handleListMelodiesCommand(context);
@@ -205,7 +207,7 @@ void CommandHandler::handleFileManagerCommand(JsonVariant contents, const Messag
} else if (action == "delete_melody") {
handleDeleteMelodyCommand(contents, context);
} else {
- LOG_WARNING("Unknown file manager action: %s", action.c_str());
+ LOG_WARNING(TAG, "Unknown file manager action: %s", action.c_str());
sendErrorResponse("file_manager", "Unknown action: " + action, context);
}
}
@@ -217,14 +219,14 @@ void CommandHandler::handleRelaySetupCommand(JsonVariant contents, const Message
}
String action = contents["action"];
- LOG_DEBUG("Processing relay setup action: %s", action.c_str());
+ LOG_DEBUG(TAG, "Processing relay setup action: %s", action.c_str());
if (action == "set_timings") {
handleSetRelayTimersCommand(contents, context);
} else if (action == "set_outputs") {
handleSetRelayOutputsCommand(contents, context);
} else {
- LOG_WARNING("Unknown relay setup action: %s", action.c_str());
+ LOG_WARNING(TAG, "Unknown relay setup action: %s", action.c_str());
sendErrorResponse("relay_setup", "Unknown action: " + action, context);
}
}
@@ -236,7 +238,7 @@ void CommandHandler::handleClockSetupCommand(JsonVariant contents, const Message
}
String action = contents["action"];
- LOG_DEBUG("Processing clock setup action: %s", action.c_str());
+ LOG_DEBUG(TAG, "Processing clock setup action: %s", action.c_str());
if (action == "set_outputs") {
handleSetClockOutputsCommand(contents, context);
@@ -257,7 +259,7 @@ void CommandHandler::handleClockSetupCommand(JsonVariant contents, const Message
} else if (action == "set_enabled") {
handleSetClockEnabledCommand(contents, context);
} else {
- LOG_WARNING("Unknown clock setup action: %s", action.c_str());
+ LOG_WARNING(TAG, "Unknown clock setup action: %s", action.c_str());
sendErrorResponse("clock_setup", "Unknown action: " + action, context);
}
}
@@ -269,7 +271,7 @@ void CommandHandler::handleSystemInfoCommand(JsonVariant contents, const Message
}
String action = contents["action"];
- LOG_DEBUG("Processing system info action: %s", action.c_str());
+ LOG_DEBUG(TAG, "Processing system info action: %s", action.c_str());
if (action == "report_status") {
handleStatusCommand(context);
@@ -286,7 +288,7 @@ void CommandHandler::handleSystemInfoCommand(JsonVariant contents, const Message
} else if (action == "sync_time_to_lcd") {
handleSyncTimeToLcdCommand(context);
} else {
- LOG_WARNING("Unknown system info action: %s", action.c_str());
+ LOG_WARNING(TAG, "Unknown system info action: %s", action.c_str());
sendErrorResponse("system_info", "Unknown action: " + action, context);
}
}
@@ -303,7 +305,7 @@ void CommandHandler::handleListMelodiesCommand(const MessageContext& context) {
DeserializationError error = deserializeJson(doc, fileListJson);
if (error) {
- LOG_ERROR("Failed to parse file list JSON: %s", error.c_str());
+ LOG_ERROR(TAG, "Failed to parse file list JSON: %s", error.c_str());
sendErrorResponse("list_melodies", "Failed to parse file list", context);
return;
}
@@ -362,14 +364,14 @@ void CommandHandler::handleSetRelayTimersCommand(JsonVariant contents, const Mes
bool saved = _configManager.saveBellDurations();
if (saved) {
sendSuccessResponse("set_relay_timers", "Relay timers updated and saved", context);
- LOG_INFO("Relay timers updated and saved successfully");
+ LOG_INFO(TAG, "Relay timers updated and saved successfully");
} else {
sendErrorResponse("set_relay_timers", "Failed to save relay timers to SD card", context);
- LOG_ERROR("Failed to save relay timers configuration");
+ LOG_ERROR(TAG, "Failed to save relay timers configuration");
}
} catch (...) {
sendErrorResponse("set_relay_timers", "Failed to update relay timers", context);
- LOG_ERROR("Exception occurred while updating relay timers");
+ LOG_ERROR(TAG, "Exception occurred while updating relay timers");
}
}
@@ -380,14 +382,14 @@ void CommandHandler::handleSetRelayOutputsCommand(JsonVariant contents, const Me
bool saved = _configManager.saveBellOutputs();
if (saved) {
sendSuccessResponse("set_relay_outputs", "Relay outputs updated and saved", context);
- LOG_INFO("Relay outputs updated and saved successfully");
+ LOG_INFO(TAG, "Relay outputs updated and saved successfully");
} else {
sendErrorResponse("set_relay_outputs", "Failed to save relay outputs to SD card", context);
- LOG_ERROR("Failed to save relay outputs configuration");
+ LOG_ERROR(TAG, "Failed to save relay outputs configuration");
}
} catch (...) {
sendErrorResponse("set_relay_outputs", "Failed to update relay outputs", context);
- LOG_ERROR("Exception occurred while updating relay outputs");
+ LOG_ERROR(TAG, "Exception occurred while updating relay outputs");
}
}
@@ -398,14 +400,14 @@ void CommandHandler::handleSetClockOutputsCommand(JsonVariant contents, const Me
bool saved = _configManager.saveClockConfig();
if (saved) {
sendSuccessResponse("set_clock_outputs", "Clock outputs updated and saved", context);
- LOG_INFO("Clock outputs updated and saved successfully");
+ LOG_INFO(TAG, "Clock outputs updated and saved successfully");
} else {
sendErrorResponse("set_clock_outputs", "Failed to save clock outputs to SD card", context);
- LOG_ERROR("Failed to save clock outputs configuration");
+ LOG_ERROR(TAG, "Failed to save clock outputs configuration");
}
} catch (...) {
sendErrorResponse("set_clock_outputs", "Failed to update clock outputs", context);
- LOG_ERROR("Exception occurred while updating clock outputs");
+ LOG_ERROR(TAG, "Exception occurred while updating clock outputs");
}
}
@@ -416,14 +418,14 @@ void CommandHandler::handleSetClockTimingsCommand(JsonVariant contents, const Me
bool saved = _configManager.saveClockConfig();
if (saved) {
sendSuccessResponse("set_clock_timings", "Clock timings updated and saved", context);
- LOG_INFO("Clock timings updated and saved successfully");
+ LOG_INFO(TAG, "Clock timings updated and saved successfully");
} else {
sendErrorResponse("set_clock_timings", "Failed to save clock timings to SD card", context);
- LOG_ERROR("Failed to save clock timings configuration");
+ LOG_ERROR(TAG, "Failed to save clock timings configuration");
}
} catch (...) {
sendErrorResponse("set_clock_timings", "Failed to update clock timings", context);
- LOG_ERROR("Exception occurred while updating clock timings");
+ LOG_ERROR(TAG, "Exception occurred while updating clock timings");
}
}
@@ -434,14 +436,14 @@ void CommandHandler::handleSetClockAlertsCommand(JsonVariant contents, const Mes
bool saved = _configManager.saveClockConfig();
if (saved) {
sendSuccessResponse("set_clock_alerts", "Clock alerts updated and saved", context);
- LOG_INFO("Clock alerts updated and saved successfully");
+ LOG_INFO(TAG, "Clock alerts updated and saved successfully");
} else {
sendErrorResponse("set_clock_alerts", "Failed to save clock alerts to SD card", context);
- LOG_ERROR("Failed to save clock alerts configuration");
+ LOG_ERROR(TAG, "Failed to save clock alerts configuration");
}
} catch (...) {
sendErrorResponse("set_clock_alerts", "Failed to update clock alerts", context);
- LOG_ERROR("Exception occurred while updating clock alerts");
+ LOG_ERROR(TAG, "Exception occurred while updating clock alerts");
}
}
@@ -452,14 +454,14 @@ void CommandHandler::handleSetClockBacklightCommand(JsonVariant contents, const
bool saved = _configManager.saveClockConfig();
if (saved) {
sendSuccessResponse("set_clock_backlight", "Clock backlight updated and saved", context);
- LOG_INFO("Clock backlight updated and saved successfully");
+ LOG_INFO(TAG, "Clock backlight updated and saved successfully");
} else {
sendErrorResponse("set_clock_backlight", "Failed to save clock backlight to SD card", context);
- LOG_ERROR("Failed to save clock backlight configuration");
+ LOG_ERROR(TAG, "Failed to save clock backlight configuration");
}
} catch (...) {
sendErrorResponse("set_clock_backlight", "Failed to update clock backlight", context);
- LOG_ERROR("Exception occurred while updating clock backlight");
+ LOG_ERROR(TAG, "Exception occurred while updating clock backlight");
}
}
@@ -470,14 +472,14 @@ void CommandHandler::handleSetClockSilenceCommand(JsonVariant contents, const Me
bool saved = _configManager.saveClockConfig();
if (saved) {
sendSuccessResponse("set_clock_silence", "Clock silence periods updated and saved", context);
- LOG_INFO("Clock silence periods updated and saved successfully");
+ LOG_INFO(TAG, "Clock silence periods updated and saved successfully");
} else {
sendErrorResponse("set_clock_silence", "Failed to save clock silence configuration to SD card", context);
- LOG_ERROR("Failed to save clock silence configuration");
+ LOG_ERROR(TAG, "Failed to save clock silence configuration");
}
} catch (...) {
sendErrorResponse("set_clock_silence", "Failed to update clock silence periods", context);
- LOG_ERROR("Exception occurred while updating clock silence periods");
+ LOG_ERROR(TAG, "Exception occurred while updating clock silence periods");
}
}
@@ -514,7 +516,7 @@ void CommandHandler::handleSetRtcTimeCommand(JsonVariant contents, const Message
// Update timezone configuration
_configManager.updateTimeConfig(baseGmtOffset, dstOffset);
- LOG_INFO("Timezone updated: %s (GMT%+ld, DST%+ld)",
+ LOG_INFO(TAG, "Timezone updated: %s (GMT%+ld, DST%+ld)",
timezoneName.c_str(), baseGmtOffset/3600, dstOffset/3600);
// Apply total offset to timestamp
@@ -529,11 +531,11 @@ void CommandHandler::handleSetRtcTimeCommand(JsonVariant contents, const Message
if (verifyTime > 0 && abs((long)verifyTime - (long)localTimestamp) < 5) { // Allow 5 second tolerance
sendSuccessResponse("set_rtc_time",
"RTC time and timezone updated successfully", context);
- LOG_INFO("RTC time set with timezone: UTC %lu + %ld = local %lu",
+ LOG_INFO(TAG, "RTC time set with timezone: UTC %lu + %ld = local %lu",
timestamp, totalOffset, localTimestamp);
} else {
sendErrorResponse("set_rtc_time", "Failed to verify RTC time was set correctly", context);
- LOG_ERROR("RTC time verification failed - expected: %lu, got: %lu", localTimestamp, verifyTime);
+ LOG_ERROR(TAG, "RTC time verification failed - expected: %lu, got: %lu", localTimestamp, verifyTime);
}
} else {
// Legacy method: Use device's existing timezone config
@@ -543,10 +545,10 @@ void CommandHandler::handleSetRtcTimeCommand(JsonVariant contents, const Message
unsigned long verifyTime = _timeKeeper->getTime();
if (verifyTime > 0 && abs((long)verifyTime - (long)timestamp) < 5) { // Allow 5 second tolerance
sendSuccessResponse("set_rtc_time", "RTC time updated successfully", context);
- LOG_INFO("RTC time set using device timezone config: %lu", timestamp);
+ LOG_INFO(TAG, "RTC time set using device timezone config: %lu", timestamp);
} else {
sendErrorResponse("set_rtc_time", "Failed to verify RTC time was set correctly", context);
- LOG_ERROR("RTC time verification failed - expected: %lu, got: %lu", timestamp, verifyTime);
+ LOG_ERROR(TAG, "RTC time verification failed - expected: %lu, got: %lu", timestamp, verifyTime);
}
}
}
@@ -585,11 +587,11 @@ void CommandHandler::handleSetPhysicalClockTimeCommand(JsonVariant contents, con
if (saved) {
sendSuccessResponse("set_physical_clock_time", "Physical clock time updated and saved successfully", context);
- LOG_INFO("Physical clock time set to %02d:%02d (12h: %02d:%02d) and saved to SD",
+ LOG_INFO(TAG, "Physical clock time set to %02d:%02d (12h: %02d:%02d) and saved to SD",
hour, minute, clockHour, minute);
} else {
sendErrorResponse("set_physical_clock_time", "Physical clock time updated but failed to save to SD card", context);
- LOG_ERROR("Physical clock time set to %02d:%02d but failed to save to SD", hour, minute);
+ LOG_ERROR(TAG, "Physical clock time set to %02d:%02d but failed to save to SD", hour, minute);
}
}
@@ -601,12 +603,12 @@ void CommandHandler::handlePauseClockUpdatesCommand(JsonVariant contents, const
if (contents["action"] == "pause_clock_updates") {
_timeKeeper->pauseClockUpdates();
sendSuccessResponse("pause_clock_updates", "Clock updates paused", context);
- LOG_DEBUG("Clock updates paused");
+ LOG_DEBUG(TAG, "Clock updates paused");
return;
} else if (contents["action"] == "resume_clock_updates") {
_timeKeeper->resumeClockUpdates();
sendSuccessResponse("resume_clock_updates", "Clock updates resumed", context);
- LOG_DEBUG("Clock updates resumed");
+ LOG_DEBUG(TAG, "Clock updates resumed");
return;
}
}
@@ -626,14 +628,14 @@ void CommandHandler::handleSetClockEnabledCommand(JsonVariant contents, const Me
if (saved) {
String status = enabled ? "enabled" : "disabled";
sendSuccessResponse("set_clock_enabled", "Clock " + status + " and saved successfully", context);
- LOG_INFO("Clock %s via remote command", status.c_str());
+ LOG_INFO(TAG, "Clock %s via remote command", status.c_str());
} else {
sendErrorResponse("set_clock_enabled", "Clock setting updated but failed to save to SD card", context);
- LOG_ERROR("Failed to save clock enabled setting to SD card");
+ LOG_ERROR(TAG, "Failed to save clock enabled setting to SD card");
}
} catch (...) {
sendErrorResponse("set_clock_enabled", "Failed to update clock enabled setting", context);
- LOG_ERROR("Exception occurred while updating clock enabled setting");
+ LOG_ERROR(TAG, "Exception occurred while updating clock enabled setting");
}
}
@@ -668,14 +670,14 @@ void CommandHandler::handleGetDeviceTimeCommand(const MessageContext& context) {
response["payload"]["local_timestamp"] = millis() / 1000;
response["payload"]["utc_timestamp"] = millis() / 1000;
response["payload"]["rtc_available"] = false;
- LOG_WARNING("TimeKeeper reference not set for device time request");
+ LOG_WARNING(TAG, "TimeKeeper reference not set for device time request");
}
String responseStr;
serializeJson(response, responseStr);
sendResponse(responseStr, context);
- LOG_DEBUG("Device time requested");
+ LOG_DEBUG(TAG, "Device time requested");
}
void CommandHandler::handleGetClockTimeCommand(const MessageContext& context) {
@@ -694,7 +696,7 @@ void CommandHandler::handleGetClockTimeCommand(const MessageContext& context) {
serializeJson(response, responseStr);
sendResponse(responseStr, context);
- LOG_DEBUG("Physical clock time requested: %02d:%02d (last sync: %lu)",
+ LOG_DEBUG(TAG, "Physical clock time requested: %02d:%02d (last sync: %lu)",
_configManager.getPhysicalClockHour(),
_configManager.getPhysicalClockMinute(),
_configManager.getLastSyncTime());
@@ -716,16 +718,16 @@ void CommandHandler::handleCommitFirmwareCommand(const MessageContext& context)
return;
}
- LOG_INFO("πΎ Manual firmware commit requested via %s",
+ LOG_INFO(TAG, "πΎ Manual firmware commit requested via %s",
context.source == MessageSource::MQTT ? "MQTT" : "WebSocket");
try {
_firmwareValidator->commitFirmware();
sendSuccessResponse("commit_firmware", "Firmware committed successfully", context);
- LOG_INFO("β
Firmware manually committed - system is now stable");
+ LOG_INFO(TAG, "β
Firmware manually committed - system is now stable");
} catch (...) {
sendErrorResponse("commit_firmware", "Failed to commit firmware", context);
- LOG_ERROR("β Failed to commit firmware");
+ LOG_ERROR(TAG, "β Failed to commit firmware");
}
}
@@ -735,18 +737,18 @@ void CommandHandler::handleRollbackFirmwareCommand(const MessageContext& context
return;
}
- LOG_WARNING("π Manual firmware rollback requested via %s",
+ LOG_WARNING(TAG, "π Manual firmware rollback requested via %s",
context.source == MessageSource::MQTT ? "MQTT" : "WebSocket");
try {
_firmwareValidator->rollbackFirmware();
sendSuccessResponse("rollback_firmware", "Firmware rollback initiated - device will reboot", context);
- LOG_WARNING("π Firmware rollback initiated - device should reboot shortly");
+ LOG_WARNING(TAG, "π Firmware rollback initiated - device should reboot shortly");
// Device should reboot automatically, but this response might not be sent
} catch (...) {
sendErrorResponse("rollback_firmware", "Failed to initiate firmware rollback", context);
- LOG_ERROR("β Failed to initiate firmware rollback");
+ LOG_ERROR(TAG, "β Failed to initiate firmware rollback");
}
}
@@ -820,7 +822,7 @@ void CommandHandler::handleGetFirmwareStatusCommand(const MessageContext& contex
serializeJson(response, responseStr);
sendResponse(responseStr, context);
- LOG_DEBUG("Firmware status requested: %s", stateStr.c_str());
+ LOG_DEBUG(TAG, "Firmware status requested: %s", stateStr.c_str());
}
void CommandHandler::handleNetworkInfoCommand(const MessageContext& context) {
@@ -839,7 +841,7 @@ void CommandHandler::handleNetworkInfoCommand(const MessageContext& context) {
}
void CommandHandler::handleGetFullSettingsCommand(const MessageContext& context) {
- LOG_DEBUG("Full settings requested");
+ LOG_DEBUG(TAG, "Full settings requested");
// Get all settings as JSON string from ConfigManager
String settingsJson = _configManager.getAllSettingsAsJson();
@@ -854,7 +856,7 @@ void CommandHandler::handleGetFullSettingsCommand(const MessageContext& context)
DeserializationError error = deserializeJson(settingsDoc, settingsJson);
if (error) {
- LOG_ERROR("Failed to parse settings JSON: %s", error.c_str());
+ LOG_ERROR(TAG, "Failed to parse settings JSON: %s", error.c_str());
sendErrorResponse("get_full_settings", "Failed to serialize settings", context);
return;
}
@@ -865,7 +867,7 @@ void CommandHandler::handleGetFullSettingsCommand(const MessageContext& context)
serializeJson(response, responseStr);
sendResponse(responseStr, context);
- LOG_DEBUG("Full settings sent (%d bytes)", responseStr.length());
+ LOG_DEBUG(TAG, "Full settings sent (%d bytes)", responseStr.length());
}
void CommandHandler::handleSyncTimeToLcdCommand(const MessageContext& context) {
@@ -880,7 +882,7 @@ void CommandHandler::handleSyncTimeToLcdCommand(const MessageContext& context) {
} else {
// Fallback to millis if TimeKeeper not available
localTimestamp = millis() / 1000;
- LOG_WARNING("TimeKeeper not available for LCD time sync");
+ LOG_WARNING(TAG, "TimeKeeper not available for LCD time sync");
}
// Get timezone offset from ConfigManager (in seconds)
@@ -897,7 +899,7 @@ void CommandHandler::handleSyncTimeToLcdCommand(const MessageContext& context) {
serializeJson(response, responseStr);
sendResponse(responseStr, context);
- LOG_DEBUG("LCD time sync: UTC=%lu, offset=%ld", utcTimestamp, totalOffset);
+ LOG_DEBUG(TAG, "LCD time sync: UTC=%lu, offset=%ld", utcTimestamp, totalOffset);
}
void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const MessageContext& context) {
@@ -933,7 +935,7 @@ void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const M
hostname = newHostname;
configChanged = true;
needsReboot = true;
- LOG_INFO("Hostname will be updated to: %s", hostname.c_str());
+ LOG_INFO(TAG, "Hostname will be updated to: %s", hostname.c_str());
} else {
sendErrorResponse("set_network_config", "Invalid hostname (must be 1-32 characters)", context);
return;
@@ -975,9 +977,9 @@ void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const M
dns2.fromString(contents["dns2"].as());
}
- LOG_INFO("Static IP configuration will be applied: %s", ip.toString().c_str());
+ LOG_INFO(TAG, "Static IP configuration will be applied: %s", ip.toString().c_str());
} else {
- LOG_INFO("DHCP mode will be enabled");
+ LOG_INFO(TAG, "DHCP mode will be enabled");
}
}
@@ -996,10 +998,10 @@ void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const M
responseMsg += ". RESTART DEVICE to apply changes";
}
sendSuccessResponse("set_network_config", responseMsg, context);
- LOG_INFO("β
Network configuration saved to SD card");
+ LOG_INFO(TAG, "β
Network configuration saved to SD card");
} else {
sendErrorResponse("set_network_config", "Configuration updated but failed to save to SD card", context);
- LOG_ERROR("β Failed to save network configuration to SD card");
+ LOG_ERROR(TAG, "β Failed to save network configuration to SD card");
}
} else {
sendSuccessResponse("set_network_config", "No changes detected", context);
@@ -1007,10 +1009,10 @@ void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const M
} catch (const std::exception& e) {
sendErrorResponse("set_network_config", String("Exception: ") + e.what(), context);
- LOG_ERROR("Exception in handleSetNetworkConfigCommand: %s", e.what());
+ LOG_ERROR(TAG, "Exception in handleSetNetworkConfigCommand: %s", e.what());
} catch (...) {
sendErrorResponse("set_network_config", "Unknown error occurred", context);
- LOG_ERROR("Unknown exception in handleSetNetworkConfigCommand");
+ LOG_ERROR(TAG, "Unknown exception in handleSetNetworkConfigCommand");
}
}
@@ -1020,7 +1022,7 @@ void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const M
void CommandHandler::handleResetDefaultsCommand(const MessageContext& context) {
- LOG_WARNING("β οΈ Factory reset requested. Proceeding...");
+ LOG_WARNING(TAG, "β οΈ Factory reset requested. Proceeding...");
try {
// Reset all configurations to defaults
@@ -1028,14 +1030,14 @@ void CommandHandler::handleResetDefaultsCommand(const MessageContext& context) {
if (resetComplete) {
sendSuccessResponse("reset_defaults", "Reset to Defaults completed. Device will Restart to apply changes.", context);
- LOG_WARNING("β
Factory reset completed and all configurations saved to SD card");
+ LOG_WARNING(TAG, "β
Factory reset completed and all configurations saved to SD card");
} else {
sendErrorResponse("reset_defaults", "Reset to Defaults applied but failed to save some configurations to SD card", context);
- LOG_ERROR("β Reset to Defaults applied but failed to save some configurations to SD card");
+ LOG_ERROR(TAG, "β Reset to Defaults applied but failed to save some configurations to SD card");
}
} catch (...) {
sendErrorResponse("reset_defaults", "Failed to perform Reset to Defaults", context);
- LOG_ERROR("β Exception occurred during Resetting to Defaults");
+ LOG_ERROR(TAG, "β Exception occurred during Resetting to Defaults");
}
}
@@ -1053,7 +1055,7 @@ void CommandHandler::handleSystemCommand(JsonVariant contents, const MessageCont
}
String action = contents["action"];
- LOG_DEBUG("Processing system action: %s", action.c_str());
+ LOG_DEBUG(TAG, "Processing system action: %s", action.c_str());
if (action == "status") {
handleStatusCommand(context);
@@ -1082,7 +1084,7 @@ void CommandHandler::handleSystemCommand(JsonVariant contents, const MessageCont
} else if (action == "custom_update") {
handleCustomUpdateCommand(contents, context);
} else {
- LOG_WARNING("Unknown system action: %s", action.c_str());
+ LOG_WARNING(TAG, "Unknown system action: %s", action.c_str());
sendErrorResponse("system", "Unknown action: " + action, context);
}
}
@@ -1110,11 +1112,11 @@ void CommandHandler::handleSetSerialLogLevelCommand(JsonVariant contents, const
if (saved) {
sendSuccessResponse("set_serial_log_level",
"Serial log level set to " + String(level) + " and saved", context);
- LOG_INFO("Serial log level updated to %d", level);
+ LOG_INFO(TAG, "Serial log level updated to %d", level);
} else {
sendErrorResponse("set_serial_log_level",
"Log level set but failed to save to SD card", context);
- LOG_ERROR("Failed to save serial log level to SD card");
+ LOG_ERROR(TAG, "Failed to save serial log level to SD card");
}
} else {
sendErrorResponse("set_serial_log_level",
@@ -1132,17 +1134,20 @@ void CommandHandler::handleSetSdLogLevelCommand(JsonVariant contents, const Mess
// Set the level in ConfigManager
if (_configManager.setSdLogLevel(level)) {
+ // Apply immediately
+ Logging::setSdLevel((Logging::LogLevel)level);
+
// Save to SD card
bool saved = _configManager.saveGeneralConfig();
-
+
if (saved) {
- sendSuccessResponse("set_sd_log_level",
+ sendSuccessResponse("set_sd_log_level",
"SD log level set to " + String(level) + " and saved", context);
- LOG_INFO("SD log level updated to %d (not yet implemented)", level);
+ LOG_INFO(TAG, "SD log level updated to %d", level);
} else {
sendErrorResponse("set_sd_log_level",
"Log level set but failed to save to SD card", context);
- LOG_ERROR("Failed to save SD log level to SD card");
+ LOG_ERROR(TAG, "Failed to save SD log level to SD card");
}
} else {
sendErrorResponse("set_sd_log_level",
@@ -1169,11 +1174,11 @@ void CommandHandler::handleSetMqttLogLevelCommand(JsonVariant contents, const Me
if (saved) {
sendSuccessResponse("set_mqtt_log_level",
"MQTT log level set to " + String(level) + " and saved", context);
- LOG_INFO("MQTT log level updated to %d", level);
+ LOG_INFO(TAG, "MQTT log level updated to %d", level);
} else {
sendErrorResponse("set_mqtt_log_level",
"Log level set but failed to save to SD card", context);
- LOG_ERROR("Failed to save MQTT log level to SD card");
+ LOG_ERROR(TAG, "Failed to save MQTT log level to SD card");
}
} else {
sendErrorResponse("set_mqtt_log_level",
@@ -1198,7 +1203,7 @@ void CommandHandler::handleSetMqttEnabledCommand(JsonVariant contents, const Mes
if (saved) {
sendSuccessResponse("set_mqtt_enabled",
String("MQTT ") + (enabled ? "enabled" : "disabled") + " and saved", context);
- LOG_INFO("MQTT %s by user command", enabled ? "enabled" : "disabled");
+ LOG_INFO(TAG, "MQTT %s by user command", enabled ? "enabled" : "disabled");
// If disabling, disconnect MQTT immediately
// If enabling, trigger connection attempt
@@ -1209,12 +1214,12 @@ void CommandHandler::handleSetMqttEnabledCommand(JsonVariant contents, const Mes
_communicationRouter->getMQTTClient().connect();
}
} else {
- LOG_WARNING("CommunicationRouter reference not set - cannot control MQTT");
+ LOG_WARNING(TAG, "CommunicationRouter reference not set - cannot control MQTT");
}
} else {
sendErrorResponse("set_mqtt_enabled",
"MQTT state changed but failed to save to SD card", context);
- LOG_ERROR("Failed to save MQTT enabled state to SD card");
+ LOG_ERROR(TAG, "Failed to save MQTT enabled state to SD card");
}
}
@@ -1223,7 +1228,7 @@ void CommandHandler::handleSetMqttEnabledCommand(JsonVariant contents, const Mes
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
void CommandHandler::handleRestartCommand(const MessageContext& context) {
- LOG_WARNING("π Device restart requested via command");
+ LOG_WARNING(TAG, "π Device restart requested via command");
sendSuccessResponse("restart", "Device will restart in 2 seconds", context);
// Small delay to ensure response is sent
@@ -1238,12 +1243,12 @@ void CommandHandler::handleRestartCommand(const MessageContext& context) {
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
void CommandHandler::handleForceUpdateCommand(JsonVariant contents, const MessageContext& context) {
- LOG_WARNING("π Force OTA update requested via command");
+ LOG_WARNING(TAG, "π Force OTA update requested via command");
// Check if player is active
if (_player && _player->isCurrentlyPlaying()) {
sendErrorResponse("force_update", "Cannot update while playback is active", context);
- LOG_WARNING("Force update rejected - player is active");
+ LOG_WARNING(TAG, "Force update rejected - player is active");
return;
}
@@ -1264,7 +1269,7 @@ void CommandHandler::handleForceUpdateCommand(JsonVariant contents, const Messag
// Note: If update succeeds, device will reboot and this won't be reached
if (!result) {
- LOG_ERROR("Force update failed");
+ LOG_ERROR(TAG, "Force update failed");
// Error response may not be received if we already restarted
}
}
@@ -1274,7 +1279,7 @@ void CommandHandler::handleForceUpdateCommand(JsonVariant contents, const Messag
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
void CommandHandler::handleCustomUpdateCommand(JsonVariant contents, const MessageContext& context) {
- LOG_WARNING("π₯ Custom OTA update requested via command");
+ LOG_WARNING(TAG, "π₯ Custom OTA update requested via command");
// Validate required parameters
if (!contents.containsKey("firmware_url")) {
@@ -1295,11 +1300,11 @@ void CommandHandler::handleCustomUpdateCommand(JsonVariant contents, const Messa
// Check if player is active
if (_player && _player->isCurrentlyPlaying()) {
sendErrorResponse("custom_update", "Cannot update while playback is active", context);
- LOG_WARNING("Custom update rejected - player is active");
+ LOG_WARNING(TAG, "Custom update rejected - player is active");
return;
}
- LOG_INFO("Custom update: URL=%s, Checksum=%s, Size=%u, Version=%u",
+ LOG_INFO(TAG, "Custom update: URL=%s, Checksum=%s, Size=%u, Version=%u",
firmwareUrl.c_str(),
checksum.isEmpty() ? "none" : checksum.c_str(),
fileSize,
@@ -1316,7 +1321,7 @@ void CommandHandler::handleCustomUpdateCommand(JsonVariant contents, const Messa
// Note: If update succeeds, device will reboot and this won't be reached
if (!result) {
- LOG_ERROR("Custom update failed");
+ LOG_ERROR(TAG, "Custom update failed");
// Error response may not be received if we already restarted
}
}
diff --git a/vesper/src/Communication/CommunicationRouter/CommunicationRouter.cpp b/vesper/src/Communication/CommunicationRouter/CommunicationRouter.cpp
index b48ca47..a194bf9 100644
--- a/vesper/src/Communication/CommunicationRouter/CommunicationRouter.cpp
+++ b/vesper/src/Communication/CommunicationRouter/CommunicationRouter.cpp
@@ -3,6 +3,8 @@
*/
#include "CommunicationRouter.hpp"
+
+#define TAG "CommRouter"
#include "../../ConfigManager/ConfigManager.hpp"
#include "../../OTAManager/OTAManager.hpp"
#include "../../Networking/Networking.hpp"
@@ -39,11 +41,11 @@ CommunicationRouter::CommunicationRouter(ConfigManager& configManager,
CommunicationRouter::~CommunicationRouter() {}
void CommunicationRouter::begin() {
- LOG_INFO("Initializing Communication Router v4.0 (Modular)");
+ LOG_INFO(TAG, "Initializing Communication Router v4.0 (Modular)");
// π₯ CRITICAL: Initialize WebSocket FIRST to ensure it's always set up
// Even if MQTT fails, we want WebSocket to work!
- LOG_INFO("Setting up WebSocket server...");
+ LOG_INFO(TAG, "Setting up WebSocket server...");
// Initialize WebSocket server
_wsServer.begin();
@@ -54,11 +56,11 @@ void CommunicationRouter::begin() {
// π₯ CRITICAL FIX: Attach WebSocket handler to AsyncWebServer
// This MUST happen before any potential failures!
_server.addHandler(&_webSocket);
- LOG_INFO("β
WebSocket handler attached to AsyncWebServer on /ws");
+ LOG_INFO(TAG, "β
WebSocket handler attached to AsyncWebServer on /ws");
//Now initialize MQTT client (can fail without breaking WebSocket)
try {
- LOG_INFO("Setting up MQTT client...");
+ LOG_INFO(TAG, "Setting up MQTT client...");
_mqttClient.begin();
_mqttClient.setCallback([this](const String& topic, const String& payload) {
onMqttMessage(topic, payload);
@@ -73,23 +75,36 @@ void CommunicationRouter::begin() {
logTopic
);
- // Apply MQTT log level from config
- uint8_t mqttLogLevel = _configManager.getMqttLogLevel();
- Logging::setMqttLogLevel((Logging::LogLevel)mqttLogLevel);
- LOG_INFO("MQTT logging enabled with level %d on topic: %s", mqttLogLevel, logTopic.c_str());
+ // Apply log levels from config for all three channels
+ Logging::setSerialLevel((Logging::LogLevel)_configManager.getSerialLogLevel());
+ Logging::setMqttLevel((Logging::LogLevel)_configManager.getMqttLogLevel());
+ Logging::setSdLevel((Logging::LogLevel)_configManager.getSdLogLevel());
+ LOG_INFO(TAG, "Log levels applied β Serial:%d MQTT:%d SD:%d",
+ _configManager.getSerialLogLevel(),
+ _configManager.getMqttLogLevel(),
+ _configManager.getSdLogLevel());
- LOG_INFO("β
MQTT client initialized");
+ // Silence MQTT-internal subsystems on the MQTT channel to prevent log storms.
+ // These systems generate logs while sending logs β suppress them over MQTT only.
+ Logging::setSubsystemMqttLevel("MQTTClient", Logging::NONE);
+ Logging::setSubsystemMqttLevel("CommRouter", Logging::WARNING);
+ Logging::setSubsystemMqttLevel("Logger", Logging::NONE);
+
+ LOG_INFO(TAG, "β
MQTT client initialized");
} catch (...) {
- LOG_ERROR("β MQTT initialization failed, but WebSocket is still available");
+ LOG_ERROR(TAG, "β MQTT initialization failed, but WebSocket is still available");
}
+
+ // Wire up SD logging channel (requires FileManager to be set first via setFileManagerReference)
+ // SD callback is registered lazily in setFileManagerReference once the pointer is available
// π₯ CRITICAL FIX: Connect ClientManager to CommandHandler
_commandHandler.setClientManagerReference(&_clientManager);
- LOG_INFO("ClientManager reference set for CommandHandler");
+ LOG_INFO(TAG, "ClientManager reference set for CommandHandler");
// π₯ Set CommunicationRouter reference for MQTT control commands
_commandHandler.setCommunicationRouterReference(this);
- LOG_INFO("CommunicationRouter reference set for CommandHandler");
+ LOG_INFO(TAG, "CommunicationRouter reference set for CommandHandler");
// Setup command handler response callback
_commandHandler.setResponseCallback([this](const String& response, const CommandHandler::MessageContext& context) {
@@ -97,30 +112,30 @@ void CommunicationRouter::begin() {
});
// Initialize HTTP Request Handler
- LOG_INFO("Setting up HTTP REST API...");
+ LOG_INFO(TAG, "Setting up HTTP REST API...");
_httpHandler.begin();
_httpHandler.setCommandHandlerReference(&_commandHandler);
- LOG_INFO("β
HTTP REST API initialized");
+ LOG_INFO(TAG, "β
HTTP REST API initialized");
// Initialize Settings Web Server
- LOG_INFO("Setting up Settings Web Server...");
+ LOG_INFO(TAG, "Setting up Settings Web Server...");
_settingsServer.begin();
- LOG_INFO("β
Settings Web Server initialized at /settings");
+ LOG_INFO(TAG, "β
Settings Web Server initialized at /settings");
// Initialize UART Command Handler
- LOG_INFO("Setting up UART Command Handler...");
+ LOG_INFO(TAG, "Setting up UART Command Handler...");
_uartHandler.begin();
_uartHandler.setCallback([this](JsonDocument& message) {
onUartMessage(message);
});
- LOG_INFO("β
UART Command Handler initialized (TX: GPIO12, RX: GPIO13)");
+ LOG_INFO(TAG, "β
UART Command Handler initialized (TX: GPIO12, RX: GPIO13)");
- LOG_INFO("Communication Router initialized with modular architecture");
- LOG_INFO(" β’ MQTT: AsyncMqttClient");
- LOG_INFO(" β’ WebSocket: Multi-client support");
- LOG_INFO(" β’ HTTP REST API: /api endpoints");
- LOG_INFO(" β’ UART: External device control");
- LOG_INFO(" β’ Settings Page: /settings");
+ LOG_INFO(TAG, "Communication Router initialized with modular architecture");
+ LOG_INFO(TAG, " β’ MQTT: AsyncMqttClient");
+ LOG_INFO(TAG, " β’ WebSocket: Multi-client support");
+ LOG_INFO(TAG, " β’ HTTP REST API: /api endpoints");
+ LOG_INFO(TAG, " β’ UART: External device control");
+ LOG_INFO(TAG, " β’ Settings Page: /settings");
}
void CommunicationRouter::loop() {
@@ -136,6 +151,14 @@ void CommunicationRouter::setPlayerReference(Player* player) {
void CommunicationRouter::setFileManagerReference(FileManager* fm) {
_fileManager = fm;
_commandHandler.setFileManagerReference(fm);
+
+ // Register SD log channel now that FileManager is available
+ if (fm != nullptr) {
+ Logging::setSdWriteCallback([fm](const String& line) {
+ fm->appendLine("/logs/vesper.log", line);
+ });
+ LOG_INFO(TAG, "SD log channel registered -> /logs/vesper.log");
+ }
}
void CommunicationRouter::setTimeKeeperReference(Timekeeper* tk) {
@@ -155,11 +178,11 @@ void CommunicationRouter::setTelemetryReference(Telemetry* telemetry) {
void CommunicationRouter::setupUdpDiscovery() {
uint16_t discoveryPort = _configManager.getNetworkConfig().discoveryPort;
if (_udp.listen(discoveryPort)) {
- LOG_INFO("UDP discovery listening on port %u", discoveryPort);
+ LOG_INFO(TAG, "UDP discovery listening on port %u", discoveryPort);
_udp.onPacket([this](AsyncUDPPacket packet) {
String msg = String((const char*)packet.data(), packet.length());
- LOG_DEBUG("UDP from %s:%u -> %s",
+ LOG_DEBUG(TAG, "UDP from %s:%u -> %s",
packet.remoteIP().toString().c_str(),
packet.remotePort(),
msg.c_str());
@@ -200,7 +223,7 @@ void CommunicationRouter::setupUdpDiscovery() {
packet.remoteIP(), packet.remotePort());
});
} else {
- LOG_ERROR("Failed to start UDP discovery.");
+ LOG_ERROR(TAG, "Failed to start UDP discovery.");
}
}
@@ -219,22 +242,24 @@ size_t CommunicationRouter::getWebSocketClientCount() const {
bool CommunicationRouter::isHealthy() const {
// Check if required references are set
if (!_player || !_fileManager || !_timeKeeper) {
- LOG_DEBUG("CommunicationRouter: Unhealthy - Missing references");
+ LOG_WARNING(TAG, "Unhealthy - missing subsystem references (player=%d fileManager=%d timeKeeper=%d)",
+ _player != nullptr, _fileManager != nullptr, _timeKeeper != nullptr);
return false;
}
-
+
+ // Check network connectivity first β no point checking connections without a network
+ if (!_networking.isConnected()) {
+ LOG_WARNING(TAG, "Unhealthy - no network connection");
+ return false;
+ }
+
// Check if at least one protocol is connected
if (!isMqttConnected() && !hasActiveWebSocketClients()) {
- LOG_DEBUG("CommunicationRouter: Unhealthy - No active connections");
+ LOG_WARNING(TAG, "Unhealthy - no active connections (MQTT=%d, WebSocket=%d)",
+ isMqttConnected(), hasActiveWebSocketClients());
return false;
}
-
- // Check network connectivity
- if (!_networking.isConnected()) {
- LOG_DEBUG("CommunicationRouter: Unhealthy - No network connection");
- return false;
- }
-
+
return true;
}
@@ -270,9 +295,9 @@ void CommunicationRouter::broadcastToAllWebSocketClients(const JsonDocument& mes
void CommunicationRouter::publishToMqtt(const String& data) {
if (_mqttClient.isConnected()) {
_mqttClient.publish("data", data, 0, false);
- LOG_DEBUG("Published to MQTT: %s", data.c_str());
+ LOG_DEBUG(TAG, "Published to MQTT: %s", data.c_str());
} else {
- LOG_ERROR("MQTT Not Connected! Message Failed: %s", data.c_str());
+ LOG_ERROR(TAG, "MQTT Not Connected! Message Failed: %s", data.c_str());
}
}
@@ -294,29 +319,29 @@ void CommunicationRouter::sendBellOverloadNotification(const std::vector doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
- LOG_ERROR("Failed to parse MQTT JSON: %s", error.c_str());
+ LOG_ERROR(TAG, "Failed to parse MQTT JSON: %s", error.c_str());
return;
}
@@ -330,7 +355,7 @@ void CommunicationRouter::onMqttMessage(const String& topic, const String& paylo
void CommunicationRouter::onWebSocketMessage(uint32_t clientId, const JsonDocument& message) {
// Extract command for logging
String cmd = message["cmd"] | "unknown";
- LOG_INFO("π¨ WebSocket message from client #%u: cmd=%s", clientId, cmd.c_str());
+ LOG_INFO(TAG, "π¨ WebSocket message from client #%u: cmd=%s", clientId, cmd.c_str());
// Create message context for WebSocket with client ID
CommandHandler::MessageContext context(CommandHandler::MessageSource::WEBSOCKET, clientId);
@@ -339,7 +364,7 @@ void CommunicationRouter::onWebSocketMessage(uint32_t clientId, const JsonDocume
JsonDocument& mutableDoc = const_cast(message);
_commandHandler.processCommand(mutableDoc, context);
- LOG_DEBUG("WebSocket message from client #%u processed", clientId);
+ LOG_DEBUG(TAG, "WebSocket message from client #%u processed", clientId);
}
void CommunicationRouter::onUartMessage(JsonDocument& message) {
@@ -360,12 +385,12 @@ void CommunicationRouter::onUartMessage(JsonDocument& message) {
if (!allowed) {
// Silently ignore - do NOT send error response to avoid feedback loop
- LOG_DEBUG("UART: Ignoring non-whitelisted command (cmd=%s, action=%s)",
+ LOG_DEBUG(TAG, "UART: Ignoring non-whitelisted command (cmd=%s, action=%s)",
cmd.c_str(), action.c_str());
return;
}
- LOG_INFO("π UART command received: cmd=%s, action=%s", cmd.c_str(), action.c_str());
+ LOG_INFO(TAG, "π UART command received: cmd=%s, action=%s", cmd.c_str(), action.c_str());
// Create message context for UART
CommandHandler::MessageContext context(CommandHandler::MessageSource::UART);
@@ -373,20 +398,20 @@ void CommunicationRouter::onUartMessage(JsonDocument& message) {
// Forward to command handler
_commandHandler.processCommand(message, context);
- LOG_DEBUG("UART message processed");
+ LOG_DEBUG(TAG, "UART message processed");
}
void CommunicationRouter::sendResponse(const String& response, const CommandHandler::MessageContext& context) {
if (context.source == CommandHandler::MessageSource::MQTT) {
- LOG_DEBUG("βοΈ Sending response via MQTT: %s", response.c_str());
+ LOG_DEBUG(TAG, "βοΈ Sending response via MQTT: %s", response.c_str());
publishToMqtt(response);
} else if (context.source == CommandHandler::MessageSource::WEBSOCKET) {
- LOG_DEBUG("βοΈ Sending response to WebSocket client #%u: %s", context.clientId, response.c_str());
+ LOG_DEBUG(TAG, "βοΈ Sending response to WebSocket client #%u: %s", context.clientId, response.c_str());
_wsServer.sendToClient(context.clientId, response);
} else if (context.source == CommandHandler::MessageSource::UART) {
- LOG_DEBUG("βοΈ Sending response via UART: %s", response.c_str());
+ LOG_DEBUG(TAG, "βοΈ Sending response via UART: %s", response.c_str());
_uartHandler.send(response);
} else {
- LOG_ERROR("β Unknown message source for response routing!");
+ LOG_ERROR(TAG, "β Unknown message source for response routing!");
}
}
diff --git a/vesper/src/Communication/HTTPRequestHandler/HTTPRequestHandler.cpp b/vesper/src/Communication/HTTPRequestHandler/HTTPRequestHandler.cpp
index 52978e7..a9b53d1 100644
--- a/vesper/src/Communication/HTTPRequestHandler/HTTPRequestHandler.cpp
+++ b/vesper/src/Communication/HTTPRequestHandler/HTTPRequestHandler.cpp
@@ -5,6 +5,8 @@
*/
#include "HTTPRequestHandler.hpp"
+
+#define TAG "HTTPHandler"
#include "../CommandHandler/CommandHandler.hpp"
#include "../../ConfigManager/ConfigManager.hpp"
#include "../../Logging/Logging.hpp"
@@ -20,7 +22,7 @@ HTTPRequestHandler::~HTTPRequestHandler() {
}
void HTTPRequestHandler::begin() {
- LOG_INFO("HTTPRequestHandler - Initializing HTTP REST API endpoints");
+ LOG_INFO(TAG, "HTTPRequestHandler - Initializing HTTP REST API endpoints");
// POST /api/command - Execute any command
_server.on("/api/command", HTTP_POST,
@@ -61,15 +63,15 @@ void HTTPRequestHandler::begin() {
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type");
- LOG_INFO("HTTPRequestHandler - REST API endpoints registered");
- LOG_INFO(" POST /api/command - Execute commands");
- LOG_INFO(" GET /api/status - System status");
- LOG_INFO(" GET /api/ping - Health check");
+ LOG_INFO(TAG, "HTTPRequestHandler - REST API endpoints registered");
+ LOG_INFO(TAG, " POST /api/command - Execute commands");
+ LOG_INFO(TAG, " GET /api/status - System status");
+ LOG_INFO(TAG, " GET /api/ping - Health check");
}
void HTTPRequestHandler::setCommandHandlerReference(CommandHandler* handler) {
_commandHandler = handler;
- LOG_DEBUG("HTTPRequestHandler - CommandHandler reference set");
+ LOG_DEBUG(TAG, "HTTPRequestHandler - CommandHandler reference set");
}
bool HTTPRequestHandler::isHealthy() const {
@@ -88,12 +90,12 @@ void HTTPRequestHandler::handleCommandRequest(AsyncWebServerRequest* request, ui
DeserializationError error = deserializeJson(doc, data, len);
if (error) {
- LOG_WARNING("HTTPRequestHandler - JSON parse error: %s", error.c_str());
+ LOG_WARNING(TAG, "HTTPRequestHandler - JSON parse error: %s", error.c_str());
sendErrorResponse(request, 400, "Invalid JSON");
return;
}
- LOG_DEBUG("HTTPRequestHandler - Processing command via HTTP");
+ LOG_DEBUG(TAG, "HTTPRequestHandler - Processing command via HTTP");
// Create message context for HTTP (treat as WebSocket with special ID)
CommandHandler::MessageContext context(CommandHandler::MessageSource::WEBSOCKET, 0xFFFFFFFF);
@@ -129,7 +131,7 @@ void HTTPRequestHandler::handleStatusRequest(AsyncWebServerRequest* request) {
return;
}
- LOG_DEBUG("HTTPRequestHandler - Status request via HTTP");
+ LOG_DEBUG(TAG, "HTTPRequestHandler - Status request via HTTP");
// Create a status command
JsonDocument doc;
@@ -160,7 +162,7 @@ void HTTPRequestHandler::handleStatusRequest(AsyncWebServerRequest* request) {
}
void HTTPRequestHandler::handlePingRequest(AsyncWebServerRequest* request) {
- LOG_DEBUG("HTTPRequestHandler - Ping request via HTTP");
+ LOG_DEBUG(TAG, "HTTPRequestHandler - Ping request via HTTP");
JsonDocument response;
response["status"] = "ok";
diff --git a/vesper/src/Communication/MQTTAsyncClient/MQTTAsyncClient.cpp b/vesper/src/Communication/MQTTAsyncClient/MQTTAsyncClient.cpp
index 6b3a498..fffc7f2 100644
--- a/vesper/src/Communication/MQTTAsyncClient/MQTTAsyncClient.cpp
+++ b/vesper/src/Communication/MQTTAsyncClient/MQTTAsyncClient.cpp
@@ -3,6 +3,8 @@
*/
#include "MQTTAsyncClient.hpp"
+
+#define TAG "MQTTClient"
#include "../../ConfigManager/ConfigManager.hpp"
#include "../../Networking/Networking.hpp"
#include "../../Logging/Logging.hpp"
@@ -66,7 +68,7 @@ MQTTAsyncClient::~MQTTAsyncClient() {
}
void MQTTAsyncClient::begin() {
- LOG_INFO("Initializing MQTT Async Client");
+ LOG_INFO(TAG, "Initializing MQTT Async Client");
auto& mqttConfig = _configManager.getMqttConfig();
@@ -76,7 +78,7 @@ void MQTTAsyncClient::begin() {
_dataTopic = "vesper/" + deviceUID + "/data";
_clientId = "vesper-" + deviceUID;
- LOG_INFO("MQTT Topics: control=%s, data=%s", _controlTopic.c_str(), _dataTopic.c_str());
+ LOG_INFO(TAG, "MQTT Topics: control=%s, data=%s", _controlTopic.c_str(), _dataTopic.c_str());
// Setup event handlers
_mqttClient.onConnect([this](bool sessionPresent) {
@@ -110,7 +112,7 @@ void MQTTAsyncClient::begin() {
_mqttClient.setKeepAlive(15);
_mqttClient.setCleanSession(true);
- LOG_INFO("β
MQTT Async Client initialized");
+ LOG_INFO(TAG, "β
MQTT Async Client initialized");
}
void MQTTAsyncClient::connect() {
@@ -118,28 +120,28 @@ void MQTTAsyncClient::connect() {
// π₯ Check if MQTT is enabled
if (!mqttConfig.enabled) {
- LOG_DEBUG("MQTT is disabled in configuration - skipping connection");
+ LOG_DEBUG(TAG, "MQTT is disabled in configuration - skipping connection");
return;
}
if (_mqttClient.connected()) {
- LOG_DEBUG("Already connected to MQTT");
+ LOG_DEBUG(TAG, "Already connected to MQTT");
return;
}
// Track connection attempt
_lastConnectionAttempt = millis();
- LOG_INFO("Free heap BEFORE MQTT connect: %d bytes", ESP.getFreeHeap());
+ LOG_INFO(TAG, "Free heap BEFORE MQTT connect: %d bytes", ESP.getFreeHeap());
_mqttClient.connect();
- LOG_INFO("MQTT connect() called - waiting for async connection...");
+ LOG_INFO(TAG, "MQTT connect() called - waiting for async connection...");
}
void MQTTAsyncClient::disconnect() {
_mqttClient.disconnect();
- LOG_INFO("Disconnected from MQTT broker");
+ LOG_INFO(TAG, "Disconnected from MQTT broker");
}
uint16_t MQTTAsyncClient::publish(const String& topic, const String& payload, int qos, bool retain) {
@@ -155,7 +157,7 @@ uint16_t MQTTAsyncClient::publish(const String& topic, const String& payload, in
uint16_t packetId = _mqttClient.publish(fullTopic.c_str(), qos, retain, payload.c_str());
if (packetId > 0) {
- LOG_DEBUG("Published to %s: %s (packetId=%d)", fullTopic.c_str(), payload.c_str(), packetId);
+ LOG_DEBUG(TAG, "Published to %s: %s (packetId=%d)", fullTopic.c_str(), payload.c_str(), packetId);
}
// REMOVED: Error logging here to prevent infinite recursion with MQTT logs
@@ -175,11 +177,11 @@ void MQTTAsyncClient::onNetworkConnected() {
// π₯ Only attempt connection if MQTT is enabled
if (!mqttConfig.enabled) {
- LOG_DEBUG("Network connected but MQTT is disabled - skipping MQTT connection");
+ LOG_DEBUG(TAG, "Network connected but MQTT is disabled - skipping MQTT connection");
return;
}
- LOG_DEBUG("Network connected - scheduling MQTT connection after 2s stabilization (non-blocking)");
+ LOG_DEBUG(TAG, "Network connected - scheduling MQTT connection after 2s stabilization (non-blocking)");
// Reset reconnect attempts on fresh network connection
_reconnectAttempts = 0;
@@ -189,14 +191,14 @@ void MQTTAsyncClient::onNetworkConnected() {
if (_networkStabilizationTimer) {
xTimerStart(_networkStabilizationTimer, 0);
} else {
- LOG_ERROR("Network stabilization timer not initialized!");
+ LOG_ERROR(TAG, "Network stabilization timer not initialized!");
// Fallback to immediate connection (better than blocking)
connect();
}
}
void MQTTAsyncClient::onNetworkDisconnected() {
- LOG_DEBUG("Network disconnected - MQTT will auto-reconnect when network returns");
+ LOG_DEBUG(TAG, "Network disconnected - MQTT will auto-reconnect when network returns");
if (_mqttClient.connected()) {
_mqttClient.disconnect(true);
@@ -205,12 +207,12 @@ void MQTTAsyncClient::onNetworkDisconnected() {
void MQTTAsyncClient::subscribe() {
uint16_t packetId = _mqttClient.subscribe(_controlTopic.c_str(), 0);
- LOG_INFO("π¬ Subscribing to control topic: %s (packetId=%d)", _controlTopic.c_str(), packetId);
+ LOG_INFO(TAG, "π¬ Subscribing to control topic: %s (packetId=%d)", _controlTopic.c_str(), packetId);
}
void MQTTAsyncClient::onMqttConnect(bool sessionPresent) {
- LOG_INFO("β
Connected to MQTT broker (session present: %s)", sessionPresent ? "yes" : "no");
- LOG_INFO("π Free heap AFTER MQTT connect: %d bytes", ESP.getFreeHeap());
+ LOG_INFO(TAG, "β
Connected to MQTT broker (session present: %s)", sessionPresent ? "yes" : "no");
+ LOG_INFO(TAG, "π Free heap AFTER MQTT connect: %d bytes", ESP.getFreeHeap());
// Reset reconnection attempts on successful connection
_reconnectAttempts = 0;
@@ -250,14 +252,14 @@ void MQTTAsyncClient::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
break;
}
- LOG_ERROR("β Disconnected from MQTT broker - Reason: %s (%d)", reasonStr, static_cast(reason));
+ LOG_ERROR(TAG, "β Disconnected from MQTT broker - Reason: %s (%d)", reasonStr, static_cast(reason));
// Stop heartbeat timer when disconnected
stopHeartbeat();
// π₯ Don't attempt reconnection if MQTT is disabled
if (!mqttConfig.enabled) {
- LOG_INFO("MQTT is disabled - not attempting reconnection");
+ LOG_INFO(TAG, "MQTT is disabled - not attempting reconnection");
return;
}
@@ -268,24 +270,24 @@ void MQTTAsyncClient::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
// Calculate backoff delay
unsigned long reconnectDelay = getReconnectDelay();
- LOG_INFO("Network still connected - scheduling MQTT reconnection #%d in %lu seconds (backoff active)",
+ LOG_INFO(TAG, "Network still connected - scheduling MQTT reconnection #%d in %lu seconds (backoff active)",
_reconnectAttempts, reconnectDelay / 1000);
// Update timer period with new delay
xTimerChangePeriod(_mqttReconnectTimer, pdMS_TO_TICKS(reconnectDelay), 0);
xTimerStart(_mqttReconnectTimer, 0);
} else {
- LOG_INFO("Network is down - waiting for network to reconnect");
+ LOG_INFO(TAG, "Network is down - waiting for network to reconnect");
}
}
void MQTTAsyncClient::onMqttSubscribe(uint16_t packetId, uint8_t qos) {
- LOG_INFO("β
Subscribed to topic (packetId=%d, QoS=%d)", packetId, qos);
+ LOG_INFO(TAG, "β
Subscribed to topic (packetId=%d, QoS=%d)", packetId, qos);
}
void MQTTAsyncClient::onMqttUnsubscribe(uint16_t packetId) {
- LOG_DEBUG("Unsubscribed from topic (packetId=%d)", packetId);
+ LOG_DEBUG(TAG, "Unsubscribed from topic (packetId=%d)", packetId);
}
void MQTTAsyncClient::onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
@@ -293,7 +295,7 @@ void MQTTAsyncClient::onMqttMessage(char* topic, char* payload, AsyncMqttClientM
String topicStr = String(topic);
String payloadStr = String(payload).substring(0, len);
- LOG_DEBUG("MQTT message received - topic: %s, payload: %s", topicStr.c_str(), payloadStr.c_str());
+ LOG_DEBUG(TAG, "MQTT message received - topic: %s, payload: %s", topicStr.c_str(), payloadStr.c_str());
// Call user callback
if (_messageCallback) {
@@ -302,16 +304,16 @@ void MQTTAsyncClient::onMqttMessage(char* topic, char* payload, AsyncMqttClientM
}
void MQTTAsyncClient::onMqttPublish(uint16_t packetId) {
- LOG_DEBUG("MQTT publish acknowledged (packetId=%d)", packetId);
+ LOG_DEBUG(TAG, "MQTT publish acknowledged (packetId=%d)", packetId);
}
void MQTTAsyncClient::attemptReconnection() {
// Double-check network is still up
if (_networking.isConnected()) {
- LOG_INFO("Attempting MQTT reconnection...");
+ LOG_INFO(TAG, "Attempting MQTT reconnection...");
connect();
} else {
- LOG_WARNING("Network down during reconnect attempt - aborting");
+ LOG_WARNING(TAG, "Network down during reconnect attempt - aborting");
}
}
@@ -331,7 +333,7 @@ void MQTTAsyncClient::mqttReconnectTimerCallback(TimerHandle_t xTimer) {
void MQTTAsyncClient::startHeartbeat() {
if (_heartbeatTimer) {
- LOG_INFO("π Starting MQTT heartbeat (every %d seconds)", HEARTBEAT_INTERVAL / 1000);
+ LOG_INFO(TAG, "π Starting MQTT heartbeat (every %d seconds)", HEARTBEAT_INTERVAL / 1000);
// Publish first heartbeat immediately
publishHeartbeat();
@@ -344,13 +346,13 @@ void MQTTAsyncClient::startHeartbeat() {
void MQTTAsyncClient::stopHeartbeat() {
if (_heartbeatTimer) {
xTimerStop(_heartbeatTimer, 0);
- LOG_INFO("β€οΈ Stopped MQTT heartbeat");
+ LOG_INFO(TAG, "β€οΈ Stopped MQTT heartbeat");
}
}
void MQTTAsyncClient::publishHeartbeat() {
if (!_mqttClient.connected()) {
- LOG_WARNING("β οΈ Cannot publish heartbeat - MQTT not connected");
+ LOG_WARNING(TAG, "β οΈ Cannot publish heartbeat - MQTT not connected");
return;
}
@@ -397,10 +399,10 @@ void MQTTAsyncClient::publishHeartbeat() {
uint16_t packetId = _mqttClient.publish(heartbeatTopic.c_str(), 1, true, heartbeatMessage.c_str());
if (packetId > 0) {
- LOG_DEBUG("π Published heartbeat (retained) - IP: %s, Uptime: %lums",
+ LOG_DEBUG(TAG, "π Published heartbeat (retained) - IP: %s, Uptime: %lums",
_networking.getLocalIP().c_str(), uptimeMs);
} else {
- LOG_ERROR("β Failed to publish heartbeat");
+ LOG_ERROR(TAG, "β Failed to publish heartbeat");
}
}
@@ -415,7 +417,7 @@ void MQTTAsyncClient::heartbeatTimerCallback(TimerHandle_t xTimer) {
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
void MQTTAsyncClient::connectAfterStabilization() {
- LOG_DEBUG("Network stabilization complete - connecting to MQTT");
+ LOG_DEBUG(TAG, "Network stabilization complete - connecting to MQTT");
connect();
}
diff --git a/vesper/src/Communication/ResponseBuilder/ResponseBuilder.cpp b/vesper/src/Communication/ResponseBuilder/ResponseBuilder.cpp
index c88a6d7..5b27256 100644
--- a/vesper/src/Communication/ResponseBuilder/ResponseBuilder.cpp
+++ b/vesper/src/Communication/ResponseBuilder/ResponseBuilder.cpp
@@ -1,4 +1,6 @@
#include "ResponseBuilder.hpp"
+
+#define TAG "ResponseBuilder"
#include "../../Logging/Logging.hpp"
// Static member initialization
@@ -72,7 +74,7 @@ String ResponseBuilder::deviceStatus(PlayerStatus playerStatus, uint32_t timeEla
String result;
serializeJson(statusDoc, result);
- LOG_DEBUG("Device status response: %s", result.c_str());
+ LOG_DEBUG(TAG, "Device status response: %s", result.c_str());
return result;
}
@@ -135,7 +137,7 @@ String ResponseBuilder::buildResponse(Status status, const String& type, const S
String result;
serializeJson(_responseDoc, result);
- LOG_DEBUG("Response built: %s", result.c_str());
+ LOG_DEBUG(TAG, "Response built: %s", result.c_str());
return result;
}
@@ -149,7 +151,7 @@ String ResponseBuilder::buildResponse(Status status, const String& type, const J
String result;
serializeJson(_responseDoc, result);
- LOG_DEBUG("Response built: %s", result.c_str());
+ LOG_DEBUG(TAG, "Response built: %s", result.c_str());
return result;
}
diff --git a/vesper/src/Communication/UARTCommandHandler/UARTCommandHandler.cpp b/vesper/src/Communication/UARTCommandHandler/UARTCommandHandler.cpp
index e9bee20..46a8425 100644
--- a/vesper/src/Communication/UARTCommandHandler/UARTCommandHandler.cpp
+++ b/vesper/src/Communication/UARTCommandHandler/UARTCommandHandler.cpp
@@ -3,6 +3,8 @@
*/
#include "UARTCommandHandler.hpp"
+
+#define TAG "UARTHandler"
#include "../../Logging/Logging.hpp"
UARTCommandHandler::UARTCommandHandler(uint8_t txPin, uint8_t rxPin, uint32_t baudRate)
@@ -24,10 +26,10 @@ UARTCommandHandler::~UARTCommandHandler() {
}
void UARTCommandHandler::begin() {
- LOG_INFO("Initializing UART Command Handler");
- LOG_INFO(" TX Pin: GPIO%d", _txPin);
- LOG_INFO(" RX Pin: GPIO%d", _rxPin);
- LOG_INFO(" Baud Rate: %u", _baudRate);
+ LOG_INFO(TAG, "Initializing UART Command Handler");
+ LOG_INFO(TAG, " TX Pin: GPIO%d", _txPin);
+ LOG_INFO(TAG, " RX Pin: GPIO%d", _rxPin);
+ LOG_INFO(TAG, " Baud Rate: %u", _baudRate);
// Initialize Serial2 with custom pins
_serial.begin(_baudRate, SERIAL_8N1, _rxPin, _txPin);
@@ -38,7 +40,7 @@ void UARTCommandHandler::begin() {
}
_ready = true;
- LOG_INFO("UART Command Handler ready");
+ LOG_INFO(TAG, "UART Command Handler ready");
}
void UARTCommandHandler::loop() {
@@ -65,7 +67,7 @@ void UARTCommandHandler::loop() {
_buffer[_bufferIndex++] = c;
} else {
// Buffer overflow - discard and reset
- LOG_ERROR("UART buffer overflow, discarding message");
+ LOG_ERROR(TAG, "UART buffer overflow, discarding message");
_errorCount++;
resetBuffer();
}
@@ -78,7 +80,7 @@ void UARTCommandHandler::setCallback(MessageCallback callback) {
void UARTCommandHandler::send(const String& response) {
if (!_ready) {
- LOG_ERROR("UART not ready, cannot send response");
+ LOG_ERROR(TAG, "UART not ready, cannot send response");
return;
}
@@ -86,11 +88,11 @@ void UARTCommandHandler::send(const String& response) {
_serial.print('\n'); // Newline delimiter
_serial.flush(); // Ensure data is sent
- LOG_DEBUG("UART TX: %s", response.c_str());
+ LOG_DEBUG(TAG, "UART TX: %s", response.c_str());
}
void UARTCommandHandler::processLine(const char* line) {
- LOG_DEBUG("UART RX: %s", line);
+ LOG_DEBUG(TAG, "UART RX: %s", line);
// Skip empty lines or whitespace-only
if (strlen(line) == 0) return;
@@ -100,7 +102,7 @@ void UARTCommandHandler::processLine(const char* line) {
DeserializationError error = deserializeJson(doc, line);
if (error) {
- LOG_ERROR("UART JSON parse error: %s", error.c_str());
+ LOG_ERROR(TAG, "UART JSON parse error: %s", error.c_str());
_errorCount++;
// Send error response back
@@ -121,7 +123,7 @@ void UARTCommandHandler::processLine(const char* line) {
if (_callback) {
_callback(doc);
} else {
- LOG_WARNING("UART message received but no callback set");
+ LOG_WARNING(TAG, "UART message received but no callback set");
}
}
diff --git a/vesper/src/Communication/WebSocketServer/WebSocketServer.cpp b/vesper/src/Communication/WebSocketServer/WebSocketServer.cpp
index 7fa8420..6c477a2 100644
--- a/vesper/src/Communication/WebSocketServer/WebSocketServer.cpp
+++ b/vesper/src/Communication/WebSocketServer/WebSocketServer.cpp
@@ -3,6 +3,8 @@
*/
#include "WebSocketServer.hpp"
+
+#define TAG "WebSocket"
#include "../../Logging/Logging.hpp"
#include "../ResponseBuilder/ResponseBuilder.hpp"
@@ -23,7 +25,7 @@ WebSocketServer::~WebSocketServer() {
void WebSocketServer::begin() {
_webSocket.onEvent(onEvent);
- LOG_INFO("WebSocket server initialized on /ws");
+ LOG_INFO(TAG, "WebSocket server initialized on /ws");
// π₯ CRITICAL: This line was missing - attach WebSocket to the AsyncWebServer
// Without this, the server doesn't know about the WebSocket handler!
@@ -40,17 +42,17 @@ void WebSocketServer::sendToClient(uint32_t clientId, const String& message) {
void WebSocketServer::broadcastToAll(const String& message) {
_clientManager.broadcastToAll(message);
- LOG_DEBUG("Broadcast to all WebSocket clients: %s", message.c_str());
+ LOG_DEBUG(TAG, "Broadcast to all WebSocket clients: %s", message.c_str());
}
void WebSocketServer::broadcastToMaster(const String& message) {
_clientManager.sendToMasterClients(message);
- LOG_DEBUG("Broadcast to master clients: %s", message.c_str());
+ LOG_DEBUG(TAG, "Broadcast to master clients: %s", message.c_str());
}
void WebSocketServer::broadcastToSecondary(const String& message) {
_clientManager.sendToSecondaryClients(message);
- LOG_DEBUG("Broadcast to secondary clients: %s", message.c_str());
+ LOG_DEBUG(TAG, "Broadcast to secondary clients: %s", message.c_str());
}
bool WebSocketServer::hasClients() const {
@@ -64,7 +66,7 @@ size_t WebSocketServer::getClientCount() const {
void WebSocketServer::onEvent(AsyncWebSocket* server, AsyncWebSocketClient* client,
AwsEventType type, void* arg, uint8_t* data, size_t len) {
if (!_instance) {
- LOG_ERROR("WebSocketServer static instance is NULL - callback ignored!");
+ LOG_ERROR(TAG, "WebSocketServer static instance is NULL - callback ignored!");
return;
}
@@ -82,7 +84,7 @@ void WebSocketServer::onEvent(AsyncWebSocket* server, AsyncWebSocketClient* clie
break;
case WS_EVT_ERROR:
- LOG_ERROR("WebSocket client #%u error(%u): %s",
+ LOG_ERROR(TAG, "WebSocket client #%u error(%u): %s",
client->id(), *((uint16_t*)arg), (char*)data);
break;
@@ -92,7 +94,7 @@ void WebSocketServer::onEvent(AsyncWebSocket* server, AsyncWebSocketClient* clie
}
void WebSocketServer::onConnect(AsyncWebSocketClient* client) {
- LOG_INFO("WebSocket client #%u connected from %s",
+ LOG_INFO(TAG, "WebSocket client #%u connected from %s",
client->id(), client->remoteIP().toString().c_str());
// Add client to manager (type UNKNOWN until they identify)
@@ -104,7 +106,7 @@ void WebSocketServer::onConnect(AsyncWebSocketClient* client) {
}
void WebSocketServer::onDisconnect(AsyncWebSocketClient* client) {
- LOG_INFO("WebSocket client #%u disconnected", client->id());
+ LOG_INFO(TAG, "WebSocket client #%u disconnected", client->id());
_clientManager.removeClient(client->id());
_clientManager.cleanupDisconnectedClients();
@@ -118,7 +120,7 @@ void WebSocketServer::onData(AsyncWebSocketClient* client, void* arg, uint8_t* d
// Allocate buffer for payload
char* payload = (char*)malloc(len + 1);
if (!payload) {
- LOG_ERROR("Failed to allocate memory for WebSocket payload");
+ LOG_ERROR(TAG, "Failed to allocate memory for WebSocket payload");
String errorResponse = ResponseBuilder::error("memory_error", "Out of memory");
_clientManager.sendToClient(client->id(), errorResponse);
return;
@@ -127,14 +129,14 @@ void WebSocketServer::onData(AsyncWebSocketClient* client, void* arg, uint8_t* d
memcpy(payload, data, len);
payload[len] = '\0';
- LOG_DEBUG("WebSocket client #%u sent: %s", client->id(), payload);
+ LOG_DEBUG(TAG, "WebSocket client #%u sent: %s", client->id(), payload);
// Parse JSON
StaticJsonDocument<2048> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
- LOG_ERROR("Failed to parse WebSocket JSON from client #%u: %s", client->id(), error.c_str());
+ LOG_ERROR(TAG, "Failed to parse WebSocket JSON from client #%u: %s", client->id(), error.c_str());
String errorResponse = ResponseBuilder::error("parse_error", "Invalid JSON");
_clientManager.sendToClient(client->id(), errorResponse);
} else {
@@ -143,15 +145,15 @@ void WebSocketServer::onData(AsyncWebSocketClient* client, void* arg, uint8_t* d
// Call user callback if set
if (_messageCallback) {
- LOG_DEBUG("Routing message from client #%u to callback handler", client->id());
+ LOG_DEBUG(TAG, "Routing message from client #%u to callback handler", client->id());
_messageCallback(client->id(), doc);
} else {
- LOG_WARNING("WebSocket message received but no callback handler is set!");
+ LOG_WARNING(TAG, "WebSocket message received but no callback handler is set!");
}
}
free(payload);
} else {
- LOG_WARNING("Received fragmented or non-text WebSocket message from client #%u - ignoring", client->id());
+ LOG_WARNING(TAG, "Received fragmented or non-text WebSocket message from client #%u - ignoring", client->id());
}
}
diff --git a/vesper/src/ConfigManager/ConfigManager.cpp b/vesper/src/ConfigManager/ConfigManager.cpp
index 7d192a4..4d3e9f7 100644
--- a/vesper/src/ConfigManager/ConfigManager.cpp
+++ b/vesper/src/ConfigManager/ConfigManager.cpp
@@ -1,4 +1,6 @@
#include "ConfigManager.hpp"
+
+#define TAG "ConfigManager"
#include "../../src/Logging/Logging.hpp"
#include // For MAC address generation
#include // For timestamp generation
@@ -27,7 +29,7 @@ void ConfigManager::initializeCleanDefaults() {
// Set MQTT user to deviceUID for unique identification
mqttConfig.user = deviceConfig.deviceUID;
- LOG_DEBUG("ConfigManager - Clean defaults initialized with auto-generated identifiers");
+ LOG_DEBUG(TAG, "ConfigManager - Clean defaults initialized with auto-generated identifiers");
}
void ConfigManager::generateNetworkIdentifiers() {
@@ -36,7 +38,7 @@ void ConfigManager::generateNetworkIdentifiers() {
networkConfig.apSsid = "BellSystems-Setup-" + deviceConfig.deviceUID;
- LOG_DEBUG("ConfigManager - Generated hostname: %s, AP SSID: %s",
+ LOG_DEBUG(TAG, "ConfigManager - Generated hostname: %s, AP SSID: %s",
networkConfig.hostname.c_str(), networkConfig.apSsid.c_str());
}
@@ -49,11 +51,11 @@ void ConfigManager::createDefaultBellConfig() {
}
bool ConfigManager::begin() {
- LOG_INFO("ConfigManager - β
Initializing...");
+ LOG_INFO(TAG, "ConfigManager - β
Initializing...");
// Step 1: Initialize NVS for device identity (factory-set, permanent)
if (!initializeNVS()) {
- LOG_ERROR("ConfigManager - β NVS initialization failed, using empty defaults");
+ LOG_ERROR(TAG, "ConfigManager - β NVS initialization failed, using empty defaults");
} else {
// Load device identity from NVS (deviceUID, hwType, hwVersion)
loadDeviceIdentityFromNVS();
@@ -64,58 +66,58 @@ bool ConfigManager::begin() {
// Step 3: Initialize SD card for user-configurable settings
if (!ensureSDCard()) {
- LOG_ERROR("ConfigManager - β SD Card initialization failed, using defaults");
+ LOG_ERROR(TAG, "ConfigManager - β SD Card initialization failed, using defaults");
return false;
}
// Step 5: Load update servers list
if (!loadUpdateServers()) {
- LOG_WARNING("ConfigManager - β οΈ Could not load update servers - using fallback only");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Could not load update servers - using fallback only");
}
// Load network config, save defaults if not found
if (!loadNetworkConfig()) {
- LOG_WARNING("ConfigManager - β οΈ Creating default network config file");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Creating default network config file");
saveNetworkConfig();
}
// Load time config, save defaults if not found
if (!loadTimeConfig()) {
- LOG_WARNING("ConfigManager - β οΈ Creating default time config file (GMT+2)");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Creating default time config file (GMT+2)");
saveTimeConfig();
}
// Load bell durations, save defaults if not found
if (!loadBellDurations()) {
- LOG_WARNING("ConfigManager - β οΈ Creating default bell durations file");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Creating default bell durations file");
saveBellDurations();
}
// Load bell outputs, save defaults if not found
if (!loadBellOutputs()) {
- LOG_WARNING("ConfigManager - β οΈ Creating default bell outputs file");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Creating default bell outputs file");
saveBellOutputs();
}
// Load clock config, save defaults if not found
if (!loadClockConfig()) {
- LOG_WARNING("ConfigManager - β οΈ Creating default clock config file");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Creating default clock config file");
saveClockConfig();
}
// Load clock state, save defaults if not found
if (!loadClockState()) {
- LOG_WARNING("ConfigManager - β οΈ Creating default clock state file");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Creating default clock state file");
saveClockState();
}
if (!loadGeneralConfig()) {
- LOG_WARNING("ConfigManager - β οΈ Creating default general config file");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Creating default general config file");
saveGeneralConfig();
}
- LOG_INFO("ConfigManager - β
Initialization Complete ! UID: %s, Hostname: %s",
+ LOG_INFO(TAG, "ConfigManager - β
Initialization Complete ! UID: %s, Hostname: %s",
deviceConfig.deviceUID.c_str(), networkConfig.hostname.c_str());
return true;
}
@@ -127,29 +129,29 @@ bool ConfigManager::begin() {
bool ConfigManager::initializeNVS() {
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
- LOG_WARNING("ConfigManager - β οΈ NVS partition truncated, erasing...");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ NVS partition truncated, erasing...");
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
if (err != ESP_OK) {
- LOG_ERROR("ConfigManager - β Failed to initialize NVS flash: %s", esp_err_to_name(err));
+ LOG_ERROR(TAG, "ConfigManager - β Failed to initialize NVS flash: %s", esp_err_to_name(err));
return false;
}
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvsHandle);
if (err != ESP_OK) {
- LOG_ERROR("ConfigManager - β Failed to open NVS handle: %s", esp_err_to_name(err));
+ LOG_ERROR(TAG, "ConfigManager - β Failed to open NVS handle: %s", esp_err_to_name(err));
return false;
}
- LOG_DEBUG("ConfigManager - NVS initialized successfully");
+ LOG_DEBUG(TAG, "ConfigManager - NVS initialized successfully");
return true;
}
bool ConfigManager::loadDeviceIdentityFromNVS() {
if (nvsHandle == 0) {
- LOG_ERROR("ConfigManager - β NVS not initialized, cannot load device identity");
+ LOG_ERROR(TAG, "ConfigManager - β NVS not initialized, cannot load device identity");
return false;
}
@@ -160,21 +162,21 @@ bool ConfigManager::loadDeviceIdentityFromNVS() {
// Validate that factory identity exists
if (deviceConfig.deviceUID.isEmpty() || deviceConfig.hwType.isEmpty() || deviceConfig.hwVersion.isEmpty()) {
- LOG_ERROR("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
- LOG_ERROR(" β οΈ CRITICAL: DEVICE IDENTITY NOT FOUND IN NVS");
- LOG_ERROR(" β οΈ This device has NOT been factory-programmed!");
- LOG_ERROR(" β οΈ Please flash factory firmware to set device identity");
- LOG_ERROR("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
+ LOG_ERROR(TAG, "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
+ LOG_ERROR(TAG, " β οΈ CRITICAL: DEVICE IDENTITY NOT FOUND IN NVS");
+ LOG_ERROR(TAG, " β οΈ This device has NOT been factory-programmed!");
+ LOG_ERROR(TAG, " β οΈ Please flash factory firmware to set device identity");
+ LOG_ERROR(TAG, "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
return false;
}
- LOG_INFO("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
- LOG_INFO(" π FACTORY DEVICE IDENTITY LOADED FROM NVS (READ-ONLY)");
- LOG_INFO(" π Device UID: %s", deviceConfig.deviceUID.c_str());
- LOG_INFO(" π§ Hardware Type: %s", deviceConfig.hwType.c_str());
- LOG_INFO(" π Hardware Version: %s", deviceConfig.hwVersion.c_str());
- LOG_INFO(" π These values are PERMANENT and cannot be changed by production firmware");
- LOG_INFO("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
+ LOG_INFO(TAG, "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
+ LOG_INFO(TAG, " π FACTORY DEVICE IDENTITY LOADED FROM NVS (READ-ONLY)");
+ LOG_INFO(TAG, " π Device UID: %s", deviceConfig.deviceUID.c_str());
+ LOG_INFO(TAG, " π§ Hardware Type: %s", deviceConfig.hwType.c_str());
+ LOG_INFO(TAG, " π Hardware Version: %s", deviceConfig.hwVersion.c_str());
+ LOG_INFO(TAG, " π These values are PERMANENT and cannot be changed by production firmware");
+ LOG_INFO(TAG, "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
return true;
}
@@ -185,7 +187,7 @@ bool ConfigManager::loadDeviceIdentityFromNVS() {
String ConfigManager::readNVSString(const char* key, const String& defaultValue) {
if (nvsHandle == 0) {
- LOG_ERROR("ConfigManager - β NVS not initialized, returning default for key: %s", key);
+ LOG_ERROR(TAG, "ConfigManager - β NVS not initialized, returning default for key: %s", key);
return defaultValue;
}
@@ -193,12 +195,12 @@ String ConfigManager::readNVSString(const char* key, const String& defaultValue)
esp_err_t err = nvs_get_str(nvsHandle, key, NULL, &required_size);
if (err == ESP_ERR_NVS_NOT_FOUND) {
- LOG_DEBUG("ConfigManager - NVS key '%s' not found, using default: %s", key, defaultValue.c_str());
+ LOG_DEBUG(TAG, "ConfigManager - NVS key '%s' not found, using default: %s", key, defaultValue.c_str());
return defaultValue;
}
if (err != ESP_OK) {
- LOG_ERROR("ConfigManager - β Error reading NVS key '%s': %s", key, esp_err_to_name(err));
+ LOG_ERROR(TAG, "ConfigManager - β Error reading NVS key '%s': %s", key, esp_err_to_name(err));
return defaultValue;
}
@@ -206,7 +208,7 @@ String ConfigManager::readNVSString(const char* key, const String& defaultValue)
err = nvs_get_str(nvsHandle, key, buffer, &required_size);
if (err != ESP_OK) {
- LOG_ERROR("ConfigManager - β Error reading NVS value for key '%s': %s", key, esp_err_to_name(err));
+ LOG_ERROR(TAG, "ConfigManager - β Error reading NVS value for key '%s': %s", key, esp_err_to_name(err));
delete[] buffer;
return defaultValue;
}
@@ -214,7 +216,7 @@ String ConfigManager::readNVSString(const char* key, const String& defaultValue)
String result = String(buffer);
delete[] buffer;
- LOG_VERBOSE("ConfigManager - Read NVS key '%s': %s", key, result.c_str());
+ LOG_VERBOSE(TAG, "ConfigManager - Read NVS key '%s': %s", key, result.c_str());
return result;
}
@@ -226,7 +228,7 @@ bool ConfigManager::ensureSDCard() {
if (!sdInitialized) {
sdInitialized = SD.begin(hardwareConfig.sdChipSelect);
if (!sdInitialized) {
- LOG_ERROR("ConfigManager - β SD Card not available");
+ LOG_ERROR(TAG, "ConfigManager - β SD Card not available");
}
}
return sdInitialized;
@@ -258,12 +260,12 @@ bool ConfigManager::saveDeviceConfig() {
size_t len = serializeJson(doc, buffer, sizeof(buffer));
if (len == 0 || len >= sizeof(buffer)) {
- LOG_ERROR("ConfigManager - β Failed to serialize device config JSON");
+ LOG_ERROR(TAG, "ConfigManager - β Failed to serialize device config JSON");
return false;
}
saveFileToSD("/settings", "deviceConfig.json", buffer);
- LOG_DEBUG("ConfigManager - Device config saved - FwVer: %s", deviceConfig.fwVersion.c_str());
+ LOG_DEBUG(TAG, "ConfigManager - Device config saved - FwVer: %s", deviceConfig.fwVersion.c_str());
return true;
}
@@ -272,7 +274,7 @@ bool ConfigManager::loadDeviceConfig() {
File file = SD.open("/settings/deviceConfig.json", FILE_READ);
if (!file) {
- LOG_WARNING("ConfigManager - β οΈ Device config file not found - using firmware version default");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Device config file not found - using firmware version default");
return false;
}
@@ -281,13 +283,13 @@ bool ConfigManager::loadDeviceConfig() {
file.close();
if (error) {
- LOG_ERROR("ConfigManager - β Failed to parse device config from SD: %s", error.c_str());
+ LOG_ERROR(TAG, "ConfigManager - β Failed to parse device config from SD: %s", error.c_str());
return false;
}
if (doc.containsKey("fwVersion")) {
deviceConfig.fwVersion = doc["fwVersion"].as();
- LOG_VERBOSE("ConfigManager - Firmware version loaded from SD: %s", deviceConfig.fwVersion.c_str());
+ LOG_VERBOSE(TAG, "ConfigManager - Firmware version loaded from SD: %s", deviceConfig.fwVersion.c_str());
}
return true;
@@ -295,22 +297,22 @@ bool ConfigManager::loadDeviceConfig() {
bool ConfigManager::isHealthy() const {
if (!sdInitialized) {
- LOG_VERBOSE("ConfigManager - β οΈ Unhealthy - SD card not initialized");
+ LOG_VERBOSE(TAG, "ConfigManager - β οΈ Unhealthy - SD card not initialized");
return false;
}
if (deviceConfig.deviceUID.isEmpty()) {
- LOG_VERBOSE("ConfigManager - β οΈ Unhealthy - Device UID not set (factory configuration required)");
+ LOG_VERBOSE(TAG, "ConfigManager - β οΈ Unhealthy - Device UID not set (factory configuration required)");
return false;
}
if (deviceConfig.hwType.isEmpty()) {
- LOG_VERBOSE("ConfigManager - β οΈ Unhealthy - Hardware type not set (factory configuration required)");
+ LOG_VERBOSE(TAG, "ConfigManager - β οΈ Unhealthy - Hardware type not set (factory configuration required)");
return false;
}
if (networkConfig.hostname.isEmpty()) {
- LOG_VERBOSE("ConfigManager - β οΈ Unhealthy - Hostname not generated (initialization issue)");
+ LOG_VERBOSE(TAG, "ConfigManager - β οΈ Unhealthy - Hostname not generated (initialization issue)");
return false;
}
@@ -327,7 +329,7 @@ bool ConfigManager::loadBellDurations() {
File file = SD.open("/settings/relayTimings.json", FILE_READ);
if (!file) {
- LOG_WARNING("ConfigManager - β οΈ Settings file not found on SD. Using default bell durations.");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Settings file not found on SD. Using default bell durations.");
return false;
}
@@ -336,7 +338,7 @@ bool ConfigManager::loadBellDurations() {
file.close();
if (error) {
- LOG_ERROR("ConfigManager - β Failed to parse settings from SD. Using default bell durations.");
+ LOG_ERROR(TAG, "ConfigManager - β Failed to parse settings from SD. Using default bell durations.");
return false;
}
@@ -347,7 +349,7 @@ bool ConfigManager::loadBellDurations() {
}
}
- LOG_DEBUG("ConfigManager - Bell durations loaded from SD");
+ LOG_DEBUG(TAG, "ConfigManager - Bell durations loaded from SD");
return true;
}
@@ -364,12 +366,12 @@ bool ConfigManager::saveBellDurations() {
size_t len = serializeJson(doc, buffer, sizeof(buffer));
if (len == 0 || len >= sizeof(buffer)) {
- LOG_ERROR("ConfigManager - β Failed to serialize bell durations JSON");
+ LOG_ERROR(TAG, "ConfigManager - β Failed to serialize bell durations JSON");
return false;
}
saveFileToSD("/settings", "relayTimings.json", buffer);
- LOG_DEBUG("ConfigManager - Bell durations saved to SD");
+ LOG_DEBUG(TAG, "ConfigManager - Bell durations saved to SD");
return true;
}
@@ -380,7 +382,7 @@ bool ConfigManager::loadBellOutputs() {
File file = SD.open("/settings/bellOutputs.json", FILE_READ);
if (!file) {
- LOG_WARNING("ConfigManager - β οΈ Bell outputs file not found on SD. Using default 1:1 mapping.");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Bell outputs file not found on SD. Using default 1:1 mapping.");
return false;
}
@@ -389,7 +391,7 @@ bool ConfigManager::loadBellOutputs() {
file.close();
if (error) {
- LOG_ERROR("ConfigManager - β Failed to parse bell outputs from SD. Using defaults.");
+ LOG_ERROR(TAG, "ConfigManager - β Failed to parse bell outputs from SD. Using defaults.");
return false;
}
@@ -400,7 +402,7 @@ bool ConfigManager::loadBellOutputs() {
}
}
- LOG_DEBUG("ConfigManager - Bell outputs loaded from SD");
+ LOG_DEBUG(TAG, "ConfigManager - Bell outputs loaded from SD");
return true;
}
@@ -417,12 +419,12 @@ bool ConfigManager::saveBellOutputs() {
size_t len = serializeJson(doc, buffer, sizeof(buffer));
if (len == 0 || len >= sizeof(buffer)) {
- LOG_ERROR("ConfigManager - β Failed to serialize bell outputs JSON");
+ LOG_ERROR(TAG, "ConfigManager - β Failed to serialize bell outputs JSON");
return false;
}
saveFileToSD("/settings", "bellOutputs.json", buffer);
- LOG_DEBUG("ConfigManager - Bell outputs saved to SD");
+ LOG_DEBUG(TAG, "ConfigManager - Bell outputs saved to SD");
return true;
}
@@ -433,7 +435,7 @@ void ConfigManager::updateBellDurations(JsonVariant doc) {
bellConfig.durations[i] = doc[key].as();
}
}
- LOG_DEBUG("ConfigManager - Updated bell durations");
+ LOG_DEBUG(TAG, "ConfigManager - Updated bell durations");
}
void ConfigManager::updateBellOutputs(JsonVariant doc) {
@@ -441,10 +443,10 @@ void ConfigManager::updateBellOutputs(JsonVariant doc) {
String key = String("b") + (i + 1);
if (doc.containsKey(key)) {
bellConfig.outputs[i] = doc[key].as() - 1;
- LOG_VERBOSE("ConfigManager - Bell %d output set to %d", i + 1, bellConfig.outputs[i]);
+ LOG_VERBOSE(TAG, "ConfigManager - Bell %d output set to %d", i + 1, bellConfig.outputs[i]);
}
}
- LOG_DEBUG("ConfigManager - Updated bell outputs");
+ LOG_DEBUG(TAG, "ConfigManager - Updated bell outputs");
}
@@ -488,13 +490,13 @@ void ConfigManager::saveFileToSD(const char* dirPath, const char* filename, cons
File file = SD.open(fullPath.c_str(), FILE_WRITE);
if (!file) {
- LOG_ERROR("ConfigManager - β Failed to open file: %s", fullPath.c_str());
+ LOG_ERROR(TAG, "ConfigManager - β Failed to open file: %s", fullPath.c_str());
return;
}
file.print(data);
file.close();
- LOG_VERBOSE("ConfigManager - File %s saved successfully", fullPath.c_str());
+ LOG_VERBOSE(TAG, "ConfigManager - File %s saved successfully", fullPath.c_str());
}
// Clock configuration methods and other remaining methods follow the same pattern...
@@ -517,7 +519,7 @@ void ConfigManager::updateClockOutputs(JsonVariant doc) {
if (doc.containsKey("pauseDuration")) {
clockConfig.pauseDuration = doc["pauseDuration"].as();
}
- LOG_DEBUG("ConfigManager - Updated Clock outputs to: C1: %d / C2: %d, Pulse: %dms, Pause: %dms",
+ LOG_DEBUG(TAG, "ConfigManager - Updated Clock outputs to: C1: %d / C2: %d, Pulse: %dms, Pause: %dms",
clockConfig.c1output, clockConfig.c2output, clockConfig.pulseDuration, clockConfig.pauseDuration);
}
@@ -543,7 +545,7 @@ void ConfigManager::updateClockAlerts(JsonVariant doc) {
// Convert from 1-based (API) to 0-based (internal), or keep 255 (disabled)
clockConfig.quarterBell = (bellNum == 255) ? 255 : bellNum - 1;
}
- LOG_DEBUG("ConfigManager - Updated Clock alerts");
+ LOG_DEBUG(TAG, "ConfigManager - Updated Clock alerts");
}
void ConfigManager::updateClockBacklight(JsonVariant doc) {
@@ -559,7 +561,7 @@ void ConfigManager::updateClockBacklight(JsonVariant doc) {
if (doc.containsKey("offTime")) {
clockConfig.backlightOffTime = doc["offTime"].as();
}
- LOG_DEBUG("ConfigManager - Updated Clock backlight");
+ LOG_DEBUG(TAG, "ConfigManager - Updated Clock backlight");
}
void ConfigManager::updateClockSilence(JsonVariant doc) {
@@ -587,7 +589,7 @@ void ConfigManager::updateClockSilence(JsonVariant doc) {
clockConfig.nighttimeSilenceOffTime = nighttime["offTime"].as();
}
}
- LOG_DEBUG("ConfigManager - Updated Clock silence");
+ LOG_DEBUG(TAG, "ConfigManager - Updated Clock silence");
}
bool ConfigManager::loadClockConfig() {
@@ -595,7 +597,7 @@ bool ConfigManager::loadClockConfig() {
File file = SD.open("/settings/clockConfig.json", FILE_READ);
if (!file) {
- LOG_WARNING("ConfigManager - β οΈ Clock config file not found - using defaults");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Clock config file not found - using defaults");
return false;
}
@@ -604,7 +606,7 @@ bool ConfigManager::loadClockConfig() {
file.close();
if (error) {
- LOG_ERROR("ConfigManager - β Failed to parse clock config from SD: %s", error.c_str());
+ LOG_ERROR(TAG, "ConfigManager - β Failed to parse clock config from SD: %s", error.c_str());
return false;
}
@@ -636,7 +638,7 @@ bool ConfigManager::loadClockConfig() {
if (doc.containsKey("nighttimeSilenceOnTime")) clockConfig.nighttimeSilenceOnTime = doc["nighttimeSilenceOnTime"].as();
if (doc.containsKey("nighttimeSilenceOffTime")) clockConfig.nighttimeSilenceOffTime = doc["nighttimeSilenceOffTime"].as();
- LOG_DEBUG("ConfigManager - Clock config loaded");
+ LOG_DEBUG(TAG, "ConfigManager - Clock config loaded");
return true;
}
@@ -677,12 +679,12 @@ bool ConfigManager::saveClockConfig() {
size_t len = serializeJson(doc, buffer, sizeof(buffer));
if (len == 0 || len >= sizeof(buffer)) {
- LOG_ERROR("ConfigManager - β Failed to serialize clock config JSON");
+ LOG_ERROR(TAG, "ConfigManager - β Failed to serialize clock config JSON");
return false;
}
saveFileToSD("/settings", "clockConfig.json", buffer);
- LOG_DEBUG("ConfigManager - Clock config saved");
+ LOG_DEBUG(TAG, "ConfigManager - Clock config saved");
return true;
}
@@ -691,7 +693,7 @@ bool ConfigManager::loadClockState() {
File file = SD.open("/settings/clockState.json", FILE_READ);
if (!file) {
- LOG_WARNING("ConfigManager - β οΈ Clock state file not found - using defaults");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Clock state file not found - using defaults");
clockConfig.physicalHour = 0;
clockConfig.physicalMinute = 0;
clockConfig.nextOutputIsC1 = true;
@@ -704,7 +706,7 @@ bool ConfigManager::loadClockState() {
file.close();
if (error) {
- LOG_ERROR("ConfigManager - β Failed to parse clock state from SD: %s", error.c_str());
+ LOG_ERROR(TAG, "ConfigManager - β Failed to parse clock state from SD: %s", error.c_str());
return false;
}
@@ -713,7 +715,7 @@ bool ConfigManager::loadClockState() {
clockConfig.nextOutputIsC1 = doc["nextIsC1"].as();
clockConfig.lastSyncTime = doc["lastSyncTime"].as();
- LOG_DEBUG("ConfigManager - Clock state loaded");
+ LOG_DEBUG(TAG, "ConfigManager - Clock state loaded");
return true;
}
@@ -730,12 +732,12 @@ bool ConfigManager::saveClockState() {
size_t len = serializeJson(doc, buffer, sizeof(buffer));
if (len == 0 || len >= sizeof(buffer)) {
- LOG_ERROR("ConfigManager - β Failed to serialize clock state JSON");
+ LOG_ERROR(TAG, "ConfigManager - β Failed to serialize clock state JSON");
return false;
}
saveFileToSD("/settings", "clockState.json", buffer);
- LOG_VERBOSE("ConfigManager - Clock state saved");
+ LOG_VERBOSE(TAG, "ConfigManager - Clock state saved");
return true;
}
@@ -744,7 +746,7 @@ bool ConfigManager::loadUpdateServers() {
File file = SD.open("/settings/updateServers.json", FILE_READ);
if (!file) {
- LOG_DEBUG("ConfigManager - Update servers file not found - using fallback only");
+ LOG_DEBUG(TAG, "ConfigManager - Update servers file not found - using fallback only");
return false;
}
@@ -753,7 +755,7 @@ bool ConfigManager::loadUpdateServers() {
file.close();
if (error) {
- LOG_ERROR("ConfigManager - β Failed to parse update servers JSON: %s", error.c_str());
+ LOG_ERROR(TAG, "ConfigManager - β Failed to parse update servers JSON: %s", error.c_str());
return false;
}
@@ -770,7 +772,7 @@ bool ConfigManager::loadUpdateServers() {
}
}
- LOG_DEBUG("ConfigManager - Loaded %d update servers from SD card", updateServers.size());
+ LOG_DEBUG(TAG, "ConfigManager - Loaded %d update servers from SD card", updateServers.size());
return true;
}
@@ -787,7 +789,7 @@ void ConfigManager::updateTimeConfig(long gmtOffsetSec, int daylightOffsetSec) {
timeConfig.gmtOffsetSec = gmtOffsetSec;
timeConfig.daylightOffsetSec = daylightOffsetSec;
saveTimeConfig(); // Save time config specifically
- LOG_DEBUG("ConfigManager - TimeConfig updated - GMT offset %ld sec, DST offset %d sec",
+ LOG_DEBUG(TAG, "ConfigManager - TimeConfig updated - GMT offset %ld sec, DST offset %d sec",
gmtOffsetSec, daylightOffsetSec);
}
@@ -801,7 +803,7 @@ void ConfigManager::updateNetworkConfig(const String& hostname, bool useStaticIP
networkConfig.dns1 = dns1;
networkConfig.dns2 = dns2;
saveNetworkConfig(); // Save immediately to SD
- LOG_DEBUG("ConfigManager - NetworkConfig updated - Hostname: %s, Static IP: %s, IP: %s",
+ LOG_DEBUG(TAG, "ConfigManager - NetworkConfig updated - Hostname: %s, Static IP: %s, IP: %s",
hostname.c_str(), useStaticIP ? "enabled" : "disabled", ip.toString().c_str());
}
@@ -814,7 +816,7 @@ bool ConfigManager::loadNetworkConfig() {
File file = SD.open("/settings/networkConfig.json", FILE_READ);
if (!file) {
- LOG_DEBUG("ConfigManager - Network config file not found - using auto-generated hostname and DHCP");
+ LOG_DEBUG(TAG, "ConfigManager - Network config file not found - using auto-generated hostname and DHCP");
return false;
}
@@ -823,7 +825,7 @@ bool ConfigManager::loadNetworkConfig() {
file.close();
if (error) {
- LOG_ERROR("ConfigManager - β Failed to parse network config from SD: %s", error.c_str());
+ LOG_ERROR(TAG, "ConfigManager - β Failed to parse network config from SD: %s", error.c_str());
return false;
}
@@ -832,7 +834,7 @@ bool ConfigManager::loadNetworkConfig() {
String customHostname = doc["hostname"].as();
if (!customHostname.isEmpty()) {
networkConfig.hostname = customHostname;
- LOG_DEBUG("ConfigManager - Custom hostname loaded from SD: %s", customHostname.c_str());
+ LOG_DEBUG(TAG, "ConfigManager - Custom hostname loaded from SD: %s", customHostname.c_str());
}
}
@@ -844,7 +846,7 @@ bool ConfigManager::loadNetworkConfig() {
// Load permanent AP mode setting
if (doc.containsKey("permanentAPMode")) {
networkConfig.permanentAPMode = doc["permanentAPMode"].as();
- LOG_DEBUG("ConfigManager - Permanent AP mode: %s", networkConfig.permanentAPMode ? "enabled" : "disabled");
+ LOG_DEBUG(TAG, "ConfigManager - Permanent AP mode: %s", networkConfig.permanentAPMode ? "enabled" : "disabled");
}
if (doc.containsKey("ip")) {
@@ -882,7 +884,7 @@ bool ConfigManager::loadNetworkConfig() {
}
}
- LOG_DEBUG("ConfigManager - Network config loaded - Hostname: %s, Static IP: %s",
+ LOG_DEBUG(TAG, "ConfigManager - Network config loaded - Hostname: %s, Static IP: %s",
networkConfig.hostname.c_str(),
networkConfig.useStaticIP ? "enabled" : "disabled");
@@ -912,12 +914,12 @@ bool ConfigManager::saveNetworkConfig() {
size_t len = serializeJson(doc, buffer, sizeof(buffer));
if (len == 0 || len >= sizeof(buffer)) {
- LOG_ERROR("ConfigManager - β Failed to serialize network config JSON");
+ LOG_ERROR(TAG, "ConfigManager - β Failed to serialize network config JSON");
return false;
}
saveFileToSD("/settings", "networkConfig.json", buffer);
- LOG_DEBUG("ConfigManager - Network config saved to SD");
+ LOG_DEBUG(TAG, "ConfigManager - Network config saved to SD");
return true;
}
@@ -930,7 +932,7 @@ bool ConfigManager::loadTimeConfig() {
File file = SD.open("/settings/timeConfig.json", FILE_READ);
if (!file) {
- LOG_DEBUG("ConfigManager - Time config file not found - using defaults (GMT+2)");
+ LOG_DEBUG(TAG, "ConfigManager - Time config file not found - using defaults (GMT+2)");
return false;
}
@@ -939,7 +941,7 @@ bool ConfigManager::loadTimeConfig() {
file.close();
if (error) {
- LOG_ERROR("ConfigManager - β Failed to parse time config from SD: %s", error.c_str());
+ LOG_ERROR(TAG, "ConfigManager - β Failed to parse time config from SD: %s", error.c_str());
return false;
}
@@ -961,7 +963,7 @@ bool ConfigManager::loadTimeConfig() {
timeConfig.daylightOffsetSec = doc["daylightOffsetSec"].as();
}
- LOG_DEBUG("ConfigManager - Time config loaded - NTP: %s, GMT offset: %ld, DST offset: %d",
+ LOG_DEBUG(TAG, "ConfigManager - Time config loaded - NTP: %s, GMT offset: %ld, DST offset: %d",
timeConfig.ntpServer.c_str(),
timeConfig.gmtOffsetSec,
timeConfig.daylightOffsetSec);
@@ -985,12 +987,12 @@ bool ConfigManager::saveTimeConfig() {
size_t len = serializeJson(doc, buffer, sizeof(buffer));
if (len == 0 || len >= sizeof(buffer)) {
- LOG_ERROR("ConfigManager - β Failed to serialize time config JSON");
+ LOG_ERROR(TAG, "ConfigManager - β Failed to serialize time config JSON");
return false;
}
saveFileToSD("/settings", "timeConfig.json", buffer);
- LOG_DEBUG("ConfigManager - Time config saved to SD");
+ LOG_DEBUG(TAG, "ConfigManager - Time config saved to SD");
return true;
}
@@ -1000,9 +1002,9 @@ bool ConfigManager::saveTimeConfig() {
bool ConfigManager::resetAllToDefaults() {
- LOG_INFO("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
- LOG_INFO(" π RESET SETTINGS TO DEFAULTS INITIATED");
- LOG_INFO("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
+ LOG_INFO(TAG, "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
+ LOG_INFO(TAG, " π RESET SETTINGS TO DEFAULTS INITIATED");
+ LOG_INFO(TAG, "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
if (!ensureSDCard()) {
return false;
@@ -1029,22 +1031,22 @@ bool ConfigManager::resetAllToDefaults() {
int numFiles = sizeof(settingsFiles) / sizeof(settingsFiles[0]);
- LOG_DEBUG("ConfigManager - Step 1: Deleting %d configuration files...", numFiles);
+ LOG_DEBUG(TAG, "ConfigManager - Step 1: Deleting %d configuration files...", numFiles);
for (int i = 0; i < numFiles; i++) {
const char* filepath = settingsFiles[i];
if (SD.exists(filepath)) {
if (SD.remove(filepath)) {
- LOG_VERBOSE("ConfigManager - β
Deleted: %s", filepath);
+ LOG_VERBOSE(TAG, "ConfigManager - β
Deleted: %s", filepath);
filesDeleted++;
} else {
- LOG_ERROR("ConfigManager - β Failed to delete: %s", filepath);
+ LOG_ERROR(TAG, "ConfigManager - β Failed to delete: %s", filepath);
filesFailed++;
allDeleted = false;
}
} else {
- LOG_VERBOSE("ConfigManager - Skip (not found): %s", filepath);
+ LOG_VERBOSE(TAG, "ConfigManager - Skip (not found): %s", filepath);
}
}
@@ -1053,7 +1055,7 @@ bool ConfigManager::resetAllToDefaults() {
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if (SD.exists("/melodies")) {
- LOG_DEBUG("ConfigManager - Step 2: Deleting melody files...");
+ LOG_DEBUG(TAG, "ConfigManager - Step 2: Deleting melody files...");
File melodiesDir = SD.open("/melodies");
if (melodiesDir && melodiesDir.isDirectory()) {
@@ -1066,10 +1068,10 @@ bool ConfigManager::resetAllToDefaults() {
if (!entry.isDirectory()) {
if (SD.remove(entryPath.c_str())) {
- LOG_VERBOSE("ConfigManager - β
Deleted melody: %s", entryPath.c_str());
+ LOG_VERBOSE(TAG, "ConfigManager - β
Deleted melody: %s", entryPath.c_str());
melodiesDeleted++;
} else {
- LOG_ERROR("ConfigManager - β Failed to delete melody: %s", entryPath.c_str());
+ LOG_ERROR(TAG, "ConfigManager - β Failed to delete melody: %s", entryPath.c_str());
melodiesFailed++;
allDeleted = false;
}
@@ -1083,33 +1085,33 @@ bool ConfigManager::resetAllToDefaults() {
// Try to remove the empty directory
if (SD.rmdir("/melodies")) {
- LOG_VERBOSE("ConfigManager - β
Deleted /melodies directory");
+ LOG_VERBOSE(TAG, "ConfigManager - β
Deleted /melodies directory");
} else {
- LOG_WARNING("ConfigManager - β οΈ Could not delete /melodies directory (may not be empty)");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Could not delete /melodies directory (may not be empty)");
}
- LOG_DEBUG("ConfigManager - Melodies deleted: %d, failed: %d", melodiesDeleted, melodiesFailed);
+ LOG_DEBUG(TAG, "ConfigManager - Melodies deleted: %d, failed: %d", melodiesDeleted, melodiesFailed);
filesDeleted += melodiesDeleted;
filesFailed += melodiesFailed;
}
} else {
- LOG_VERBOSE("ConfigManager - /melodies directory not found");
+ LOG_VERBOSE(TAG, "ConfigManager - /melodies directory not found");
}
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SUMMARY
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- LOG_INFO("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
- LOG_INFO("ConfigManager - Full reset summary:");
- LOG_INFO("ConfigManager - β
Files deleted: %d", filesDeleted);
- LOG_INFO("ConfigManager - β Files failed: %d", filesFailed);
- LOG_INFO("ConfigManager - π Total processed: %d", filesDeleted + filesFailed);
- LOG_INFO("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
+ LOG_INFO(TAG, "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
+ LOG_INFO(TAG, "ConfigManager - Full reset summary:");
+ LOG_INFO(TAG, "ConfigManager - β
Files deleted: %d", filesDeleted);
+ LOG_INFO(TAG, "ConfigManager - β Files failed: %d", filesFailed);
+ LOG_INFO(TAG, "ConfigManager - π Total processed: %d", filesDeleted + filesFailed);
+ LOG_INFO(TAG, "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
- LOG_INFO("ConfigManager - β
RESET TO DEFAULT COMPLETE");
- LOG_INFO("ConfigManager - π Device will boot with default settings on next restart");
- LOG_INFO("ConfigManager - π Device identity (UID) preserved");
+ LOG_INFO(TAG, "ConfigManager - β
RESET TO DEFAULT COMPLETE");
+ LOG_INFO(TAG, "ConfigManager - π Device will boot with default settings on next restart");
+ LOG_INFO(TAG, "ConfigManager - π Device identity (UID) preserved");
return allDeleted;
}
@@ -1212,31 +1214,31 @@ String ConfigManager::getAllSettingsAsJson() const {
bool ConfigManager::setSerialLogLevel(uint8_t level) {
if (level > 5) { // Max level is VERBOSE (5)
- LOG_WARNING("ConfigManager - β οΈ Invalid serial log level %d, valid range is 0-5", level);
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Invalid serial log level %d, valid range is 0-5", level);
return false;
}
generalConfig.serialLogLevel = level;
- LOG_DEBUG("ConfigManager - Serial log level set to %d", level);
+ LOG_DEBUG(TAG, "ConfigManager - Serial log level set to %d", level);
return true;
}
bool ConfigManager::setSdLogLevel(uint8_t level) {
if (level > 5) { // Max level is VERBOSE (5)
- LOG_WARNING("ConfigManager - β οΈ Invalid SD log level %d, valid range is 0-5", level);
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Invalid SD log level %d, valid range is 0-5", level);
return false;
}
generalConfig.sdLogLevel = level;
- LOG_DEBUG("ConfigManager - SD log level set to %d", level);
+ LOG_DEBUG(TAG, "ConfigManager - SD log level set to %d", level);
return true;
}
bool ConfigManager::setMqttLogLevel(uint8_t level) {
if (level > 5) { // Max level is VERBOSE (5)
- LOG_WARNING("ConfigManager - β οΈ Invalid MQTT log level %d, valid range is 0-5", level);
+ LOG_WARNING(TAG, "ConfigManager - β οΈ Invalid MQTT log level %d, valid range is 0-5", level);
return false;
}
generalConfig.mqttLogLevel = level;
- LOG_DEBUG("ConfigManager - MQTT log level set to %d", level);
+ LOG_DEBUG(TAG, "ConfigManager - MQTT log level set to %d", level);
return true;
}
@@ -1245,7 +1247,7 @@ bool ConfigManager::loadGeneralConfig() {
File file = SD.open("/settings/generalConfig.json", FILE_READ);
if (!file) {
- LOG_WARNING("ConfigManager - β οΈ General config file not found - using defaults");
+ LOG_WARNING(TAG, "ConfigManager - β οΈ General config file not found - using defaults");
return false;
}
@@ -1254,7 +1256,7 @@ bool ConfigManager::loadGeneralConfig() {
file.close();
if (error) {
- LOG_ERROR("ConfigManager - β Failed to parse general config from SD: %s", error.c_str());
+ LOG_ERROR(TAG, "ConfigManager - β Failed to parse general config from SD: %s", error.c_str());
return false;
}
@@ -1272,7 +1274,7 @@ bool ConfigManager::loadGeneralConfig() {
mqttConfig.enabled = generalConfig.mqttEnabled; // Sync with mqttConfig
}
- LOG_DEBUG("ConfigManager - General config loaded - Serial log level: %d, SD log level: %d, MQTT log level: %d, MQTT enabled: %s",
+ LOG_DEBUG(TAG, "ConfigManager - General config loaded - Serial log level: %d, SD log level: %d, MQTT log level: %d, MQTT enabled: %s",
generalConfig.serialLogLevel, generalConfig.sdLogLevel, generalConfig.mqttLogLevel,
generalConfig.mqttEnabled ? "true" : "false");
return true;
@@ -1291,11 +1293,11 @@ bool ConfigManager::saveGeneralConfig() {
size_t len = serializeJson(doc, buffer, sizeof(buffer));
if (len == 0 || len >= sizeof(buffer)) {
- LOG_ERROR("ConfigManager - β Failed to serialize general config JSON");
+ LOG_ERROR(TAG, "ConfigManager - β Failed to serialize general config JSON");
return false;
}
saveFileToSD("/settings", "generalConfig.json", buffer);
- LOG_DEBUG("ConfigManager - General config saved (MQTT enabled: %s)", generalConfig.mqttEnabled ? "true" : "false");
+ LOG_DEBUG(TAG, "ConfigManager - General config saved (MQTT enabled: %s)", generalConfig.mqttEnabled ? "true" : "false");
return true;
}
diff --git a/vesper/src/ConfigManager/ConfigManager.hpp b/vesper/src/ConfigManager/ConfigManager.hpp
index fd4ec2e..3c7df66 100644
--- a/vesper/src/ConfigManager/ConfigManager.hpp
+++ b/vesper/src/ConfigManager/ConfigManager.hpp
@@ -25,7 +25,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -106,8 +105,8 @@ public:
uint8_t ethSpiMiso = 19; // π Hardware-specific - OK as is
uint8_t ethSpiMosi = 23; // π Hardware-specific - OK as is
- // ETH PHY Configuration - hardware-specific
- eth_phy_type_t ethPhyType = ETH_PHY_W5500; // π Hardware-specific - OK as is
+ // ETH PHY Configuration - ETHERNET DISABLED (kept for legacy/future use)
+ uint8_t ethPhyType = 9; // was ETH_PHY_W5500 (9) β Ethernet removed in v138
uint8_t ethPhyAddr = 1; // π Hardware-specific - OK as is
uint8_t ethPhyCs = 5; // πΎ Hardware-specific - OK as is
int8_t ethPhyIrq = -1; // β‘ Hardware-specific - OK as is
@@ -409,6 +408,8 @@ public:
bool setSerialLogLevel(uint8_t level);
bool setSdLogLevel(uint8_t level);
bool setMqttLogLevel(uint8_t level);
+ uint8_t getSerialLogLevel() const { return generalConfig.serialLogLevel; }
+ uint8_t getSdLogLevel() const { return generalConfig.sdLogLevel; }
uint8_t getMqttLogLevel() const { return generalConfig.mqttLogLevel; }
void setMqttEnabled(bool enabled) { generalConfig.mqttEnabled = enabled; mqttConfig.enabled = enabled; }
bool getMqttEnabled() const { return generalConfig.mqttEnabled; }
diff --git a/vesper/src/FileManager/FileManager.cpp b/vesper/src/FileManager/FileManager.cpp
index f354f79..cead620 100644
--- a/vesper/src/FileManager/FileManager.cpp
+++ b/vesper/src/FileManager/FileManager.cpp
@@ -1,4 +1,6 @@
#include "FileManager.hpp"
+
+#define TAG "FileManager"
#include "../BuiltInMelodies/BuiltInMelodies.hpp"
FileManager::FileManager(ConfigManager* config) : configManager(config) {
@@ -8,18 +10,18 @@ FileManager::FileManager(ConfigManager* config) : configManager(config) {
bool FileManager::initializeSD() {
uint8_t sdPin = configManager->getHardwareConfig().sdChipSelect;
if (!SD.begin(sdPin)) {
- LOG_ERROR("SD Card initialization failed!");
+ LOG_ERROR(TAG, "SD Card initialization failed!");
return false;
}
return true;
}
bool FileManager::addMelody(JsonVariant doc) {
- LOG_INFO("Adding melody from JSON data...");
+ LOG_INFO(TAG, "Adding melody from JSON data...");
// Extract URL and filename from JSON
if (!doc.containsKey("download_url") || !doc.containsKey("melodys_uid")) {
- LOG_ERROR("Missing required parameters: download_url or melodys_uid");
+ LOG_ERROR(TAG, "Missing required parameters: download_url or melodys_uid");
return false;
}
@@ -30,20 +32,20 @@ bool FileManager::addMelody(JsonVariant doc) {
if (BuiltInMelodies::isBuiltInMelody(melodyUid)) {
const BuiltInMelodies::MelodyInfo* builtinMelody = BuiltInMelodies::findMelodyByUID(melodyUid);
if (builtinMelody != nullptr) {
- LOG_INFO("Melody '%s' is a built-in melody, skipping download", melodyUid);
+ LOG_INFO(TAG, "Melody '%s' is a built-in melody, skipping download", melodyUid);
return true; // Success - no download needed
}
// If starts with builtin_ but not found, log warning and try download anyway
- LOG_WARNING("Melody '%s' has builtin_ prefix but not found in library, attempting download", melodyUid);
+ LOG_WARNING(TAG, "Melody '%s' has builtin_ prefix but not found in library, attempting download", melodyUid);
}
// Download the melody file to /melodies directory
if (downloadFile(url, "/melodies", melodyUid)) {
- LOG_INFO("Melody download successful: %s", melodyUid);
+ LOG_INFO(TAG, "Melody download successful: %s", melodyUid);
return true;
}
- LOG_ERROR("Melody download failed: %s", melodyUid);
+ LOG_ERROR(TAG, "Melody download failed: %s", melodyUid);
return false;
}
@@ -68,7 +70,7 @@ bool FileManager::ensureDirectoryExists(const String& dirPath) {
}
bool FileManager::downloadFile(const String& url, const String& directory, const String& filename) {
- LOG_INFO("Starting download from: %s", url.c_str());
+ LOG_INFO(TAG, "Starting download from: %s", url.c_str());
// Check if URL is HTTPS
bool isHttps = url.startsWith("https://");
@@ -82,10 +84,10 @@ bool FileManager::downloadFile(const String& url, const String& directory, const
secureClient->setInsecure(); // Skip certificate validation for Firebase
secureClient->setTimeout(15); // 15 second timeout for TLS operations
http.begin(*secureClient, url);
- LOG_DEBUG("Using HTTPS with secure client");
+ LOG_DEBUG(TAG, "Using HTTPS with secure client");
} else {
http.begin(url);
- LOG_DEBUG("Using HTTP");
+ LOG_DEBUG(TAG, "Using HTTP");
}
http.setTimeout(30000); // 30 second timeout for large files
@@ -93,18 +95,18 @@ bool FileManager::downloadFile(const String& url, const String& directory, const
// Disable task watchdog for current task during blocking HTTPS operation
// The TLS handshake can take several seconds and would trigger watchdog
- LOG_DEBUG("Disabling watchdog for download...");
+ LOG_DEBUG(TAG, "Disabling watchdog for download...");
esp_task_wdt_delete(NULL);
- LOG_DEBUG("Sending HTTP GET request...");
+ LOG_DEBUG(TAG, "Sending HTTP GET request...");
int httpCode = http.GET();
// Re-enable task watchdog after HTTP request completes
esp_task_wdt_add(NULL);
- LOG_DEBUG("Watchdog re-enabled after HTTP request");
+ LOG_DEBUG(TAG, "Watchdog re-enabled after HTTP request");
if (httpCode != HTTP_CODE_OK && httpCode != HTTP_CODE_MOVED_PERMANENTLY && httpCode != HTTP_CODE_FOUND) {
- LOG_ERROR("HTTP GET failed, code: %d, error: %s", httpCode, http.errorToString(httpCode).c_str());
+ LOG_ERROR(TAG, "HTTP GET failed, code: %d, error: %s", httpCode, http.errorToString(httpCode).c_str());
http.end();
if (secureClient) delete secureClient;
return false;
@@ -118,7 +120,7 @@ bool FileManager::downloadFile(const String& url, const String& directory, const
// Ensure directory exists
if (!ensureDirectoryExists(directory)) {
- LOG_ERROR("Failed to create directory: %s", directory.c_str());
+ LOG_ERROR(TAG, "Failed to create directory: %s", directory.c_str());
http.end();
if (secureClient) delete secureClient;
return false;
@@ -131,7 +133,7 @@ bool FileManager::downloadFile(const String& url, const String& directory, const
File file = SD.open(fullPath.c_str(), FILE_WRITE);
if (!file) {
- LOG_ERROR("Failed to open file for writing: %s", fullPath.c_str());
+ LOG_ERROR(TAG, "Failed to open file for writing: %s", fullPath.c_str());
http.end();
if (secureClient) delete secureClient;
return false;
@@ -140,7 +142,7 @@ bool FileManager::downloadFile(const String& url, const String& directory, const
// Get stream and file size
WiFiClient* stream = http.getStreamPtr();
int contentLength = http.getSize();
- LOG_DEBUG("Content length: %d bytes", contentLength);
+ LOG_DEBUG(TAG, "Content length: %d bytes", contentLength);
uint8_t buffer[512]; // Smaller buffer for better responsiveness
size_t totalBytes = 0;
@@ -163,7 +165,7 @@ bool FileManager::downloadFile(const String& url, const String& directory, const
// Log progress every 5 seconds
if (millis() - lastLog > 5000) {
- LOG_DEBUG("Download progress: %u bytes", totalBytes);
+ LOG_DEBUG(TAG, "Download progress: %u bytes", totalBytes);
lastLog = millis();
}
}
@@ -191,19 +193,19 @@ bool FileManager::downloadFile(const String& url, const String& directory, const
file.close();
http.end();
if (secureClient) delete secureClient;
- LOG_INFO("Download complete, file saved to: %s (%u bytes)", fullPath.c_str(), totalBytes);
+ LOG_INFO(TAG, "Download complete, file saved to: %s (%u bytes)", fullPath.c_str(), totalBytes);
return true;
}
String FileManager::listFilesAsJson(const char* dirPath) {
if (!initializeSD()) {
- LOG_ERROR("SD initialization failed");
+ LOG_ERROR(TAG, "SD initialization failed");
return "{}";
}
File dir = SD.open(dirPath);
if (!dir || !dir.isDirectory()) {
- LOG_ERROR("Directory not found: %s", dirPath);
+ LOG_ERROR(TAG, "Directory not found: %s", dirPath);
return "{}";
}
@@ -242,10 +244,10 @@ bool FileManager::deleteFile(const String& filePath) {
}
if (SD.remove(filePath.c_str())) {
- LOG_INFO("File deleted: %s", filePath.c_str());
+ LOG_INFO(TAG, "File deleted: %s", filePath.c_str());
return true;
} else {
- LOG_ERROR("Failed to delete file: %s", filePath.c_str());
+ LOG_ERROR(TAG, "Failed to delete file: %s", filePath.c_str());
return false;
}
}
@@ -276,18 +278,18 @@ bool FileManager::writeJsonFile(const String& filePath, JsonDocument& doc) {
File file = SD.open(filePath.c_str(), FILE_WRITE);
if (!file) {
- LOG_ERROR("Failed to open file for writing: %s", filePath.c_str());
+ LOG_ERROR(TAG, "Failed to open file for writing: %s", filePath.c_str());
return false;
}
if (serializeJson(doc, file) == 0) {
- LOG_ERROR("Failed to write JSON to file: %s", filePath.c_str());
+ LOG_ERROR(TAG, "Failed to write JSON to file: %s", filePath.c_str());
file.close();
return false;
}
file.close();
- LOG_DEBUG("JSON file written successfully: %s", filePath.c_str());
+ LOG_DEBUG(TAG, "JSON file written successfully: %s", filePath.c_str());
return true;
}
@@ -298,7 +300,7 @@ bool FileManager::readJsonFile(const String& filePath, JsonDocument& doc) {
File file = SD.open(filePath.c_str(), FILE_READ);
if (!file) {
- LOG_ERROR("Failed to open file for reading: %s", filePath.c_str());
+ LOG_ERROR(TAG, "Failed to open file for reading: %s", filePath.c_str());
return false;
}
@@ -306,12 +308,12 @@ bool FileManager::readJsonFile(const String& filePath, JsonDocument& doc) {
file.close();
if (error) {
- LOG_ERROR("Failed to parse JSON from file: %s, error: %s",
+ LOG_ERROR(TAG, "Failed to parse JSON from file: %s, error: %s",
filePath.c_str(), error.c_str());
return false;
}
- LOG_DEBUG("JSON file read successfully: %s", filePath.c_str());
+ LOG_DEBUG(TAG, "JSON file read successfully: %s", filePath.c_str());
return true;
}
@@ -322,26 +324,26 @@ bool FileManager::readJsonFile(const String& filePath, JsonDocument& doc) {
bool FileManager::isHealthy() const {
// Check if ConfigManager is available
if (!configManager) {
- LOG_DEBUG("FileManager: Unhealthy - ConfigManager not available");
+ LOG_DEBUG(TAG, "FileManager: Unhealthy - ConfigManager not available");
return false;
}
// Check if SD card can be initialized
uint8_t sdPin = configManager->getHardwareConfig().sdChipSelect;
if (!SD.begin(sdPin)) {
- LOG_DEBUG("FileManager: Unhealthy - SD Card initialization failed");
+ LOG_DEBUG(TAG, "FileManager: Unhealthy - SD Card initialization failed");
return false;
}
// Check if we can read from SD card (test with root directory)
File root = SD.open("/");
if (!root) {
- LOG_DEBUG("FileManager: Unhealthy - Cannot access SD root directory");
+ LOG_DEBUG(TAG, "FileManager: Unhealthy - Cannot access SD root directory");
return false;
}
if (!root.isDirectory()) {
- LOG_DEBUG("FileManager: Unhealthy - SD root is not a directory");
+ LOG_DEBUG(TAG, "FileManager: Unhealthy - SD root is not a directory");
root.close();
return false;
}
@@ -352,7 +354,7 @@ bool FileManager::isHealthy() const {
String testFile = "/health_test.tmp";
File file = SD.open(testFile.c_str(), FILE_WRITE);
if (!file) {
- LOG_DEBUG("FileManager: Unhealthy - Cannot write to SD card");
+ LOG_DEBUG(TAG, "FileManager: Unhealthy - Cannot write to SD card");
return false;
}
@@ -362,7 +364,7 @@ bool FileManager::isHealthy() const {
// Verify we can read the test file
file = SD.open(testFile.c_str(), FILE_READ);
if (!file) {
- LOG_DEBUG("FileManager: Unhealthy - Cannot read test file from SD card");
+ LOG_DEBUG(TAG, "FileManager: Unhealthy - Cannot read test file from SD card");
return false;
}
@@ -373,9 +375,24 @@ bool FileManager::isHealthy() const {
SD.remove(testFile.c_str());
if (content != "health_check") {
- LOG_DEBUG("FileManager: Unhealthy - SD card read/write test failed");
+ LOG_DEBUG(TAG, "FileManager: Unhealthy - SD card read/write test failed");
return false;
}
return true;
}
+
+bool FileManager::appendLine(const String& filePath, const String& line) {
+ if (!initializeSD()) {
+ return false;
+ }
+
+ File file = SD.open(filePath.c_str(), FILE_APPEND);
+ if (!file) {
+ return false;
+ }
+
+ file.println(line);
+ file.close();
+ return true;
+}
diff --git a/vesper/src/FileManager/FileManager.hpp b/vesper/src/FileManager/FileManager.hpp
index cfacc67..07b794c 100644
--- a/vesper/src/FileManager/FileManager.hpp
+++ b/vesper/src/FileManager/FileManager.hpp
@@ -50,6 +50,9 @@ public:
// Generic read/write for JSON data
bool writeJsonFile(const String& filePath, JsonDocument& doc);
bool readJsonFile(const String& filePath, JsonDocument& doc);
+
+ // Append a single text line to a file (used by SD log channel)
+ bool appendLine(const String& filePath, const String& line);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// HEALTH CHECK METHOD
diff --git a/vesper/src/FirmwareValidator/FirmwareValidator.cpp b/vesper/src/FirmwareValidator/FirmwareValidator.cpp
index 0b0bd8f..fef20fc 100644
--- a/vesper/src/FirmwareValidator/FirmwareValidator.cpp
+++ b/vesper/src/FirmwareValidator/FirmwareValidator.cpp
@@ -5,6 +5,8 @@
*/
#include "FirmwareValidator.hpp"
+
+#define TAG "FirmwareValidator"
#include "../HealthMonitor/HealthMonitor.hpp"
#include "../ConfigManager/ConfigManager.hpp"
#include
@@ -43,45 +45,45 @@ FirmwareValidator::~FirmwareValidator() {
}
bool FirmwareValidator::begin(HealthMonitor* healthMonitor, ConfigManager* configManager) {
- LOG_INFO("π‘οΈ Initializing Firmware Validator System");
+ LOG_INFO(TAG, "π‘οΈ Initializing Firmware Validator System");
_healthMonitor = healthMonitor;
_configManager = configManager;
// Initialize NVS for persistent state storage
if (!initializeNVS()) {
- LOG_ERROR("β Failed to initialize NVS for firmware validation");
+ LOG_ERROR(TAG, "β Failed to initialize NVS for firmware validation");
return false;
}
// Initialize ESP32 partition information
if (!initializePartitions()) {
- LOG_ERROR("β Failed to initialize ESP32 partitions");
+ LOG_ERROR(TAG, "β Failed to initialize ESP32 partitions");
return false;
}
// Load previous validation state
loadValidationState();
- LOG_INFO("β
Firmware Validator initialized");
- LOG_INFO("π Running partition: %s", getPartitionLabel(_runningPartition).c_str());
- LOG_INFO("π Backup partition: %s", getPartitionLabel(_backupPartition).c_str());
- LOG_INFO("π Validation state: %s", validationStateToString(_validationState).c_str());
+ LOG_INFO(TAG, "β
Firmware Validator initialized");
+ LOG_INFO(TAG, "π Running partition: %s", getPartitionLabel(_runningPartition).c_str());
+ LOG_INFO(TAG, "π Backup partition: %s", getPartitionLabel(_backupPartition).c_str());
+ LOG_INFO(TAG, "π Validation state: %s", validationStateToString(_validationState).c_str());
return true;
}
bool FirmwareValidator::performStartupValidation() {
- LOG_INFO("π Starting firmware startup validation...");
+ LOG_INFO(TAG, "π Starting firmware startup validation...");
// Check if this is a new firmware that needs validation
const esp_partition_t* bootPartition = esp_ota_get_boot_partition();
const esp_partition_t* runningPartition = esp_ota_get_running_partition();
if (bootPartition != runningPartition) {
- LOG_WARNING("β οΈ Boot partition differs from running partition!");
- LOG_WARNING(" Boot: %s", getPartitionLabel(bootPartition).c_str());
- LOG_WARNING(" Running: %s", getPartitionLabel(runningPartition).c_str());
+ LOG_WARNING(TAG, "β οΈ Boot partition differs from running partition!");
+ LOG_WARNING(TAG, " Boot: %s", getPartitionLabel(bootPartition).c_str());
+ LOG_WARNING(TAG, " Running: %s", getPartitionLabel(runningPartition).c_str());
}
// Increment boot count for this session
@@ -91,11 +93,11 @@ bool FirmwareValidator::performStartupValidation() {
if (_validationState == FirmwareValidationState::UNKNOWN) {
// First boot of potentially new firmware
_validationState = FirmwareValidationState::STARTUP_PENDING;
- LOG_INFO("π New firmware detected - entering validation mode");
+ LOG_INFO(TAG, "π New firmware detected - entering validation mode");
}
if (_validationState == FirmwareValidationState::STARTUP_PENDING) {
- LOG_INFO("π Performing startup validation...");
+ LOG_INFO(TAG, "π Performing startup validation...");
_validationState = FirmwareValidationState::STARTUP_RUNNING;
_validationStartTime = millis();
@@ -109,7 +111,7 @@ bool FirmwareValidator::performStartupValidation() {
break;
}
- LOG_WARNING("β οΈ Startup health check failed, retrying...");
+ LOG_WARNING(TAG, "β οΈ Startup health check failed, retrying...");
delay(1000); // Wait 1 second before retry
}
@@ -117,18 +119,18 @@ bool FirmwareValidator::performStartupValidation() {
_validationState = FirmwareValidationState::RUNTIME_TESTING;
_startupRetryCount = 0; // Reset retry count on success
saveValidationState();
- LOG_INFO("β
Firmware startup validation PASSED - proceeding with initialization");
+ LOG_INFO(TAG, "β
Firmware startup validation PASSED - proceeding with initialization");
return true;
} else {
- LOG_ERROR("β Startup validation FAILED after %lu ms", _config.startupTimeoutMs);
+ LOG_ERROR(TAG, "β Startup validation FAILED after %lu ms", _config.startupTimeoutMs);
_startupRetryCount++;
if (_startupRetryCount >= _config.maxStartupRetries) {
- LOG_ERROR("π₯ Maximum startup retries exceeded - triggering rollback");
+ LOG_ERROR(TAG, "π₯ Maximum startup retries exceeded - triggering rollback");
handleValidationFailure("Startup validation failed repeatedly");
return false; // This will trigger rollback and reboot
} else {
- LOG_WARNING("π Startup retry %d/%d - rebooting...",
+ LOG_WARNING(TAG, "π Startup retry %d/%d - rebooting...",
_startupRetryCount, _config.maxStartupRetries);
saveValidationState();
delay(1000);
@@ -137,20 +139,20 @@ bool FirmwareValidator::performStartupValidation() {
}
}
} else if (_validationState == FirmwareValidationState::VALIDATED) {
- LOG_INFO("β
Firmware already validated - normal operation");
+ LOG_INFO(TAG, "β
Firmware already validated - normal operation");
return true;
} else if (_validationState == FirmwareValidationState::STARTUP_RUNNING) {
// Handle interrupted validation from previous boot
- LOG_INFO("π Resuming interrupted validation - transitioning to runtime testing");
+ LOG_INFO(TAG, "π Resuming interrupted validation - transitioning to runtime testing");
_validationState = FirmwareValidationState::RUNTIME_TESTING;
saveValidationState();
return true;
} else if (_validationState == FirmwareValidationState::RUNTIME_TESTING) {
// Already in runtime testing from previous boot
- LOG_INFO("π Continuing runtime validation from previous session");
+ LOG_INFO(TAG, "π Continuing runtime validation from previous session");
return true;
} else {
- LOG_WARNING("β οΈ Unexpected validation state: %s",
+ LOG_WARNING(TAG, "β οΈ Unexpected validation state: %s",
validationStateToString(_validationState).c_str());
return true; // Continue anyway
}
@@ -158,12 +160,12 @@ bool FirmwareValidator::performStartupValidation() {
void FirmwareValidator::startRuntimeValidation() {
if (_validationState != FirmwareValidationState::RUNTIME_TESTING) {
- LOG_WARNING("β οΈ Runtime validation called in wrong state: %s",
+ LOG_WARNING(TAG, "β οΈ Runtime validation called in wrong state: %s",
validationStateToString(_validationState).c_str());
return;
}
- LOG_INFO("π Starting extended runtime validation (%lu ms timeout)",
+ LOG_INFO(TAG, "π Starting extended runtime validation (%lu ms timeout)",
_config.runtimeTimeoutMs);
_validationStartTime = millis();
@@ -180,7 +182,7 @@ void FirmwareValidator::startRuntimeValidation() {
if (_validationTimer) {
xTimerStart(_validationTimer, 0);
} else {
- LOG_ERROR("β Failed to create validation timer");
+ LOG_ERROR(TAG, "β Failed to create validation timer");
handleValidationFailure("Timer creation failed");
return;
}
@@ -197,7 +199,7 @@ void FirmwareValidator::startRuntimeValidation() {
);
if (!_monitoringTask) {
- LOG_ERROR("β Failed to create monitoring task");
+ LOG_ERROR(TAG, "β Failed to create monitoring task");
handleValidationFailure("Monitoring task creation failed");
return;
}
@@ -207,21 +209,21 @@ void FirmwareValidator::startRuntimeValidation() {
setupWatchdog();
}
- LOG_INFO("β
Runtime validation started - monitoring system health...");
+ LOG_INFO(TAG, "β
Runtime validation started - monitoring system health...");
}
void FirmwareValidator::commitFirmware() {
if (_validationState == FirmwareValidationState::VALIDATED) {
- LOG_INFO("β
Firmware already committed");
+ LOG_INFO(TAG, "β
Firmware already committed");
return;
}
- LOG_INFO("πΎ Committing firmware as valid and stable...");
+ LOG_INFO(TAG, "πΎ Committing firmware as valid and stable...");
// Mark current partition as valid boot partition
esp_err_t err = esp_ota_set_boot_partition(_runningPartition);
if (err != ESP_OK) {
- LOG_ERROR("β Failed to set boot partition: %s", esp_err_to_name(err));
+ LOG_ERROR(TAG, "β Failed to set boot partition: %s", esp_err_to_name(err));
return;
}
@@ -240,11 +242,11 @@ void FirmwareValidator::commitFirmware() {
_monitoringTask = nullptr;
}
- LOG_INFO("π Firmware successfully committed! System is now stable.");
+ LOG_INFO(TAG, "π Firmware successfully committed! System is now stable.");
}
void FirmwareValidator::rollbackFirmware() {
- LOG_WARNING("π Manual firmware rollback requested");
+ LOG_WARNING(TAG, "π Manual firmware rollback requested");
handleValidationFailure("Manual rollback requested");
}
@@ -258,13 +260,13 @@ bool FirmwareValidator::initializeNVS() {
}
if (err != ESP_OK) {
- LOG_ERROR("β Failed to initialize NVS flash: %s", esp_err_to_name(err));
+ LOG_ERROR(TAG, "β Failed to initialize NVS flash: %s", esp_err_to_name(err));
return false;
}
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &_nvsHandle);
if (err != ESP_OK) {
- LOG_ERROR("β Failed to open NVS namespace: %s", esp_err_to_name(err));
+ LOG_ERROR(TAG, "β Failed to open NVS namespace: %s", esp_err_to_name(err));
return false;
}
@@ -279,9 +281,9 @@ void FirmwareValidator::loadValidationState() {
err = nvs_get_u8(_nvsHandle, NVS_STATE_KEY, &state);
if (err == ESP_OK) {
_validationState = static_cast(state);
- LOG_DEBUG("π NVS validation state found: %s", validationStateToString(_validationState).c_str());
+ LOG_DEBUG(TAG, "π NVS validation state found: %s", validationStateToString(_validationState).c_str());
} else {
- LOG_DEBUG("π No NVS validation state found, using UNKNOWN (error: %s)", esp_err_to_name(err));
+ LOG_DEBUG(TAG, "π No NVS validation state found, using UNKNOWN (error: %s)", esp_err_to_name(err));
_validationState = FirmwareValidationState::UNKNOWN;
}
@@ -289,7 +291,7 @@ void FirmwareValidator::loadValidationState() {
nvs_get_u8(_nvsHandle, NVS_RETRY_COUNT_KEY, &_startupRetryCount);
nvs_get_u8(_nvsHandle, NVS_FAILURE_COUNT_KEY, &_runtimeFailureCount);
- LOG_DEBUG("π Loaded validation state: %s (retries: %d, failures: %d)",
+ LOG_DEBUG(TAG, "π Loaded validation state: %s (retries: %d, failures: %d)",
validationStateToString(_validationState).c_str(),
_startupRetryCount, _runtimeFailureCount);
}
@@ -300,7 +302,7 @@ void FirmwareValidator::saveValidationState() {
// Save validation state
err = nvs_set_u8(_nvsHandle, NVS_STATE_KEY, static_cast(_validationState));
if (err != ESP_OK) {
- LOG_ERROR("β Failed to save validation state: %s", esp_err_to_name(err));
+ LOG_ERROR(TAG, "β Failed to save validation state: %s", esp_err_to_name(err));
}
// Save retry counts
@@ -314,16 +316,16 @@ void FirmwareValidator::saveValidationState() {
// Commit changes
err = nvs_commit(_nvsHandle);
if (err != ESP_OK) {
- LOG_ERROR("β Failed to commit NVS changes: %s", esp_err_to_name(err));
+ LOG_ERROR(TAG, "β Failed to commit NVS changes: %s", esp_err_to_name(err));
}
- LOG_DEBUG("πΎ Saved validation state: %s", validationStateToString(_validationState).c_str());
+ LOG_DEBUG(TAG, "πΎ Saved validation state: %s", validationStateToString(_validationState).c_str());
}
bool FirmwareValidator::initializePartitions() {
_runningPartition = esp_ota_get_running_partition();
if (!_runningPartition) {
- LOG_ERROR("β Failed to get running partition");
+ LOG_ERROR(TAG, "β Failed to get running partition");
return false;
}
@@ -350,7 +352,7 @@ bool FirmwareValidator::initializePartitions() {
}
if (!_backupPartition) {
- LOG_ERROR("β Failed to find backup partition");
+ LOG_ERROR(TAG, "β Failed to find backup partition");
return false;
}
@@ -358,11 +360,11 @@ bool FirmwareValidator::initializePartitions() {
}
bool FirmwareValidator::performBasicHealthCheck() {
- LOG_VERBOSE("π Performing basic startup health check...");
+ LOG_VERBOSE(TAG, "π Performing basic startup health check...");
// Check if health monitor is available
if (!_healthMonitor) {
- LOG_ERROR("β Health monitor not available");
+ LOG_ERROR(TAG, "β Health monitor not available");
return false;
}
@@ -375,20 +377,20 @@ bool FirmwareValidator::performBasicHealthCheck() {
bool basicHealthOk = bellEngineOk && outputManagerOk && configManagerOk && fileManagerOk;
if (!basicHealthOk) {
- LOG_ERROR("β Basic health check failed:");
- if (!bellEngineOk) LOG_ERROR(" - BellEngine: FAILED");
- if (!outputManagerOk) LOG_ERROR(" - OutputManager: FAILED");
- if (!configManagerOk) LOG_ERROR(" - ConfigManager: FAILED");
- if (!fileManagerOk) LOG_ERROR(" - FileManager: FAILED");
+ LOG_ERROR(TAG, "β Basic health check failed:");
+ if (!bellEngineOk) LOG_ERROR(TAG, " - BellEngine: FAILED");
+ if (!outputManagerOk) LOG_ERROR(TAG, " - OutputManager: FAILED");
+ if (!configManagerOk) LOG_ERROR(TAG, " - ConfigManager: FAILED");
+ if (!fileManagerOk) LOG_ERROR(TAG, " - FileManager: FAILED");
} else {
- LOG_VERBOSE("β
Basic health check passed");
+ LOG_VERBOSE(TAG, "β
Basic health check passed");
}
return basicHealthOk;
}
bool FirmwareValidator::performRuntimeHealthCheck() {
- LOG_VERBOSE("π Performing comprehensive runtime health check...");
+ LOG_VERBOSE(TAG, "π Performing comprehensive runtime health check...");
if (!_healthMonitor) {
return false;
@@ -402,7 +404,7 @@ bool FirmwareValidator::performRuntimeHealthCheck() {
&& (criticalFailures == 0);
if (!runtimeHealthOk) {
- LOG_WARNING("β οΈ Runtime health check failed - Critical failures: %d, Overall: %s",
+ LOG_WARNING(TAG, "β οΈ Runtime health check failed - Critical failures: %d, Overall: %s",
criticalFailures,
(overallHealth == HealthStatus::HEALTHY) ? "HEALTHY" :
(overallHealth == HealthStatus::WARNING) ? "WARNING" :
@@ -415,18 +417,18 @@ bool FirmwareValidator::performRuntimeHealthCheck() {
void FirmwareValidator::validationTimerCallback(TimerHandle_t timer) {
FirmwareValidator* validator = static_cast(pvTimerGetTimerID(timer));
- LOG_INFO("β° Runtime validation timeout reached - committing firmware");
+ LOG_INFO(TAG, "β° Runtime validation timeout reached - committing firmware");
validator->handleValidationSuccess();
}
void FirmwareValidator::monitoringTaskFunction(void* parameter) {
FirmwareValidator* validator = static_cast(parameter);
- LOG_INFO("π Firmware validation monitoring task started on Core %d", xPortGetCoreID());
+ LOG_INFO(TAG, "π Firmware validation monitoring task started on Core %d", xPortGetCoreID());
validator->monitoringLoop();
// Task should not reach here normally
- LOG_WARNING("β οΈ Firmware validation monitoring task ended unexpectedly");
+ LOG_WARNING(TAG, "β οΈ Firmware validation monitoring task ended unexpectedly");
vTaskDelete(NULL);
}
@@ -442,11 +444,11 @@ void FirmwareValidator::monitoringLoop() {
if (!healthOk) {
_runtimeFailureCount++;
- LOG_WARNING("β οΈ Runtime health check failed (%d/%d failures)",
+ LOG_WARNING(TAG, "β οΈ Runtime health check failed (%d/%d failures)",
_runtimeFailureCount, _config.maxRuntimeFailures);
if (_runtimeFailureCount >= _config.maxRuntimeFailures) {
- LOG_ERROR("π₯ Maximum runtime failures exceeded - triggering rollback");
+ LOG_ERROR(TAG, "π₯ Maximum runtime failures exceeded - triggering rollback");
handleValidationFailure("Too many runtime health check failures");
return;
}
@@ -454,7 +456,7 @@ void FirmwareValidator::monitoringLoop() {
// Reset failure count on successful health check
if (_runtimeFailureCount > 0) {
_runtimeFailureCount = 0;
- LOG_INFO("β
Runtime health recovered - reset failure count");
+ LOG_INFO(TAG, "β
Runtime health recovered - reset failure count");
}
}
@@ -464,13 +466,13 @@ void FirmwareValidator::monitoringLoop() {
}
void FirmwareValidator::handleValidationSuccess() {
- LOG_INFO("π Firmware validation completed successfully!");
+ LOG_INFO(TAG, "π Firmware validation completed successfully!");
commitFirmware();
}
void FirmwareValidator::handleValidationFailure(const String& reason) {
- LOG_ERROR("π₯ Firmware validation FAILED: %s", reason.c_str());
- LOG_ERROR("π Initiating firmware rollback...");
+ LOG_ERROR(TAG, "π₯ Firmware validation FAILED: %s", reason.c_str());
+ LOG_ERROR(TAG, "π Initiating firmware rollback...");
_validationState = FirmwareValidationState::FAILED_RUNTIME;
saveValidationState();
@@ -479,7 +481,7 @@ void FirmwareValidator::handleValidationFailure(const String& reason) {
}
void FirmwareValidator::executeRollback() {
- LOG_WARNING("π Executing firmware rollback to previous version...");
+ LOG_WARNING(TAG, "π Executing firmware rollback to previous version...");
// Clean up validation resources first
if (_validationTimer) {
@@ -496,18 +498,18 @@ void FirmwareValidator::executeRollback() {
esp_err_t err = esp_ota_mark_app_invalid_rollback_and_reboot();
if (err != ESP_OK) {
- LOG_ERROR("β Failed to rollback firmware: %s", esp_err_to_name(err));
- LOG_ERROR("π System may be in unstable state - manual intervention required");
+ LOG_ERROR(TAG, "β Failed to rollback firmware: %s", esp_err_to_name(err));
+ LOG_ERROR(TAG, "π System may be in unstable state - manual intervention required");
// If rollback fails, try manual reboot to backup partition
- LOG_WARNING("π Attempting manual reboot to backup partition...");
+ LOG_WARNING(TAG, "π Attempting manual reboot to backup partition...");
if (_backupPartition) {
esp_ota_set_boot_partition(_backupPartition);
delay(1000);
ESP.restart();
} else {
- LOG_ERROR("π No backup partition available - system halt");
+ LOG_ERROR(TAG, "π No backup partition available - system halt");
while(1) {
delay(1000); // Hang here to prevent further damage
}
@@ -515,7 +517,7 @@ void FirmwareValidator::executeRollback() {
}
// This point should not be reached as the device should reboot
- LOG_ERROR("π Rollback function returned unexpectedly");
+ LOG_ERROR(TAG, "π Rollback function returned unexpectedly");
}
FirmwareInfo FirmwareValidator::getCurrentFirmwareInfo() const {
@@ -649,7 +651,7 @@ void FirmwareValidator::incrementBootCount() {
nvs_set_u32(_nvsHandle, NVS_BOOT_COUNT_KEY, bootCount);
nvs_commit(_nvsHandle);
- LOG_DEBUG("π Boot count: %lu", bootCount);
+ LOG_DEBUG(TAG, "π Boot count: %lu", bootCount);
}
void FirmwareValidator::resetValidationCounters() {
@@ -661,35 +663,31 @@ void FirmwareValidator::resetValidationCounters() {
nvs_set_u8(_nvsHandle, NVS_FAILURE_COUNT_KEY, 0);
nvs_commit(_nvsHandle);
- LOG_DEBUG("π Reset validation counters");
+ LOG_DEBUG(TAG, "π Reset validation counters");
}
void FirmwareValidator::setupWatchdog() {
// Check if watchdog is already initialized
- esp_task_wdt_config_t config = {
- .timeout_ms = _config.watchdogTimeoutMs,
- .idle_core_mask = (1 << portNUM_PROCESSORS) - 1,
- .trigger_panic = true
- };
-
- esp_err_t err = esp_task_wdt_init(&config);
+ // Use IDF v4 API: esp_task_wdt_init(timeout_seconds, panic_on_timeout)
+ uint32_t timeoutSec = (_config.watchdogTimeoutMs + 999) / 1000; // ms β seconds, round up
+ esp_err_t err = esp_task_wdt_init(timeoutSec, true);
if (err == ESP_ERR_INVALID_STATE) {
- LOG_DEBUG("π Watchdog already initialized - skipping init");
+ LOG_DEBUG(TAG, "π Watchdog already initialized - skipping init");
} else if (err != ESP_OK) {
- LOG_WARNING("β οΈ Failed to initialize task watchdog: %s", esp_err_to_name(err));
+ LOG_WARNING(TAG, "β οΈ Failed to initialize task watchdog: %s", esp_err_to_name(err));
return;
}
// Try to add current task to watchdog
err = esp_task_wdt_add(NULL);
if (err == ESP_ERR_INVALID_ARG) {
- LOG_DEBUG("π Task already added to watchdog");
+ LOG_DEBUG(TAG, "π Task already added to watchdog");
} else if (err != ESP_OK) {
- LOG_WARNING("β οΈ Failed to add task to watchdog: %s", esp_err_to_name(err));
+ LOG_WARNING(TAG, "β οΈ Failed to add task to watchdog: %s", esp_err_to_name(err));
return;
}
- LOG_INFO("π Watchdog enabled with %lu second timeout", _config.watchdogTimeoutMs / 1000);
+ LOG_INFO(TAG, "π Watchdog enabled with %lu second timeout", _config.watchdogTimeoutMs / 1000);
}
void FirmwareValidator::feedWatchdog() {
diff --git a/vesper/src/HealthMonitor/HealthMonitor.cpp b/vesper/src/HealthMonitor/HealthMonitor.cpp
index e327d3f..c1a8e20 100644
--- a/vesper/src/HealthMonitor/HealthMonitor.cpp
+++ b/vesper/src/HealthMonitor/HealthMonitor.cpp
@@ -5,6 +5,8 @@
*/
#include "HealthMonitor.hpp"
+
+#define TAG "HealthMonitor"
#include "../BellEngine/BellEngine.hpp"
#include "../OutputManager/OutputManager.hpp"
#include "../Communication/CommunicationRouter/CommunicationRouter.hpp"
@@ -29,7 +31,7 @@ HealthMonitor::~HealthMonitor() {
}
bool HealthMonitor::begin() {
- LOG_INFO("π₯ Initializing Health Monitor System");
+ LOG_INFO(TAG, "π₯ Initializing Health Monitor System");
// Create monitoring task if auto-monitoring is enabled
if (_autoMonitoring) {
@@ -44,14 +46,14 @@ bool HealthMonitor::begin() {
);
if (_monitoringTaskHandle != nullptr) {
- LOG_INFO("β
Health Monitor initialized with automatic monitoring");
+ LOG_INFO(TAG, "β
Health Monitor initialized with automatic monitoring");
return true;
} else {
- LOG_ERROR("β Failed to create Health Monitor task");
+ LOG_ERROR(TAG, "β Failed to create Health Monitor task");
return false;
}
} else {
- LOG_INFO("β
Health Monitor initialized (manual mode)");
+ LOG_INFO(TAG, "β
Health Monitor initialized (manual mode)");
return true;
}
}
@@ -71,12 +73,12 @@ void HealthMonitor::initializeSubsystemHealth() {
_subsystemHealth["OTAManager"] = SubsystemHealth("OTAManager", false); // Non-critical
_subsystemHealth["Networking"] = SubsystemHealth("Networking", false); // Non-critical
- LOG_DEBUG("ποΈ Initialized health monitoring for %d subsystems", _subsystemHealth.size());
+ LOG_DEBUG(TAG, "ποΈ Initialized health monitoring for %d subsystems", _subsystemHealth.size());
}
void HealthMonitor::monitoringTask(void* parameter) {
HealthMonitor* monitor = static_cast(parameter);
- LOG_INFO("π₯ Health Monitor task started on Core %d", xPortGetCoreID());
+ LOG_INFO(TAG, "π₯ Health Monitor task started on Core %d", xPortGetCoreID());
while (true) {
monitor->monitoringLoop();
@@ -88,12 +90,12 @@ void HealthMonitor::monitoringLoop() {
if (_player) {
if (_player->_status != PlayerStatus::STOPPED) {
- LOG_VERBOSE("βΈοΈ Skipping health check during active playback");
+ LOG_VERBOSE(TAG, "βΈοΈ Skipping health check during active playback");
return;
}
}
- LOG_VERBOSE("π Performing periodic health check...");
+ LOG_VERBOSE(TAG, "π Performing periodic health check...");
HealthStatus overallHealth = performFullHealthCheck();
@@ -102,25 +104,32 @@ void HealthMonitor::monitoringLoop() {
uint8_t warningCount = getWarningCount();
if (criticalCount > 0) {
- LOG_WARNING("π¨ Health Monitor: %d critical failures detected!", criticalCount);
+ LOG_WARNING(TAG, "π¨ Health Monitor: %d critical failures detected!", criticalCount);
// List critical failures
for (const auto& [name, health] : _subsystemHealth) {
if (health.status == HealthStatus::CRITICAL || health.status == HealthStatus::FAILED) {
- LOG_ERROR("β CRITICAL: %s - %s", name.c_str(), health.lastError.c_str());
+ LOG_ERROR(TAG, "β CRITICAL: %s - %s", name.c_str(), health.lastError.c_str());
}
}
// Check if firmware rollback is recommended
if (shouldRollbackFirmware()) {
- LOG_ERROR("π FIRMWARE ROLLBACK RECOMMENDED - Too many critical failures");
+ LOG_ERROR(TAG, "π FIRMWARE ROLLBACK RECOMMENDED - Too many critical failures");
// In a real system, this would trigger an OTA rollback
// For now, we just log the recommendation
}
} else if (warningCount > 0) {
- LOG_WARNING("β οΈ Health Monitor: %d warnings detected", warningCount);
+ LOG_WARNING(TAG, "β οΈ Health Monitor: %d warning(s) detected", warningCount);
+
+ // List every subsystem that is in WARNING state
+ for (const auto& [name, health] : _subsystemHealth) {
+ if (health.status == HealthStatus::WARNING) {
+ LOG_WARNING(TAG, "β οΈ WARNING: %s - %s", name.c_str(), health.lastError.c_str());
+ }
+ }
} else {
- LOG_VERBOSE("β
All subsystems healthy");
+ LOG_VERBOSE(TAG, "β
All subsystems healthy");
}
}
@@ -219,7 +228,7 @@ HealthStatus HealthMonitor::performFullHealthCheck() {
}
unsigned long elapsed = millis() - startTime;
- LOG_VERBOSE("π Health check completed: %d systems in %lums", checkedSystems, elapsed);
+ LOG_VERBOSE(TAG, "π Health check completed: %d systems in %lums", checkedSystems, elapsed);
return calculateOverallHealth();
}
@@ -228,7 +237,7 @@ HealthStatus HealthMonitor::checkSubsystemHealth(const String& subsystemName) {
// Perform health check on specific subsystem
auto it = _subsystemHealth.find(subsystemName);
if (it == _subsystemHealth.end()) {
- LOG_WARNING("β Unknown subsystem: %s", subsystemName.c_str());
+ LOG_WARNING(TAG, "β Unknown subsystem: %s", subsystemName.c_str());
return HealthStatus::FAILED;
}
@@ -256,7 +265,7 @@ HealthStatus HealthMonitor::checkSubsystemHealth(const String& subsystemName) {
} else if (subsystemName == "FileManager" && _fileManager) {
healthy = _fileManager->isHealthy();
} else {
- LOG_WARNING("π Subsystem %s not connected to health monitor", subsystemName.c_str());
+ LOG_WARNING(TAG, "π Subsystem %s not connected to health monitor", subsystemName.c_str());
return HealthStatus::FAILED;
}
@@ -382,7 +391,7 @@ void HealthMonitor::updateSubsystemHealth(const String& name, HealthStatus statu
it->second.lastError = error;
it->second.lastCheck = millis();
- LOG_VERBOSE("π %s: %s %s",
+ LOG_VERBOSE(TAG, "π %s: %s %s",
name.c_str(),
healthStatusToString(status).c_str(),
error.isEmpty() ? "" : ("(" + error + ")").c_str());
diff --git a/vesper/src/InputManager/InputManager.cpp b/vesper/src/InputManager/InputManager.cpp
index f1a33d1..074973c 100644
--- a/vesper/src/InputManager/InputManager.cpp
+++ b/vesper/src/InputManager/InputManager.cpp
@@ -1,4 +1,6 @@
#include "InputManager.hpp"
+
+#define TAG "InputManager"
#include "../Logging/Logging.hpp"
// Static instance pointer
@@ -28,7 +30,7 @@ InputManager::~InputManager() {
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
bool InputManager::begin() {
- LOG_INFO("InputManager: Initializing input handling system");
+ LOG_INFO(TAG, "InputManager: Initializing input handling system");
// Configure factory reset button
configureButton(_factoryResetButton.config);
@@ -51,13 +53,13 @@ bool InputManager::begin() {
);
if (result != pdPASS) {
- LOG_ERROR("InputManager: Failed to create input task!");
+ LOG_ERROR(TAG, "InputManager: Failed to create input task!");
return false;
}
_initialized = true;
- LOG_INFO("InputManager: Initialization complete - Factory Reset on GPIO 0 (Task running)");
+ LOG_INFO(TAG, "InputManager: Initialization complete - Factory Reset on GPIO 0 (Task running)");
return true;
}
@@ -65,7 +67,7 @@ void InputManager::end() {
if (_inputTaskHandle != nullptr) {
vTaskDelete(_inputTaskHandle);
_inputTaskHandle = nullptr;
- LOG_INFO("InputManager: Input task stopped");
+ LOG_INFO(TAG, "InputManager: Input task stopped");
}
_initialized = false;
}
@@ -76,12 +78,12 @@ void InputManager::end() {
void InputManager::setFactoryResetPressCallback(ButtonCallback callback) {
_factoryResetButton.config.onPress = callback;
- LOG_DEBUG("InputManager: Factory reset press callback registered");
+ LOG_DEBUG(TAG, "InputManager: Factory reset press callback registered");
}
void InputManager::setFactoryResetLongPressCallback(ButtonCallback callback) {
_factoryResetButton.config.onLongPress = callback;
- LOG_DEBUG("InputManager: Factory reset long press callback registered");
+ LOG_DEBUG(TAG, "InputManager: Factory reset long press callback registered");
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@@ -101,7 +103,7 @@ uint32_t InputManager::getFactoryResetPressDuration() const {
bool InputManager::isHealthy() const {
if (!_initialized) {
- LOG_DEBUG("InputManager: Unhealthy - not initialized");
+ LOG_DEBUG(TAG, "InputManager: Unhealthy - not initialized");
return false;
}
@@ -116,7 +118,7 @@ bool InputManager::isHealthy() const {
void InputManager::inputTaskFunction(void* parameter) {
InputManager* manager = static_cast(parameter);
- LOG_INFO("InputManager: Input task started (polling every %dms)", INPUT_POLL_RATE_MS);
+ LOG_INFO(TAG, "InputManager: Input task started (polling every %dms)", INPUT_POLL_RATE_MS);
TickType_t lastWakeTime = xTaskGetTickCount();
const TickType_t pollInterval = pdMS_TO_TICKS(INPUT_POLL_RATE_MS);
@@ -155,7 +157,7 @@ void InputManager::configureButton(const ButtonConfig& config) {
pinMode(config.pin, INPUT_PULLUP);
}
- LOG_DEBUG("InputManager: Configured GPIO %d as input (%s)",
+ LOG_DEBUG(TAG, "InputManager: Configured GPIO %d as input (%s)",
config.pin, config.activeHigh ? "active-high" : "active-low");
}
@@ -182,7 +184,7 @@ void InputManager::updateButton(ButtonData& button) {
// Button just pressed - start debouncing
button.state = ButtonState::DEBOUNCING_PRESS;
button.stateChangeTime = now;
- LOG_DEBUG("InputManager: Button press detected on GPIO %d - debouncing",
+ LOG_DEBUG(TAG, "InputManager: Button press detected on GPIO %d - debouncing",
button.config.pin);
}
break;
@@ -192,14 +194,14 @@ void InputManager::updateButton(ButtonData& button) {
if (!currentState) {
// Button released during debounce - false trigger
button.state = ButtonState::IDLE;
- LOG_DEBUG("InputManager: False trigger on GPIO %d (released during debounce)",
+ LOG_DEBUG(TAG, "InputManager: False trigger on GPIO %d (released during debounce)",
button.config.pin);
} else if (now - button.stateChangeTime >= button.config.debounceMs) {
// Debounce time passed - confirm press
button.state = ButtonState::LONG_PRESS_PENDING;
button.pressStartTime = now;
button.longPressTriggered = false;
- LOG_INFO("InputManager: Button press confirmed on GPIO %d",
+ LOG_INFO(TAG, "InputManager: Button press confirmed on GPIO %d",
button.config.pin);
}
break;
@@ -210,14 +212,14 @@ void InputManager::updateButton(ButtonData& button) {
// Button released before long press threshold - it's a short press
button.state = ButtonState::DEBOUNCING_RELEASE;
button.stateChangeTime = now;
- LOG_INFO("InputManager: Short press detected on GPIO %d (held for %lums)",
+ LOG_INFO(TAG, "InputManager: Short press detected on GPIO %d (held for %lums)",
button.config.pin, now - button.pressStartTime);
} else if (now - button.pressStartTime >= button.config.longPressMs) {
// Long press threshold reached
button.state = ButtonState::LONG_PRESSED;
button.longPressTriggered = true;
- LOG_WARNING("InputManager: LONG PRESS DETECTED on GPIO %d (held for %lums)",
+ LOG_WARNING(TAG, "InputManager: LONG PRESS DETECTED on GPIO %d (held for %lums)",
button.config.pin, now - button.pressStartTime);
// Trigger long press callback
@@ -232,7 +234,7 @@ void InputManager::updateButton(ButtonData& button) {
if (!currentState) {
button.state = ButtonState::DEBOUNCING_RELEASE;
button.stateChangeTime = now;
- LOG_INFO("InputManager: Long press released on GPIO %d (total duration: %lums)",
+ LOG_INFO(TAG, "InputManager: Long press released on GPIO %d (total duration: %lums)",
button.config.pin, now - button.pressStartTime);
}
break;
@@ -242,7 +244,7 @@ void InputManager::updateButton(ButtonData& button) {
if (currentState) {
// Button pressed again during release debounce - go back to pressed state
button.state = ButtonState::LONG_PRESS_PENDING;
- LOG_DEBUG("InputManager: Button re-pressed during release debounce on GPIO %d",
+ LOG_DEBUG(TAG, "InputManager: Button re-pressed during release debounce on GPIO %d",
button.config.pin);
} else if (now - button.stateChangeTime >= button.config.debounceMs) {
// Debounce time passed - confirm release
@@ -250,12 +252,12 @@ void InputManager::updateButton(ButtonData& button) {
// If it was a short press (not long press), trigger the press callback
if (!button.longPressTriggered && button.config.onPress) {
- LOG_INFO("InputManager: Triggering press callback for GPIO %d",
+ LOG_INFO(TAG, "InputManager: Triggering press callback for GPIO %d",
button.config.pin);
button.config.onPress();
}
- LOG_DEBUG("InputManager: Button release confirmed on GPIO %d",
+ LOG_DEBUG(TAG, "InputManager: Button release confirmed on GPIO %d",
button.config.pin);
}
break;
diff --git a/vesper/src/Logging/Logging.cpp b/vesper/src/Logging/Logging.cpp
index 5e97461..fa39760 100644
--- a/vesper/src/Logging/Logging.cpp
+++ b/vesper/src/Logging/Logging.cpp
@@ -1,142 +1,251 @@
#include "Logging.hpp"
-// Initialize static members
-Logging::LogLevel Logging::currentLevel = Logging::VERBOSE; // Default to VERBOSE
-Logging::LogLevel Logging::mqttLogLevel = Logging::NONE; // Default MQTT logs OFF
-Logging::MqttPublishCallback Logging::mqttPublishCallback = nullptr;
-String Logging::mqttLogTopic = "";
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// STATIC MEMBER INITIALIZATION
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-void Logging::setLevel(LogLevel level) {
- currentLevel = level;
- Serial.printf("[LOGGING] Log level set to %d\n", level);
+Logging::LogLevel Logging::_serialLevel = Logging::VERBOSE;
+Logging::LogLevel Logging::_mqttLevel = Logging::NONE;
+Logging::LogLevel Logging::_sdLevel = Logging::NONE;
+
+std::map Logging::_serialOverrides;
+std::map Logging::_mqttOverrides;
+std::map Logging::_sdOverrides;
+
+Logging::MqttPublishCallback Logging::_mqttCallback = nullptr;
+Logging::SdWriteCallback Logging::_sdCallback = nullptr;
+String Logging::_mqttLogTopic = "";
+
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// GLOBAL CHANNEL LEVEL SETTERS / GETTERS
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+void Logging::setSerialLevel(LogLevel level) {
+ _serialLevel = level;
+ Serial.printf("[Logger] Serial level -> %d\n", level);
}
-Logging::LogLevel Logging::getLevel() {
- return currentLevel;
+void Logging::setMqttLevel(LogLevel level) {
+ _mqttLevel = level;
+ Serial.printf("[Logger] MQTT level -> %d\n", level);
}
-void Logging::setMqttLogLevel(LogLevel level) {
- mqttLogLevel = level;
- Serial.printf("[LOGGING] MQTT log level set to %d\n", level);
+void Logging::setSdLevel(LogLevel level) {
+ _sdLevel = level;
+ Serial.printf("[Logger] SD level -> %d\n", level);
}
-Logging::LogLevel Logging::getMqttLogLevel() {
- return mqttLogLevel;
+Logging::LogLevel Logging::getSerialLevel() { return _serialLevel; }
+Logging::LogLevel Logging::getMqttLevel() { return _mqttLevel; }
+Logging::LogLevel Logging::getSdLevel() { return _sdLevel; }
+
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// PER-SUBSYSTEM OVERRIDES
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+void Logging::setSubsystemSerialLevel(const char* tag, LogLevel level) {
+ _serialOverrides[String(tag)] = level;
}
+void Logging::setSubsystemMqttLevel(const char* tag, LogLevel level) {
+ _mqttOverrides[String(tag)] = level;
+}
+
+void Logging::setSubsystemSdLevel(const char* tag, LogLevel level) {
+ _sdOverrides[String(tag)] = level;
+}
+
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// CALLBACK REGISTRATION
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
void Logging::setMqttPublishCallback(MqttPublishCallback callback, const String& logTopic) {
- mqttPublishCallback = callback;
- mqttLogTopic = logTopic;
- Serial.printf("[LOGGING] MQTT publish callback registered for topic: %s\n", logTopic.c_str());
+ _mqttCallback = callback;
+ _mqttLogTopic = logTopic;
+ Serial.printf("[Logger] MQTT publish callback registered: %s\n", logTopic.c_str());
}
+void Logging::setSdWriteCallback(SdWriteCallback callback) {
+ _sdCallback = callback;
+ Serial.printf("[Logger] SD write callback registered\n");
+}
+
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// PUBLIC LOGGING FUNCTIONS
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+void Logging::error(const char* tag, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ log(ERROR, "ERROR", tag, format, args);
+ va_end(args);
+}
+
+void Logging::warning(const char* tag, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ log(WARNING, "WARN", tag, format, args);
+ va_end(args);
+}
+
+void Logging::info(const char* tag, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ log(INFO, "INFO", tag, format, args);
+ va_end(args);
+}
+
+void Logging::debug(const char* tag, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ log(DEBUG, "DEBG", tag, format, args);
+ va_end(args);
+}
+
+void Logging::verbose(const char* tag, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ log(VERBOSE, "VERB", tag, format, args);
+ va_end(args);
+}
+
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// UTILITIES
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
bool Logging::isLevelEnabled(LogLevel level) {
- return currentLevel >= level;
+ return _serialLevel >= level;
}
-void Logging::error(const char* format, ...) {
- va_list args;
- va_start(args, format);
- log(ERROR, "π΄ EROR", format, args);
- va_end(args);
+String Logging::levelToString(LogLevel level) {
+ switch (level) {
+ case ERROR: return "ERROR";
+ case WARNING: return "WARNING";
+ case INFO: return "INFO";
+ case DEBUG: return "DEBUG";
+ case VERBOSE: return "VERBOSE";
+ default: return "NONE";
+ }
}
-void Logging::warning(const char* format, ...) {
- va_list args;
- va_start(args, format);
- log(WARNING, "π‘ WARN", format, args);
- va_end(args);
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// PRIVATE: RESOLVE EFFECTIVE LEVEL FOR A TAG ON A CHANNEL
+// Returns the override level if one exists for this tag, otherwise the global level.
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+Logging::LogLevel Logging::resolveLevel(const char* tag, LogLevel globalLevel, const std::map& overrides) {
+ auto it = overrides.find(String(tag));
+ if (it != overrides.end()) {
+ return it->second;
+ }
+ return globalLevel;
}
-void Logging::info(const char* format, ...) {
- va_list args;
- va_start(args, format);
- log(INFO, "π’ INFO", format, args);
- va_end(args);
-}
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// PRIVATE: CORE LOG DISPATCH
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-void Logging::debug(const char* format, ...) {
- va_list args;
- va_start(args, format);
- log(DEBUG, "π DEBG", format, args);
- va_end(args);
-}
+void Logging::log(LogLevel level, const char* levelStr, const char* tag, const char* format, va_list args) {
+ // Resolve effective level for each channel (override wins over global)
+ LogLevel serialEffective = resolveLevel(tag, _serialLevel, _serialOverrides);
+ LogLevel mqttEffective = resolveLevel(tag, _mqttLevel, _mqttOverrides);
+ LogLevel sdEffective = resolveLevel(tag, _sdLevel, _sdOverrides);
-void Logging::verbose(const char* format, ...) {
- va_list args;
- va_start(args, format);
- log(VERBOSE, "π§Ύ VERB", format, args);
- va_end(args);
-}
+ bool serialEnabled = (serialEffective >= level);
+ bool mqttEnabled = (mqttEffective >= level) && (_mqttCallback != nullptr);
+ bool sdEnabled = (sdEffective >= level) && (_sdCallback != nullptr);
-void Logging::log(LogLevel level, const char* levelStr, const char* format, va_list args) {
- // Check if ANY output needs this log level
- bool serialEnabled = (currentLevel >= level);
- bool mqttEnabled = (mqttLogLevel >= level && mqttPublishCallback);
- // bool sdEnabled = (sdLogLevel >= level && sdLogCallback); // Future: SD logging
-
- // Early exit if no outputs need this message
- if (!serialEnabled && !mqttEnabled) {
+ // Early exit if nothing will output this message
+ if (!serialEnabled && !mqttEnabled && !sdEnabled) {
return;
}
- // Format the message once (only if at least one output needs it)
+ // Format the message once
char buffer[512];
vsnprintf(buffer, sizeof(buffer), format, args);
- // Serial output (independent check)
+ // Serial output
if (serialEnabled) {
- Serial.printf("[%s] ", levelStr);
- Serial.print(buffer);
- Serial.println();
+ Serial.printf("[%s][%s] %s\n", levelStr, tag, buffer);
}
- // MQTT output (independent check)
+ // MQTT output
if (mqttEnabled) {
- publishToMqtt(level, levelStr, buffer);
+ publishToMqtt(level, levelStr, tag, buffer);
}
- // Future: SD logging would go here with its own independent check
+ // SD output
+ if (sdEnabled) {
+ writeToSd(level, levelStr, tag, buffer);
+ }
}
-void Logging::publishToMqtt(LogLevel level, const char* levelStr, const char* message) {
- if (!mqttPublishCallback || mqttLogTopic.isEmpty()) {
- return;
- }
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// PRIVATE: MQTT PUBLISH
+// Uses a re-entrancy guard to prevent log-of-a-log recursion.
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+void Logging::publishToMqtt(LogLevel level, const char* levelStr, const char* tag, const char* message) {
+ if (!_mqttCallback || _mqttLogTopic.isEmpty()) return;
- // CRITICAL: Prevent infinite recursion if MQTT publish fails
- // Temporarily disable MQTT logging during publish to avoid cascading errors
static bool isPublishing = false;
- if (isPublishing) {
- return; // Already publishing, don't create recursive log loop
- }
-
+ if (isPublishing) return;
isPublishing = true;
- // Build JSON manually to minimize stack usage (no StaticJsonDocument)
- // Format: {"level":"π’ INFO","message":"text","timestamp":12345}
+ // JSON: {"level":"WARNING","subsystem":"BellEngine","message":"...","timestamp":12345}
String payload;
- payload.reserve(600); // Pre-allocate to avoid fragmentation
-
+ payload.reserve(600);
payload = "{\"level\":\"";
payload += levelStr;
+ payload += "\",\"subsystem\":\"";
+ payload += tag;
payload += "\",\"message\":\"";
- // Escape special JSON characters in message
- String escapedMsg = message;
- escapedMsg.replace("\\", "\\\\");
- escapedMsg.replace("\"", "\\\"");
- escapedMsg.replace("\n", "\\n");
- escapedMsg.replace("\r", "\\r");
+ // Escape special JSON characters
+ const char* p = message;
+ while (*p) {
+ char c = *p++;
+ if (c == '\\') payload += "\\\\";
+ else if (c == '"') payload += "\\\"";
+ else if (c == '\n') payload += "\\n";
+ else if (c == '\r') payload += "\\r";
+ else payload += c;
+ }
- payload += escapedMsg;
payload += "\",\"timestamp\":";
payload += millis();
payload += "}";
- // Publish with QoS 1 (guaranteed delivery)
- // Note: If this fails, it won't trigger another MQTT log due to isPublishing flag
- mqttPublishCallback(mqttLogTopic, payload, 1);
+ _mqttCallback(_mqttLogTopic, payload, 1);
isPublishing = false;
}
+
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// PRIVATE: SD WRITE
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+void Logging::writeToSd(LogLevel level, const char* levelStr, const char* tag, const char* message) {
+ if (!_sdCallback) return;
+
+ static bool isWriting = false;
+ if (isWriting) return;
+ isWriting = true;
+
+ // Plain text line: [WARN][BellEngine] message (timestamp: 12345ms)
+ String line;
+ line.reserve(300);
+ line = "[";
+ line += levelStr;
+ line += "][";
+ line += tag;
+ line += "] ";
+ line += message;
+ line += " (";
+ line += millis();
+ line += "ms)";
+
+ _sdCallback(line);
+
+ isWriting = false;
+}
diff --git a/vesper/src/Logging/Logging.hpp b/vesper/src/Logging/Logging.hpp
index 1811f8d..7ac402b 100644
--- a/vesper/src/Logging/Logging.hpp
+++ b/vesper/src/Logging/Logging.hpp
@@ -1,14 +1,28 @@
/*
* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- * LOGGING.HPP - Centralized Logging System
+ * LOGGING.HPP - Subsystem-Aware Centralized Logging System
* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- *
+ *
* π THE INFORMATION CHRONICLER OF VESPER π
- *
- * This header provides a unified logging interface with multiple levels,
- * timestamps, and comprehensive debugging support throughout the system.
- *
- * π VERSION: 2.0 (Enhanced logging system)
+ *
+ * Three independent output channels, each with their own level:
+ * β’ Serial β USB debugging, local connection
+ * β’ MQTT β Remote troubleshooting via web dashboard
+ * β’ SD β Persistent log storage for post-mortem analysis
+ *
+ * Per-subsystem filtering: each subsystem tag can have its own level
+ * overrides per channel. If no override is set, the global channel
+ * level applies. Set a tag's level to NONE on a specific channel to
+ * silence it entirely on that channel (e.g. MQTT internals on MQTT).
+ *
+ * Usage in each .cpp file:
+ * #define TAG "BellEngine" // one line at the top
+ * LOG_INFO(TAG, "Ring scheduled"); // all calls include the tag
+ *
+ * The JSON payload sent over MQTT includes the subsystem field:
+ * {"level":"WARNING","subsystem":"BellEngine","message":"...","timestamp":12345}
+ *
+ * π VERSION: 3.0 (Subsystem-aware logging)
* π
DATE: 2025
* π¨βπ» AUTHOR: Advanced Bell Systems
* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@@ -18,67 +32,114 @@
#define LOGGING_HPP
#include
-
-// Forward declaration
-class MQTTAsyncClient;
+#include