Releases: SenaxInc/ArduinoSMSTankAlarm
TankAlarm Beta v1.2.3
What's Changed
- Fix missing forward declaration for
sendSecurityHeadersin Server sketch by @Copilot in #283
Full Changelog: v1.2.1...v1.2.3
TankAlarm Beta v1.2.1
Full Changelog: v1.2.0...v1.2.1
TankAlarm Beta v1.2.0
Full Changelog: v1.1.8...v1.2.0
TankAlarm Beta v1.1.8
Release Notes — v1.1.8
Date: March 16, 2026
Components: Server Firmware (TankAlarm-112025-Server-BluesOpta)
Status: Stable Release
Previous Release: v1.1.7 (March 15, 2026)
Summary
v1.1.8 is a data management hardening release. It addresses 9 reliability risks identified during a comprehensive audit of the server's 3-tier historical data infrastructure (hot/warm/cold), adds a 3-layer stale sensor auto-pruning system, and introduces FTP archival of removed clients with a browseable "Archived Clients" section in the Historical Data page.
No API breaking changes. Existing on-flash configs and history files are fully compatible.
🛡️ Data Integrity Fixes
Fix #1 — Save All Dirty Data Before DFU/Reboot
- Before: Only
gConfigDirtywas checked before reboot — tank registry, client metadata, hot tier snapshots, and history settings could be lost on DFU update or software reboot. - After: The DFU handler now saves all dirty data:
gConfigDirty,gTankRegistryDirty,gClientMetadataDirty, hot tier snapshot, client config snapshots, and history settings. - Impact: Eliminates data loss window during firmware updates.
Fix #2 — Deduplicate Daily Summaries on Reboot
- Before:
gLastDailyRollupDatewas not persisted. On reboot, the daily rollup could run again for the same day, producing duplicate entries indaily_YYYYMM.jsonfiles. - After: Two changes:
gLastDailyRollupDatepersisted inhistory_settings.json(key"lastRollup")rollupDailySummaries()now removes existing entries matchingyesterdayDatebefore appending new data
- Impact: Prevents inflated daily summary values after reboots.
Fix #6 — Persist SMS Rate-Limit State
- Before:
lastSmsAlertEpochandsmsAlertsInLastHourwere RAM-only. A reboot reset the rate limiter, allowing a burst of duplicate SMS alerts. - After: Both fields serialized in
tank_registry.json(keys"se"and"sa"). Restored on boot. - Impact: SMS rate limiting survives power cycles.
💾 Backup & Recovery Improvements
Fix #3 — Expanded FTP Backup List
Added 3 critical files to the nightly FTP backup:
| File | Content |
|---|---|
tank_registry.json |
All sensor records, calibration data, alarm state |
client_metadata.json |
Client device metadata and health info |
history_settings.json |
History tier configuration, last rollup date |
Backup list now contains 9 files total (was 6).
Fix #4 — Expanded .tmp Orphan Recovery
Added 2 files to recoverOrphanedTmpFiles():
| File | Risk |
|---|---|
/fs/history/hot_tier.json |
Loss of up to 90 days of hourly telemetry snapshots |
/fs/archived_clients.json |
Loss of FTP archive manifest (archived data still exists on FTP) |
Recovery list now contains 11 files total (was 9).
📊 Historical Data Improvements
Fix #5 — Warm Tier Fallback in FTP Monthly Archives
- Before:
archiveMonthToFtp()only read the hot tier ring buffers. If a month had rolled off the hot tier, the FTP archive was empty. - After: When the hot tier has no data for a target month, the function loads warm tier daily summaries via
loadDailySummaryMonth()and aggregates per-tank monthly statistics. These entries are tagged"dataSource": "warm"in the archive JSON. - Impact: Cold-tier FTP archives now capture data even after the hot tier ring buffer has cycled.
Fix #7 — Wire ftpSyncHour Into Maintenance Check
- Before:
gHistorySettings.ftpSyncHourexisted in the settings struct and was configurable via the API, but the actual maintenance check only testedif (nowTm)— running at any hour. - After: Changed to
if (nowTm && nowTm->tm_hour == gHistorySettings.ftpSyncHour). - Impact: FTP archive runs at the configured hour instead of every hour.
Fix #8 — Warning When History Slots Exhausted
- Before: When all 20
gTankHistory[]slots were occupied,recordTelemetrySnapshot()silently discarded telemetry for unmapped sensors. - After: Rate-limited warning (every 5 minutes) logged to both Serial and
addServerSerialLog()with"warn"level. - Impact: Operators get visibility into silent data loss.
Fix #9 — Correct hotTierRetentionDays Default
- Before: Default was 730 days (2 years), but the ring buffer only holds 90 entries per tank. The misleading default suggested far more data was retained than was actually possible.
- After: Default changed to 90 days. Comments on
MAX_HOURLY_HISTORY_PER_TANKupdated to accurately describe the tiered architecture. - Impact: Configuration UI now shows a realistic default.
🧹 Stale Sensor Auto-Pruning
New 3-layer system for automatically cleaning up sensors and clients that stop reporting:
| Layer | Trigger | Threshold | Action |
|---|---|---|---|
| Config-based | Periodic timer | Configurable | Marks stale, prunes if enabled |
| Orphan sensor | Per-sensor check | 72 hours (ORPHAN_SENSOR_PRUNE_SECONDS) |
Removes individual sensors whose client is still alive but stopped reporting them |
| Dead client | Per-client check | 7 days (STALE_CLIENT_PRUNE_SECONDS) |
Removes entire client and all its sensors |
📦 FTP Archive Before Client Removal
When a client is removed (manually or by auto-pruning), the server now:
- Checks tenure: Only archives clients with
firstSeenEpocholder than 30 days (MIN_ARCHIVE_AGE_SECONDS) - Builds archive: Collects all sensor records for the client, serializes to JSON with full telemetry history
- Uploads to FTP: Date-stamped filename:
{ftpPath}/{serverUid}/archived_clients/{Site}_{YYYYMM}-{YYYYMM}_{uidSuffix}.json - Updates local manifest: Appends entry to
/fs/archived_clients.jsonfor fast enumeration
New API Endpoint
| Endpoint | Method | Description |
|---|---|---|
/api/history/archived |
GET | Returns the local manifest listing all archived clients |
/api/history/archived?file=X |
GET | Downloads a specific archive file from FTP |
Input validation enforces safe characters only and blocks path traversal.
Historical Data Page — Archived Clients Section
- New "Archived Clients" card appears when archives exist
- Dropdown to select an archived client
- Chart.js line chart renders archived telemetry data
- All user-provided strings escaped via
escHtml()to prevent XSS
Upgrade Notes
- Compatible with v1.1.7 — no protocol or config format changes
- Server-only changes — Client and Viewer firmware unchanged (version header bumped for consistency)
- Browser cache: Hard-refresh (Ctrl+Shift+R) recommended after upgrade to load the new Archived Clients UI
- No migration needed: New fields (
"se","sa","lastRollup") default to safe zero values if absent from existing JSON files hotTierRetentionDays: If you previously set this to 730 manually, the new 90-day default only applies to fresh installs or factory resets — your configured value is preserved
TankAlarm Beta v1.1.7
Release Notes — v1.1.7
Date: March 15, 2026
Components: Server, Client, Viewer Firmware (TankAlarm-112025-*-BluesOpta)
Status: Stable Release
Previous Release: v1.1.6 (March 13, 2026)
Summary
v1.1.7 completes the sensor-centric naming refactor — replacing all tank-centric numbering conventions with a generic sensorIndex data model that supports tanks, gas monitors, flow meters, and any future monitor types. The release also removes ~50 backward-compatibility fallbacks that were carrying dual-key JSON parsing from the v1.0 → v1.1 migration, and fixes two serialization bugs discovered during the refactor.
No new features. This is a naming consistency and code quality release.
Breaking changes:
- API URL parameters changed:
?tank=→?sensor=on/api/historyand/api/history/yoyendpoints - JSON API responses now emit
"sensorIndex"instead of"tank"for sensor index fields - JSON API alarm responses now emit
"label"instead of"tank"for sensor labels - Backward-compatible dual-key JSON parsing removed — clients must send current key names
🔄 Naming Refactor (Server, Client, Viewer)
Phase 1: tankNumber → sensorIndex + userNumber
- Renamed struct field
tankNumbertosensorIndexacross all three firmware files - Added new
userNumberfield (optional user-assigned display number, wire key"un") - Wire format key
"k"now maps tosensorIndex(previouslytankNumber) - Config wire format key
"number"retained for client↔server protocol compatibility - Impact: Data model is now generic — not coupled to "tank" as a concept
Phase 2: Backward Compatibility Removal
- Removed ~50 dual-key JSON parsing fallbacks across Server, Client, and Viewer
- These were
doc["newKey"] | doc["oldKey"]patterns supporting the v1.0 → v1.1 transition - Examples removed:
"tankNumber"/"number","sensorType"/"type","siteName"/"site","tankLabel"/"name", and many more - Impact: Cleaner parsing code; all nodes must run v1.1+ firmware
Phase 3: Deep Cleanup — C++ Internals
| Category | Changes |
|---|---|
| C++ local variables | tankNum → sensorIdx in 7 functions (~20 instances) |
| C++ local variables | tankFilter → sensorFilter, filterTank → filterSensorIdx, tankParam → sensorParam |
| C++ local variables | uint8_t tank → uint8_t sensorIdx in populateStatsFromDailySummary() |
| C++ JSON keys | tankObj["tank"] → tankObj["sensorIndex"] in 8 locations (FTP archive, history API, daily email, month comparison, YoY summary) |
| C++ JSON key | alarmObj["tank"] → alarmObj["label"] in contacts/alarm API |
| Comments | 9 comments updated from "tank number(s)" to "sensor index/indices" |
Phase 4: Deep Cleanup — JavaScript (Server Embedded Pages)
| Page | Changes |
|---|---|
| Calibration | tank:t.k → sensorIndex:t.k in loadTanks mapper + all downstream .tank → .sensorIndex (~12 spots) |
| Dashboard | tank:t.k → sensorIndex:t.k in buildSiteModel + sparkline keys (5 spots) |
| Client Console | tank:c.k → sensorIndex:c.k in normalizeApiData (2 spots) |
| Site Config | tank:c.k/t.k → sensorIndex:c.k/t.k + fallback push (3 spots) |
| Contacts | alarm.tank → alarm.label (3 spots: updateFilters, renderContacts, renderAlarmAssociations) |
Phase 5: URL Parameter Rename
/api/historyquery parameter?tank=CLIENT:NUM→?sensor=CLIENT:NUM/api/history/yoyquery parameter?tank=CLIENT:TANK→?sensor=CLIENT:SENSOR_INDEX- No existing JS callers used these params directly (history page uses
?days=only), so no JS changes needed
🐛 Bug Fixes
Client saveConfigToFlash() Missing monitorType
saveConfigToFlash()never serializedmonitorTypeto flash storage- After reboot, all monitors defaulted to
"tank"regardless of actual type - Now correctly writes and restores
monitorTypefor each monitor - Impact: Gas monitors, flow meters, and other types survive power cycles
Client serializeConfig() sensorIndex Fix
- Fixed
serializeConfig()to emit the correctsensorIndexvalue in config JSON - Impact: Config export/import now round-trips sensor indices correctly
📄 Documentation Updates
8 documentation files updated to reflect new naming:
| File | Changes |
|---|---|
CODE REVIEW/CODE_REVIEW_02062026.md |
uint8_t tankNumber → sensorIndex in code example |
CODE REVIEW/H4_ATOMIC_WRITE_REFACTOR_RECOMMENDATION.md |
cal.tankNumber → cal.sensorIndex (2 code blocks) |
CODE REVIEW/REVIEW_RESULTS_112025.md |
.tankNumber == tankNumber → .sensorIndex == sensorIndex |
CODE REVIEW/V1.1.6_RELEASE_NOTES.md |
clientUid + tankNumber → clientUid + sensorIndex |
Tutorials/BACKUP_RECOVERY_GUIDE.md |
"tank": "A" → "sensorIndex": "A" in JSON example |
Tutorials/DASHBOARD_GUIDE.md |
"tank": "A" → "sensorIndex": "A" in API response example |
Tutorials/SENSOR_CALIBRATION_GUIDE.md |
"tank": "A" → "sensorIndex": "A" in export example |
HISTORICAL_DATA_ARCHITECTURE.md |
?tank= → ?sensor= param docs, "tank": 1 → "sensorIndex": 1 in JSON examples |
✅ Verified API Consistency
All 12 API endpoints audited and confirmed consistent:
| Endpoint | Key Format | Status |
|---|---|---|
GET /api/tanks |
Shorthand ("k" → sensorIndex) |
✅ |
GET /api/clients |
Shorthand ("k" → sensorIndex) |
✅ |
GET /api/history |
Long-form "sensorIndex" |
✅ |
GET /api/history/compare |
Long-form "sensorIndex" |
✅ |
GET /api/history/yoy |
Long-form "sensorIndex" |
✅ |
GET /api/calibration |
Long-form "sensorIndex" |
✅ |
GET /api/contacts |
"label" for alarm entries |
✅ |
GET /api/unloads |
Shorthand ("k", "n") |
✅ |
POST /api/debug/tanks |
Shorthand ("k") |
✅ |
| Daily email builder | Long-form "sensorIndex" |
✅ |
| FTP archive write | Long-form "sensorIndex" |
✅ |
| Config generator | Config schema (independent) | ✅ |
Acceptable Remaining tank References
The following uses of "tank" were reviewed and intentionally left unchanged:
| Pattern | Rationale |
|---|---|
TankRecord, TankCalibration, TankHourlyHistory structs |
Established type names — separate major refactor scope |
gTankRegistry, gTankRecords, findTankByHash() globals |
Same — type/global naming |
.tank-card, .tank-grid, .tank-row CSS classes |
UI styling — domain vocabulary |
objectType = "tank" value |
Domain model constant — identifies monitor type |
doc["tank"] container in YoY detail response |
JSON object grouping key, not an index value |
HTML element IDs (tankFilter, tankSelect, logTankFilter) |
UI component identifiers |
refreshTank() JS function |
Refreshes a client, named for domain context |
Config wire protocol key "number" |
Shared client↔server format — coordinated change deferred |
Upgrade Notes
- All nodes must run v1.1.7 — backward-compatible JSON parsing was removed
- Browser cache: Users should hard-refresh (Ctrl+Shift+R) after upgrade to load updated JavaScript
- No config file changes needed — on-flash configs remain compatible
- FTP archives: Historical archive files written by v1.1.6 use
"tank"key; v1.1.7 writes"sensorIndex". The history page handles both (reads from stored files as-is)
Full Changelog: v1.1.6...v1.1.7
TankAlarm Beta v1.1.6
What's Changed
- Fix Server sketch compilation: missing forward declaration, orphaned function body, and security hardening by @Copilot in #274
- Fix: VIN_ANALOG_PIN undeclared in generateSessionToken() — improved entropy and pin configurability by @Copilot in #276
- Fix: Add missing forward declaration for
deriveMacFromUidin Viewer sketch by @Copilot in #281
Full Changelog: v1.1.5...v1.1.6
TankAlarm Beta v1.1.5
What's Changed
- Fix: Add missing forward declaration for pruneOrphanedTankRecords by @Copilot in #272
Full Changelog: v1.1.4...v1.1.5
TankAlarm Beta v1.1.4
Release Notes — v1.1.4
Date: February 28, 2026
Components: Client, Server, Viewer, and I2C Utility Firmware (TankAlarm-112025-*-BluesOpta)
Status: Stable Release
Previous Release: v1.1.3 (February 23, 2026)
Code Review: CODE_REVIEW_FINAL_V1.1.4_02282026.md
Summary
v1.1.4 is a reliability and hardening release that resolves all 14 "IMPLEMENT" recommendations from the 02/26/2026 code review. It ships the remaining improvements developed during the 02/26–02/28 cycle: a non-blocking pulse sampler, config versioning, global alarm cap, remote-tunable power thresholds, per-relay timeout tracking, and comprehensive I2C hardening across all sketches. Additionally, four new watchdog safety-margin fixes and one I2C timeout consistency fix close the last identified gaps.
No new features are introduced. No breaking changes. All existing configurations are fully backward-compatible.
🔧 Reliability Improvements (New in v1.1.4)
Watchdog Safety Hardening
-
Viewer & Server: Watchdog kick in I2C bus recovery
tankalarm_recoverI2CBus()now receives a watchdog kick callback in both the Viewer and Server sketches, matching the existing Client pattern. Prevents potential watchdog timeout if the I2C bus is severely hung during recovery. -
Viewer: Watchdog kick + safety cap in
fetchViewerSummary()
Thewhile (true)loop that drains queued Notecard summary notes now kicks the watchdog on each iteration and caps processing at 20 notes per call. After a prolonged outage with 10+ queued summaries, this prevents watchdog starvation. Remaining notes drain on the next loop cycle. -
Client: Watchdog kick in
trimTelemetryOutbox()
Added watchdog kicks at the top of the outer multi-pass while loop and inside the innernote.deletefor-loop. Worst-case scenario (10 passes × 16 I2C transactions × 200ms each = 32s) previously exceeded the 30-second watchdog window.
I2C Bus Timeout Consistency
- I2C Utility: Added
Wire.setTimeout(I2C_WIRE_TIMEOUT_MS)afterWire.begin()
All three production sketches (Client, Server, Viewer) already set the I2C wire timeout to prevent indefinite blocking. The I2C Utility was the only sketch missing this guard, which could cause hangs during interactive diagnostics with a misbehaving device.
🔄 Improvements Shipped (Developed 02/26–02/28, Released in v1.1.4)
These items were implemented during the v1.1.3→v1.1.4 development cycle and passed code review:
Client
| Feature | Description |
|---|---|
| Non-blocking pulse sampler | PulseSamplerContext state machine replaces blocking pulse measurement |
| Config versioning | configSchemaVersion field in ClientConfig for forward-compatible config updates |
| Global alarm cap | gGlobalAlarmTimestamps[30] with hourly rolling window prevents alarm flooding |
| Remote-tunable power thresholds | powerEcoEnterV, powerCriticalEnterV, etc. configurable from server |
| Per-relay timeout tracking | gRelayActivationTime[MAX_RELAYS] prevents stuck-relay conditions |
| Solar battery-failure 24h decay | SOLAR_BAT_FAIL_DECAY_MS timestamp tracking auto-resets stale failure state |
| Solar state portability | #else paths for STM32 LittleFS ensure cross-platform compatibility |
| Startup debounce max timeout | STARTUP_DEBOUNCE_MAX_WAIT_MS (5 min) caps the debounce window |
| Remote health interval | healthCheckBaseIntervalMs in ClientConfig — server-tunable Notecard health check period |
publishNote stack reduction |
Static char buffer[1024] replaces per-call stack allocation |
| Post-config baseline reset | resetTelemetryBaselines() fires on hardwareChanged to prevent stale data |
| Heap String elimination | Fixed buffers in config save and note flush replace heap String usage |
| Const-correctness pass | Added const to structs like MonitorConfig where mutation isn't needed |
safeSleep() consolidation |
safeSleep() replaces bare RTOS sleep calls, ensuring watchdog kicks |
| Rate-limited state logging | State transition logs rate-limited to avoid serial flooding during boundary oscillation |
All Sketches (via Common Library)
| Feature | Description |
|---|---|
| I2C bus recovery | Shared tankalarm_recoverI2CBus() in TankAlarm_I2C.h with DFU guard |
| I2C startup bus scan | tankalarm_scanI2CBus() runs at boot in all 4 sketches |
| I2C current loop retry | tankalarm_readCurrentLoopMilliamps() with 3 retries and error counting |
| Notecard health check backoff | Exponential backoff 5 min → 80 min in Client, Server, and Viewer |
| I2C error rate alerting | Daily threshold check publishes alarm note when error rate is high |
| I2C error counters in telemetry | i2c_cl_err, i2c_bus_recover fields in health reports |
Notecard begin() idempotency |
Singleton confirmed; tankalarm_ensureNotecardBinding() helper |
| Shared diagnostics library | TankAlarm_Diagnostics.h with tankalarm_freeRam() |
| Atomic flash writes | tankalarm_posix_write_file_atomic() for solar state persistence |
| BatteryData mode string fix | Fixed const char* dangling pointer — now char mode[16] with strlcpy |
📦 Build Results
| Component | Flash | RAM | Status |
|---|---|---|---|
| Client | 279,448 bytes (14%) | 73,312 bytes (14%) | Clean (exit 0) |
| Server | 644,236 bytes (32%) | 265,664 bytes (50%) | Clean (exit 0) |
| Viewer | 250,320 bytes (12%) | 76,176 bytes (14%) | Clean (exit 0) |
| I2C Utility | 166,752 bytes (8%) | 63,376 bytes (12%) | Clean (exit 0) |
Server RAM at 50% is the tightest resource constraint. The respondHtml() triple-copy pattern (identified as P2 in the code review) is the primary contributor to peak runtime RAM. Monitoring recommended.
⚠️ Breaking Changes
None. This release is fully backward-compatible with v1.1.3 configurations.
📋 Known Issues (Deferred to v1.1.5 / v1.2+)
| Priority | Issue | Rationale for Deferral |
|---|---|---|
| P2 | Server respondHtml() triple-copy memory pattern (~75 KB peak for 25 KB pages) |
No crash observed; requires compile-time HTML injection refactor |
| P2 | JSON API response builders use String concatenation |
Low frequency endpoints; migrate to JsonDocument + serializeJson() |
| P2 | Client duplicated monitor config parsing (~180 lines in 2 places) | Maintenance risk, not a runtime issue |
| P2 | Last remaining String usage in pruneNoteBufferIfNeeded() |
Single heap allocation per prune cycle; negligible impact |
| P2 | Viewer missing X-Content-Type-Options: nosniff header |
Read-only LAN kiosk; low attack surface |
| P3 | Server GET endpoints expose data without authentication | Acceptable for LAN-only deployment; must fix before internet exposure |
| P3 | Server fresh-install login accepts blank PIN | First-time setup UX trade-off |
| P3 | PIN-in-localStorage bearer pattern | Standard for embedded LAN UIs; consider session tokens in v1.2+ |
| P3 | Monolithic file architecture (Server 12K lines, Client 7.4K lines) | Arduino IDE build system constraints; shared library approach working well |
| P3 | I2C transaction timing telemetry | Pending field data from new I2C diagnostics before instrumenting |
| P3 | Extract readTankSensor helper functions |
Code quality; no runtime impact |
| P3 | Auto relay de-energize on CRITICAL_HIBERNATE | Requires hardware requirement verification |
📦 Upgrade Instructions
- Flash all four firmware images (Client, Server, Viewer, I2C Utility) to pick up the version bump and hardening fixes.
- No config reset required — existing configs are fully backward-compatible.
- No hardware changes required.
- Clear browser cache or hard-refresh (Ctrl+F5) on the Server web UI to pick up the new version display.
Files Changed (since v1.1.3)
| Component | Files Modified | Summary |
|---|---|---|
| Common library | 2 | Version bump 1.1.3 → 1.1.4 in TankAlarm_Common.h and library.properties |
Client .ino |
1 | Watchdog kicks in trimTelemetryOutbox() (outer loop + inner delete loop), version comment update |
Server .ino |
1 | Watchdog kick lambda in tankalarm_recoverI2CBus(), version comment update |
Viewer .ino |
1 | Watchdog kick lambda in tankalarm_recoverI2CBus(), watchdog kick + 20-note cap in fetchViewerSummary(), version comment update |
I2C Utility .ino |
1 | Added Wire.setTimeout(I2C_WIRE_TIMEOUT_MS) after Wire.begin() |
README.md |
1 | Version bumps (title, badge, checklist, deployment step), release date update |
TankAlarm-112025-BillOfMaterials.md |
1 | Version + date update |
CODE REVIEW/VERSION_LOCATIONS.md |
1 | Current version + table entries updated |
| Documentation | 1 | This release notes file |
Version Locations Updated
All 10 version string locations identified in VERSION_LOCATIONS.md have been updated atomically:
| File | Value |
|---|---|
TankAlarm-112025-Common/src/TankAlarm_Common.h L17 |
#define FIRMWARE_VERSION "1.1.4" |
TankAlarm-112025-Common/library.properties L2 |
version=1.1.4 |
TankAlarm-112025-Server-BluesOpta.ino L2 |
// Version: 1.1.4 |
TankAlarm-112025-Viewer-BluesOpta.ino L3 |
Version: 1.1.4 |
TankAlarm-112025-Client-BluesOpta.ino L3 |
Version: 1.1.4 |
README.md L1 |
# TankAlarm v1.1.4 |
README.md L4 |
**Version:** 1.1.4 |
README.md L342 |
Firmware version 1.1.4 confirmed |
README.md L362 |
Flash all devices with v1.1.4 firmware |
TankAlarm-112025-BillOfMaterials.md L3 |
**Version:** 1.1.4 |
What's Changed
- Fix Arduino compilation error: remove UTF-8 BOM from Server sketch...
TankAlarm Beta v1.1.3
Release Notes — v1.1.3
Date: February 23, 2026
Components: Client, Server, and Viewer Firmware (TankAlarm-112025-*-BluesOpta)
Status: Stable Release
Previous Release: v1.1.1 (February 20, 2026)
Note: v1.1.2 was an internal development version and was not formally released. This release includes all v1.1.2 changes plus new v1.1.3 features.
Summary
v1.1.3 introduces Solar-Only (No Battery) operation mode for installations powered directly by solar panels without battery backup, a configurable Vin voltage divider monitor for accurate battery voltage measurement, a complete voltage monitoring overhaul (renaming card.voltage outputs to reflect the Notecard's regulated rail vs. actual battery voltage), DFU button fixes, and comprehensive energy management bug fixes. Server-side improvements include FTP-enhanced historical data management, dashboard reliability fixes, and HTTP header hardening.
🚀 New Features
Solar-Only (No Battery) Mode (Client + Server)
Full support for installations powered directly by a solar panel with no battery backup. The device only operates during daylight hours and gracefully handles power loss at sunset.
SolarOnlyConfigstruct inTankAlarm_Battery.hwith 10 configurable fields- Startup Debounce: Vin-based (voltage threshold for configurable duration) or timer-based warmup (fallback when no Vin divider installed)
- Sensor Voltage Gating: Skips
sampleTanks()when Vin is below the configurable sensor gate voltage (4-20mA sensors need minimum excitation voltage) - Opportunistic Daily Reports: Sends report immediately on startup if overdue by configurable hours; also honors the standard scheduled time when available
- Sunset Protocol: Detects declining Vin voltage, saves state to flash (
/fs/solar_state.json), flushes pending telemetry viahub.sync, and sends asolar_sunsetalarm to the server - State Persistence: Boot count and last report epoch survive power cycles via LittleFS JSON file
- Battery Failure Fallback: For solar+battery setups, auto-enables solar-only behaviors after N consecutive CRITICAL power readings; sends
battery_failurealarm with SMS escalation; auto-recovers when battery improves - Server Config UI: Two new power source options (
Solar Only — No Battery,Solar Only — No Battery + Vin Monitor), conditional config section with all solar-only parameters, and battery failure fallback section for solar+battery configs
Configurable Vin Voltage Divider Monitor (Client + Server)
Reads actual battery voltage via an external resistor divider wired to an Opta analog input, providing more accurate voltage than the Notecard's card.voltage (which reads the regulated V+ rail).
VinMonitorConfigstruct inTankAlarm_Battery.hwith pin, R1/R2 resistor values, poll intervalreadVinDividerVoltage(): Multi-sample averaging (8 samples, 2ms settling) with divider ratio reverse calculation- Server Config UI: Conditional Vin config section with pin/R1/R2 fields, auto-calculated divider ratio, max readable voltage, and quiescent draw
- Power-state-aware polling: 2x slower in LOW_POWER/CRITICAL states
- Three-source voltage:
getEffectiveBatteryVoltage()now considers Modbus MPPT, Notecard, and Vin divider (conservative — uses lowest reading)
Voltage Monitoring Overhaul (Client)
- Renamed
gServerVoltage→gNotecardVoltageto clarify thatcard.voltagereads the Notecard's 5V regulated rail, NOT the external battery - Added
gInputVoltagefor actual battery voltage from analog input - Added
readAnalogVinVoltage()with voltage divider support and configurable pin/resistor values
🔧 Improvements
FTP-Enhanced Historical Data Management (Server)
- Tiered historical data storage with configurable retention periods
- Transmission log for tracking data delivery status
- Extended history retention policies
Dashboard Reliability (Server)
- Fixed stuck loading spinner on API errors — uses inline style instead of CSS class dependency
- Auto-hide loading overlay with tighter caching
- Improved dashboard loading and refresh error handling
- Dashboard fetch timeout increased for reliability
- Connection: close headers added to all HTTP responses for faster connection recycling
HTTP Header Hardening (Server)
- All HTTP responses now include
Connection: closeheader - Replaced Bearer authentication header with
X-SESSION-TOKENfor API endpoints - Pending config retry interval increased to 60 minutes
Notecard Initialization Fix (Client)
- Fixed Notecard UID retrieval that could fail on first boot
- Properly handles null responses from
card.uuid
🐛 Bug Fixes
Energy Management — 3 Bugs Fixed (v1.1.3)
-
CRITICAL — Runtime solar-only enable permanently disabled sensors: When solar-only mode was enabled via a config update (not a reboot),
gSolarOnlyStartupCompletewas set tofalsebutperformStartupDebounce()is only called fromsetup(). The device would never sample sensors or send reports until rebooted. Fix: Set both startup flags totruesince the device is already running with stable power. -
MODERATE — Battery failure counter false positives:
gSolarOnlyBatFailCountonly reset when the power state recovered to ECO or better. A battery oscillating between CRITICAL and LOW_POWER accumulated false counts, eventually triggering a spurious battery failure fallback. Fix: Reset the counter whenever the device exits CRITICAL (any improvement); only deactivate the fallback flag when fully recovered to ECO or better (two-tier recovery). -
MINOR — Dead global variable: Removed unused
gSolarOnlyDebounceStart(the implementation uses a localstableStartvariable inperformStartupDebounce()instead).
DFU Button Fix (v1.1.2)
- Fixed 5 bugs with firmware update buttons on the server settings page
- DFU enable/disable/check operations now correctly target the intended device
Server Crash Fix (v1.1.2)
- Reduced RAM usage ~215KB to prevent Mbed OS crash on Opta (512KB RAM limit)
- Fixed compile errors (
heightInches→levelIncheson TankRecord, removed duplicate default arg)
UTF-8 BOM Fix (v1.1.2)
- Removed UTF-8 BOM from Server
.inofile that caused compilation failures
📦 Build Results
| Component | Flash | RAM | Status |
|---|---|---|---|
| Server | 641,380 bytes (32%) | 265,648 bytes (50%) | Clean |
| Client | 275,000 bytes (13%) | 73,280 bytes (13%) | Clean |
| Viewer | 248,672 bytes (12%) | 76,160 bytes (14%) | Clean |
⚠️ Breaking Changes
None. This release is backward-compatible with v1.1.1 configurations. Existing config files do not require modification — new solarOnlyConfig and vinMonitor fields use safe defaults when absent.
📦 Upgrade Instructions
- Flash all three firmware images (Client, Server, Viewer) to pick up the version bump.
- No config reset required — existing configs are fully backward-compatible.
- To use Solar-Only mode: Select "Solar Only — No Battery" (or "+ Vin Monitor" variant) in the Server Config Generator and configure the solar parameters.
- To use Vin Monitor: Select a power source with "+ Vin Monitor" suffix, configure analog pin and R1/R2 resistor values, and wire the voltage divider hardware.
- Clear browser cache or hard-refresh (Ctrl+F5) on the Server web UI.
Hardware Requirements for New Features
Vin Voltage Divider (Optional)
Battery+ ──── R1 (22kΩ) ──── Opta Analog Pin ──── R2 (47kΩ) ──── GND
- Default: R1=22kΩ, R2=47kΩ → ratio 0.6812, max readable 14.68V
- Quiescent draw: ~174µA at 12V (negligible)
- Any Opta analog input (A0-A7); avoid conflict with sensor pins
Solar-Only Mode
- Solar panel connected directly to Opta power input (no battery)
- Optional: Vin voltage divider for voltage-based startup debounce, sensor gating, and sunset detection
- Without Vin divider: uses timer-based warmup (configurable, default 60s) and cannot detect sunset
Files Changed (since v1.1.1)
| Component | Files | Summary |
|---|---|---|
| Common library | 2 | SolarOnlyConfig struct, VinMonitorConfig struct, helpers, version bump 1.1.1 → 1.1.3 |
| Client .ino | 1 | Solar-only mode (12 globals, 5 new functions), Vin divider, voltage overhaul, DFU fixes, 3 energy management bug fixes |
| Server .ino | 1 | Config UI (2 power source options, solar-only config section, Vin config section), FTP history, dashboard fixes, HTTP hardening |
| Viewer .ino | 0 | No code changes (inherits version bump from common header) |
| Documentation | 1 | This release notes file |
Commit History (since v1.1.1)
cf345ec Fix 3 energy management bugs found in code review
09cd784 Add Solar-Only (No Battery) mode with startup debounce, sensor gating,
opportunistic reports, sunset protocol, and battery failure fallback
16a6e26 Add configurable Vin voltage divider monitor for client
1ae635a Fix DFU buttons + voltage monitoring overhaul
ef177df Fix: remove UTF-8 BOM from TankAlarm-112025-Server-BluesOpta.ino
971a1e6 Bump project version to 1.1.2
11da738 Fix Notecard UID retrieval, dashboard fetch timeout, Connection: close headers, spinner
a11f989 Add Connection: close header to HTTP responses
a2c3917 Improve dashboard loading & refresh error handling
9f5ea6e Auto-hide loading overlay and tighten caching
0f5ec07 Fix stuck loading spinner: use inline style instead of CSS class dependency
1dfb5ea fix: dashboard spinner stuck on API error + HTTP header improvements
beaae47 feat: FTP-enhanced tiered historical data management
a2fb060 fix: reduce RAM usage ~215KB to prevent Mbed OS crash on Opta
f9f83a5 fix: resolve compile errors
...
TankAlarm Beta v1.1.1
Release Notes — v1.1.1
Date: February 20, 2026
Components: Client, Server, and Viewer Firmware (TankAlarm-112025-*-BluesOpta)
Status: Stable Release
Previous Release: v1.1.0
Summary
v1.1.1 introduces a three-fleet architecture (clients / server / viewer), a new relay-forwarding protocol for client-to-client relay commands via the server, config and serial ACK improvements, and significant code cleanup removing ~300 lines of dead code from the shared library. ArduinoJson usage in the Server and Viewer has been fully migrated to the v7 auto-sizing API.
🚀 New Features
Viewer Fleet Support
- Viewer devices now join a dedicated
tankalarm-viewerfleet duringhub.set, enabling fleet-scoped DFU, route filtering, and device management. - Client
createDefaultConfig()explicitly setsclientFleetto"tankalarm-clients". - Documentation and Notehub route guides updated for the three-fleet model (clients / server / viewer).
Relay Forwarding (Client → Server → Client)
- New relay-forwarding protocol using dedicated
relay_forward.qo/relay_forward.qinotefiles (constants added inTankAlarm_Common.h). - Client:
triggerRemoteRelays()sends viaRELAY_FORWARD_OUTBOX_FILEwith renamed payload fields (target/clientinstead of_target/_type). - Server: New
handleRelayForward()function receives relay-forward notes, parsestarget/client/relay/state/source, and re-issues the command to the target client viasendRelayCommand().
Serial ACK Protocol (Client)
- New
sendSerialAck(status)function sendsserial_ack.qonotes with the client UID and a status string. pollForSerialRequests()now emits"processing"and"complete"ACKs aroundsendSerialLogs().- New
SERIAL_ACK_OUTBOX_FILEconstant inTankAlarm_Common.h.
Config ACK Enhancements
sendConfigAck()now accepts a thirdconfigVersionparameter and includes astatusstring ("applied"/"failed") and optionalcv(config version hash) in the ACK payload.- Server injects
_cvinto dispatched configs fromClientConfigSnapshot::configVersion. - Server keeps
pendingDispatch = trueuntil the ACK is received (previously cleared immediately on send).
🔧 Improvements
ArduinoJson v7 Migration (Server & Viewer)
- All
DynamicJsonDocument/StaticJsonDocumentinstances replaced with auto-sizingJsonDocument(ArduinoJson v7). - Removed capacity constants:
CLIENT_JSON_CAPACITY,HISTORY_JSON_CAPACITY,COMPARE_JSON_CAPACITY,YOY_JSON_CAPACITY,CALIBRATION_JSON_CAPACITY,TANK_JSON_CAPACITY. - Updated API calls:
createNestedArray()→.to<JsonArray>(),createNestedObject()→.add<JsonObject>(). - Overflow error messages updated to remove references to removed capacity constants.
Notecard UUID Handling Hardened (All Three Sketches)
card.uuidrequest now guarded withif (req)null check before callingrequestAndResponse().- Response properly freed via
notecard.deleteResponse(rsp)in the correct scope.
Serial Log Pagination Fix (Client)
sendSerialLogs()now correctly sends the 20 most recent entries by computing astartOffsetinstead of always iterating from index 0.
Shared Utility Consolidation
- Server's local
computeNextAlignedEpoch()replaced by sharedtankalarm_computeNextAlignedEpoch()fromTankAlarm_Utils.h. DFU_CHECK_INTERVAL_MSconsolidated intoTankAlarm_Common.h(was duplicated in both client and server.inofiles).
🐛 Bug Fixes
Memory Safety
- Server:
sendDailyEmail()andpublishViewerSummary()now useJDelete(req)instead ofnotecard.deleteResponse(req)for freeing unsent request objects. - Client:
triggerRemoteRelays()addsJDelete(req)when body allocation fails, preventing a memory leak.
Watchdog Macro Rename
- All
#ifdef WATCHDOG_AVAILABLEcorrected to#ifdef TANKALARM_WATCHDOG_AVAILABLEacross client (6 instances) and server (8 instances), matching the macro defined inTankAlarm_Platform.h.
DFU Auto-Apply Preprocessor Fix (Viewer)
- Changed
#if DFU_AUTO_ENABLEto#ifdef DFU_AUTO_ENABLE(correct preprocessor guard for a potentially-undefined macro).
Temperature Averaging Simplified (Server)
nwsFetchAverageTemperature()simplified — removed unused time-window logic; now averages the firstNWS_AVG_HOURSvalues directly.
🗑️ Dead Code Removal (~300 lines)
TankAlarm_Battery.h
- Removed
getBatteryVoltageMode(),estimateLeadAcidSOC(),estimateLiFePO4SOC().
TankAlarm_Notecard.h
- Removed
tankalarm_setNotecardI2CSpeed(),tankalarm_getNotecardUUID(),tankalarm_configureHub(),tankalarm_getNotecardStatus()(~118 lines). Onlytankalarm_currentEpoch()remains.
TankAlarm_Utils.h
- Removed
tankalarm_isValidEpoch(),tankalarm_streq(),tankalarm_isEmptyString()(~35 lines). Onlytankalarm_roundTo()andtankalarm_computeNextAlignedEpoch()remain.
TankAlarm_Config.h
- Removed
DEFAULT_SERVER_FLEET,DEFAULT_SOLAR_POWERED,DEFAULT_MPPT_ENABLED,GRID_INBOUND_INTERVAL_MINUTES.
TankAlarm_Common.h
- Removed
CONFIG_OUTBOX_FILE,TIME_SYNC_INTERVAL_MS.
Client .ino
- Removed local POSIX wrappers (
posix_file_size(),posix_file_exists(),posix_log_error()).
Server .ino
- Removed
convertMaToLevel()wrapper (callers now useconvertMaToLevelWithTemp()directly).
⚠️ Breaking Changes
| Change | Impact |
|---|---|
Relay commands now use relay_forward.qo / .qi notefiles |
Notehub routes must be updated to forward relay_forward.qo to the server. |
Config ACK payload: success (bool) → status (string) + cv field |
Server handleConfigAck must parse the new format. |
sendConfigAck() requires 3 parameters (was 2) |
Any custom callers must be updated. |
Viewer must belong to "tankalarm-viewer" fleet |
New fleet required in Notehub before deploying. |
pendingDispatch no longer cleared on send |
Config retries now persist until ACK, changing retry behavior. |
Watchdog macro renamed to TANKALARM_WATCHDOG_AVAILABLE |
Custom platform headers must use the new name. |
📖 Documentation Updates
Tutorial Accuracy Fixes
All 14 tutorial guides under Tutorials/Tutorials-112025/ were audited against the current codebase and corrected:
| Tutorial | Fixes Applied |
|---|---|
| QUICK_START_GUIDE | server_config.h → ServerConfig.h; PRODUCT_UID/SERVER_PRODUCT_UID → DEFAULT_PRODUCT_UID/DEFAULT_SERVER_PRODUCT_UID; removed nonexistent config_template.h and SERVER_FLEET define; corrected file tree; removed stale SERVER_FLEET troubleshooting check |
| SERVER_INSTALLATION_GUIDE | Rewrote Product UID section with ServerConfig.h + __has_include pattern; WATCHDOG_TIMEOUT_SECONDS 600 → 30; removed nonexistent DEFAULT_CLIENT_FLEET compile-time define |
| CLIENT_INSTALLATION_GUIDE | PRODUCT_UID → DEFAULT_PRODUCT_UID; fixed 4 broken relative links (../ → ../../) to Server INSTALLATION.md and FLEET_SETUP.md |
| ADVANCED_CONFIGURATION_GUIDE | Complete rewrite of watchdog section — Watchdog.h → TankAlarm_Platform.h; correct constants (TANKALARM_WATCHDOG_AVAILABLE, WATCHDOG_TIMEOUT_SECONDS 30); correct API (mbedWatchdog.kick(), TANKALARM_WATCHDOG_KICK macro); removed fictional watchdog.feed()/watchdog.nearTimeout() |
| FLEET_SETUP_GUIDE | Added tankalarm-viewer fleet to Quick Start section; SERVER_PRODUCT_UID → DEFAULT_SERVER_PRODUCT_UID |
| Tutorials README | Added tankalarm-viewer to fleet checklist and file tree |
| RELAY_CONTROL_GUIDE | Added relay_forward.qo notefile name and cross-reference to Firmware Communication Guide |
| TROUBLESHOOTING_GUIDE | Added tankalarm-viewer to both fleet-verification checklists; server_config.h → ServerConfig.h |
| BACKUP_RECOVERY_GUIDE | server_config.h → ServerConfig.h; SERVER_PRODUCT_UID → DEFAULT_SERVER_PRODUCT_UID |
| UNLOAD_TRACKING_GUIDE | Removed dead link to nonexistent INTEGRATION_API_GUIDE.md |
| SERVER_INSTALLATION_GUIDE | Fixed broken relative link (../ → ../../) to HISTORICAL_DATA_ARCHITECTURE.md |
| FIRMWARE_UPDATE_GUIDE | Fixed 7 broken relative links — CODE%20REVIEW/ and TankAlarm-112025-*/ paths prefixed with ../../ |
| Tutorials README | Fixed ../LICENSE → ../../LICENSE |
Version & Date Bumps
- All 14 tutorial footers updated from v1.0 | January 7, 2026 to v1.1 | February 20, 2026.
- Version compatibility notes bumped to v1.1.1 across all tutorials.
Other Documentation
- README.md updated with current architecture description, v1.1.1 changelog entry, and release notes link.
- FLEET_SETUP.md and SERVER_INSTALLATION_GUIDE.md updated with viewer fleet requirements.
- library.properties version bumped from
1.0.2→1.1.1. - New code review docs:
CODE_REVIEW_FIX_IMPLEMENTATION_02202026.md,CODE_REVIEW_SUMMARY_02202026.md.
📦 Upgrade Instructions
- Create the viewer fleet in Notehub: add a
tankalarm-viewerfleet to your project. - Update Notehub routes to handle the new
relay_forward.qonotefile (see NOTEHUB_ROUTES_SETUP.md). - Flash all three firmware images (Client, Server, Viewer) — this release requires all devices to be updated together.
- No config file reset is required; existing configs are backward-compatible.
- Clear browser cache or hard-refresh (Ctrl+F5) on the Server web UI.
Files Changed
28 files changed, 184 insertions(+), 174 deletions(-)
| Component | Files | Summary |
|---|---|---|
| Client .ino | 1 | Relay forwarding, serial ACK, config ACK, watchdog macro, dead code removal |
| Ser... |