Skip to content

Releases: SenaxInc/ArduinoSMSTankAlarm

TankAlarm Beta v1.2.3

03 Apr 01:36

Choose a tag to compare

What's Changed

  • Fix missing forward declaration for sendSecurityHeaders in Server sketch by @Copilot in #283

Full Changelog: v1.2.1...v1.2.3

TankAlarm Beta v1.2.1

29 Mar 19:31

Choose a tag to compare

TankAlarm Beta v1.2.0

28 Mar 16:49

Choose a tag to compare

TankAlarm Beta v1.1.8

16 Mar 19:04

Choose a tag to compare

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 gConfigDirty was 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: gLastDailyRollupDate was not persisted. On reboot, the daily rollup could run again for the same day, producing duplicate entries in daily_YYYYMM.json files.
  • After: Two changes:
    1. gLastDailyRollupDate persisted in history_settings.json (key "lastRollup")
    2. rollupDailySummaries() now removes existing entries matching yesterdayDate before appending new data
  • Impact: Prevents inflated daily summary values after reboots.

Fix #6 — Persist SMS Rate-Limit State

  • Before: lastSmsAlertEpoch and smsAlertsInLastHour were 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.ftpSyncHour existed in the settings struct and was configurable via the API, but the actual maintenance check only tested if (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_TANK updated 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:

  1. Checks tenure: Only archives clients with firstSeenEpoch older than 30 days (MIN_ARCHIVE_AGE_SECONDS)
  2. Builds archive: Collects all sensor records for the client, serializes to JSON with full telemetry history
  3. Uploads to FTP: Date-stamped filename: {ftpPath}/{serverUid}/archived_clients/{Site}_{YYYYMM}-{YYYYMM}_{uidSuffix}.json
  4. Updates local manifest: Appends entry to /fs/archived_clients.json for 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

15 Mar 05:21

Choose a tag to compare

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/history and /api/history/yoy endpoints
  • 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: tankNumbersensorIndex + userNumber

  • Renamed struct field tankNumber to sensorIndex across all three firmware files
  • Added new userNumber field (optional user-assigned display number, wire key "un")
  • Wire format key "k" now maps to sensorIndex (previously tankNumber)
  • 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 tankNumsensorIdx in 7 functions (~20 instances)
C++ local variables tankFiltersensorFilter, filterTankfilterSensorIdx, tankParamsensorParam
C++ local variables uint8_t tankuint8_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.ksensorIndex:t.k in loadTanks mapper + all downstream .tank.sensorIndex (~12 spots)
Dashboard tank:t.ksensorIndex:t.k in buildSiteModel + sparkline keys (5 spots)
Client Console tank:c.ksensorIndex:c.k in normalizeApiData (2 spots)
Site Config tank:c.k/t.ksensorIndex:c.k/t.k + fallback push (3 spots)
Contacts alarm.tankalarm.label (3 spots: updateFilters, renderContacts, renderAlarmAssociations)

Phase 5: URL Parameter Rename

  • /api/history query parameter ?tank=CLIENT:NUM?sensor=CLIENT:NUM
  • /api/history/yoy query 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 serialized monitorType to flash storage
  • After reboot, all monitors defaulted to "tank" regardless of actual type
  • Now correctly writes and restores monitorType for each monitor
  • Impact: Gas monitors, flow meters, and other types survive power cycles

Client serializeConfig() sensorIndex Fix

  • Fixed serializeConfig() to emit the correct sensorIndex value 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 tankNumbersensorIndex in code example
CODE REVIEW/H4_ATOMIC_WRITE_REFACTOR_RECOMMENDATION.md cal.tankNumbercal.sensorIndex (2 code blocks)
CODE REVIEW/REVIEW_RESULTS_112025.md .tankNumber == tankNumber.sensorIndex == sensorIndex
CODE REVIEW/V1.1.6_RELEASE_NOTES.md clientUid + tankNumberclientUid + 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

13 Mar 18:34

Choose a tag to compare

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 deriveMacFromUid in Viewer sketch by @Copilot in #281

Full Changelog: v1.1.5...v1.1.6

TankAlarm Beta v1.1.5

10 Mar 15:20

Choose a tag to compare

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

01 Mar 22:51

Choose a tag to compare

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

  1. 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.

  2. Viewer: Watchdog kick + safety cap in fetchViewerSummary()
    The while (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.

  3. Client: Watchdog kick in trimTelemetryOutbox()
    Added watchdog kicks at the top of the outer multi-pass while loop and inside the inner note.delete for-loop. Worst-case scenario (10 passes × 16 I2C transactions × 200ms each = 32s) previously exceeded the 30-second watchdog window.

I2C Bus Timeout Consistency

  1. I2C Utility: Added Wire.setTimeout(I2C_WIRE_TIMEOUT_MS) after Wire.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

  1. Flash all four firmware images (Client, Server, Viewer, I2C Utility) to pick up the version bump and hardening fixes.
  2. No config reset required — existing configs are fully backward-compatible.
  3. No hardware changes required.
  4. 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.31.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...
Read more

TankAlarm Beta v1.1.3

24 Feb 04:36

Choose a tag to compare

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.

  • SolarOnlyConfig struct in TankAlarm_Battery.h with 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 via hub.sync, and sends a solar_sunset alarm 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_failure alarm 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).

  • VinMonitorConfig struct in TankAlarm_Battery.h with pin, R1/R2 resistor values, poll interval
  • readVinDividerVoltage(): 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 gServerVoltagegNotecardVoltage to clarify that card.voltage reads the Notecard's 5V regulated rail, NOT the external battery
  • Added gInputVoltage for 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: close header
  • Replaced Bearer authentication header with X-SESSION-TOKEN for 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)

  1. CRITICAL — Runtime solar-only enable permanently disabled sensors: When solar-only mode was enabled via a config update (not a reboot), gSolarOnlyStartupComplete was set to false but performStartupDebounce() is only called from setup(). The device would never sample sensors or send reports until rebooted. Fix: Set both startup flags to true since the device is already running with stable power.

  2. MODERATE — Battery failure counter false positives: gSolarOnlyBatFailCount only 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).

  3. MINOR — Dead global variable: Removed unused gSolarOnlyDebounceStart (the implementation uses a local stableStart variable in performStartupDebounce() 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 (heightIncheslevelInches on TankRecord, removed duplicate default arg)

UTF-8 BOM Fix (v1.1.2)

  • Removed UTF-8 BOM from Server .ino file 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

  1. Flash all three firmware images (Client, Server, Viewer) to pick up the version bump.
  2. No config reset required — existing configs are fully backward-compatible.
  3. To use Solar-Only mode: Select "Solar Only — No Battery" (or "+ Vin Monitor" variant) in the Server Config Generator and configure the solar parameters.
  4. 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.
  5. 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
...
Read more

TankAlarm Beta v1.1.1

20 Feb 21:39

Choose a tag to compare

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-viewer fleet during hub.set, enabling fleet-scoped DFU, route filtering, and device management.
  • Client createDefaultConfig() explicitly sets clientFleet to "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.qi notefiles (constants added in TankAlarm_Common.h).
  • Client: triggerRemoteRelays() sends via RELAY_FORWARD_OUTBOX_FILE with renamed payload fields (target/client instead of _target/_type).
  • Server: New handleRelayForward() function receives relay-forward notes, parses target/client/relay/state/source, and re-issues the command to the target client via sendRelayCommand().

Serial ACK Protocol (Client)

  • New sendSerialAck(status) function sends serial_ack.qo notes with the client UID and a status string.
  • pollForSerialRequests() now emits "processing" and "complete" ACKs around sendSerialLogs().
  • New SERIAL_ACK_OUTBOX_FILE constant in TankAlarm_Common.h.

Config ACK Enhancements

  • sendConfigAck() now accepts a third configVersion parameter and includes a status string ("applied" / "failed") and optional cv (config version hash) in the ACK payload.
  • Server injects _cv into dispatched configs from ClientConfigSnapshot::configVersion.
  • Server keeps pendingDispatch = true until the ACK is received (previously cleared immediately on send).

🔧 Improvements

ArduinoJson v7 Migration (Server & Viewer)

  • All DynamicJsonDocument / StaticJsonDocument instances replaced with auto-sizing JsonDocument (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.uuid request now guarded with if (req) null check before calling requestAndResponse().
  • 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 a startOffset instead of always iterating from index 0.

Shared Utility Consolidation

  • Server's local computeNextAlignedEpoch() replaced by shared tankalarm_computeNextAlignedEpoch() from TankAlarm_Utils.h.
  • DFU_CHECK_INTERVAL_MS consolidated into TankAlarm_Common.h (was duplicated in both client and server .ino files).

🐛 Bug Fixes

Memory Safety

  • Server: sendDailyEmail() and publishViewerSummary() now use JDelete(req) instead of notecard.deleteResponse(req) for freeing unsent request objects.
  • Client: triggerRemoteRelays() adds JDelete(req) when body allocation fails, preventing a memory leak.

Watchdog Macro Rename

  • All #ifdef WATCHDOG_AVAILABLE corrected to #ifdef TANKALARM_WATCHDOG_AVAILABLE across client (6 instances) and server (8 instances), matching the macro defined in TankAlarm_Platform.h.

DFU Auto-Apply Preprocessor Fix (Viewer)

  • Changed #if DFU_AUTO_ENABLE to #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 first NWS_AVG_HOURS values 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). Only tankalarm_currentEpoch() remains.

TankAlarm_Utils.h

  • Removed tankalarm_isValidEpoch(), tankalarm_streq(), tankalarm_isEmptyString() (~35 lines). Only tankalarm_roundTo() and tankalarm_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 use convertMaToLevelWithTemp() 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.hServerConfig.h; PRODUCT_UID/SERVER_PRODUCT_UIDDEFAULT_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 60030; removed nonexistent DEFAULT_CLIENT_FLEET compile-time define
CLIENT_INSTALLATION_GUIDE PRODUCT_UIDDEFAULT_PRODUCT_UID; fixed 4 broken relative links (../../../) to Server INSTALLATION.md and FLEET_SETUP.md
ADVANCED_CONFIGURATION_GUIDE Complete rewrite of watchdog section — Watchdog.hTankAlarm_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_UIDDEFAULT_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.hServerConfig.h
BACKUP_RECOVERY_GUIDE server_config.hServerConfig.h; SERVER_PRODUCT_UIDDEFAULT_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.21.1.1.
  • New code review docs: CODE_REVIEW_FIX_IMPLEMENTATION_02202026.md, CODE_REVIEW_SUMMARY_02202026.md.

📦 Upgrade Instructions

  1. Create the viewer fleet in Notehub: add a tankalarm-viewer fleet to your project.
  2. Update Notehub routes to handle the new relay_forward.qo notefile (see NOTEHUB_ROUTES_SETUP.md).
  3. Flash all three firmware images (Client, Server, Viewer) — this release requires all devices to be updated together.
  4. No config file reset is required; existing configs are backward-compatible.
  5. 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...
Read more