Skip to content

Commit 7447c8b

Browse files
committed
Add levelChangeThreshold config and update sample interval
Introduces a new 'levelChangeThreshold' parameter to client configuration, allowing per-site control of change-based telemetry uploads. Default sample interval is increased from 300 to 1800 seconds across documentation, client, and server code. UI and config handling updated to support the new threshold, with logic to suppress or enable delta-triggered telemetry based on its value.
1 parent 8c1adb8 commit 7447c8b

File tree

6 files changed

+62
-16
lines changed

6 files changed

+62
-16
lines changed

TankAlarm-112025-Client-BluesOpta/FLEET_IMPLEMENTATION_SUMMARY.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ The Tank Alarm system has been updated to use **Blues Notehub fleet-based device
149149
"site": "Tank Farm A",
150150
"deviceLabel": "Tank-01",
151151
"serverFleet": "tankalarm-server",
152-
"sampleSeconds": 300,
152+
"sampleSeconds": 1800,
153+
"levelChangeThreshold": 0,
153154
"reportHour": 5,
154155
"reportMinute": 0,
155156
"dailyEmail": "[email protected]",
@@ -170,7 +171,7 @@ The Tank Alarm system has been updated to use **Blues Notehub fleet-based device
170171
}
171172
```
172173

173-
SMS recipients now reside exclusively in the server configuration. Client-side `alarmSms` flags simply signal whether a tank should request SMS escalation when it triggers an alarm.
174+
SMS recipients now reside exclusively in the server configuration. Client-side `alarmSms` flags simply signal whether a tank should request SMS escalation when it triggers an alarm. The new `levelChangeThreshold` key (in inches) is optional; leave it at `0` to suppress change-based telemetry or set a value via the server console to resume delta-triggered reports for that site.
174175

175176
### Server Config Example
176177
```json

TankAlarm-112025-Client-BluesOpta/MIGRATION_GUIDE.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ For each client:
159159
## Step 7: Verify Communication
160160

161161
1. **Check Telemetry Flow (Client → Server):**
162-
- Wait for client to take samples (~5 minutes with default 300s interval)
162+
- Wait for client to take samples (~30 minutes with default 1800s interval unless you lowered it)
163163
- In Notehub, navigate to **Events**
164164
- Filter by client device
165165
- Look for notes with format: `fleet.tankalarm-server:telemetry.qi`
@@ -268,11 +268,14 @@ Once verified working:
268268
"site": "Tank Farm A",
269269
"deviceLabel": "Tank-01",
270270
"serverFleet": "tankalarm-server",
271-
"sampleSeconds": 300,
271+
"sampleSeconds": 1800,
272+
"levelChangeThreshold": 0,
272273
...
273274
}
274275
```
275276

277+
`levelChangeThreshold` (in inches) lets you opt-in to change-based telemetry per site. Leave it at `0` to disable periodic uploads and rely on alarms/daily reports, or set a threshold (for example `2.5`) via the server UI to resume delta-triggered telemetry.
278+
276279
### Server Config (LittleFS: /server_config.json)
277280

278281
**Old format:**

TankAlarm-112025-Client-BluesOpta/TankAlarm-112025-Client-BluesOpta.ino

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ static size_t strlcpy(char *dst, const char *src, size_t size) {
9696
#endif
9797

9898
#ifndef DEFAULT_SAMPLE_SECONDS
99-
#define DEFAULT_SAMPLE_SECONDS 300
99+
#define DEFAULT_SAMPLE_SECONDS 1800
100+
#endif
101+
102+
#ifndef DEFAULT_LEVEL_CHANGE_THRESHOLD_INCHES
103+
#define DEFAULT_LEVEL_CHANGE_THRESHOLD_INCHES 0.0f
100104
#endif
101105

102106
#ifndef DEFAULT_REPORT_HOUR
@@ -163,6 +167,7 @@ struct ClientConfig {
163167
char serverFleet[32]; // Target fleet name for server (e.g., "tankalarm-server")
164168
char dailyEmail[64];
165169
uint16_t sampleSeconds;
170+
float minLevelChangeInches;
166171
uint8_t reportHour;
167172
uint8_t reportMinute;
168173
uint8_t tankCount;
@@ -369,6 +374,7 @@ static void createDefaultConfig(ClientConfig &cfg) {
369374
strlcpy(cfg.serverFleet, "tankalarm-server", sizeof(cfg.serverFleet));
370375
strlcpy(cfg.dailyEmail, "[email protected]", sizeof(cfg.dailyEmail));
371376
cfg.sampleSeconds = DEFAULT_SAMPLE_SECONDS;
377+
cfg.minLevelChangeInches = DEFAULT_LEVEL_CHANGE_THRESHOLD_INCHES;
372378
cfg.reportHour = DEFAULT_REPORT_HOUR;
373379
cfg.reportMinute = DEFAULT_REPORT_MINUTE;
374380
cfg.tankCount = 1;
@@ -416,6 +422,10 @@ static bool loadConfigFromFlash(ClientConfig &cfg) {
416422
strlcpy(cfg.dailyEmail, doc["dailyEmail"].as<const char *>() ? doc["dailyEmail"].as<const char *>() : "", sizeof(cfg.dailyEmail));
417423

418424
cfg.sampleSeconds = doc["sampleSeconds"].is<uint16_t>() ? doc["sampleSeconds"].as<uint16_t>() : DEFAULT_SAMPLE_SECONDS;
425+
cfg.minLevelChangeInches = doc["levelChangeThreshold"].is<float>() ? doc["levelChangeThreshold"].as<float>() : DEFAULT_LEVEL_CHANGE_THRESHOLD_INCHES;
426+
if (cfg.minLevelChangeInches < 0.0f) {
427+
cfg.minLevelChangeInches = 0.0f;
428+
}
419429
cfg.reportHour = doc["reportHour"].is<uint8_t>() ? doc["reportHour"].as<uint8_t>() : DEFAULT_REPORT_HOUR;
420430
cfg.reportMinute = doc["reportMinute"].is<uint8_t>() ? doc["reportMinute"].as<uint8_t>() : DEFAULT_REPORT_MINUTE;
421431

@@ -455,6 +465,7 @@ static bool saveConfigToFlash(const ClientConfig &cfg) {
455465
doc["deviceLabel"] = cfg.deviceLabel;
456466
doc["serverFleet"] = cfg.serverFleet;
457467
doc["sampleSeconds"] = cfg.sampleSeconds;
468+
doc["levelChangeThreshold"] = cfg.minLevelChangeInches;
458469
doc["reportHour"] = cfg.reportHour;
459470
doc["reportMinute"] = cfg.reportMinute;
460471
doc["dailyEmail"] = cfg.dailyEmail;
@@ -739,13 +750,22 @@ static void reinitializeHardware() {
739750
gTankState[i].sensorFailed = false;
740751
gTankState[i].lastValidReading = 0.0f;
741752
gTankState[i].hasLastValidReading = false;
753+
gTankState[i].lastReportedInches = -9999.0f;
742754
}
743755

744756
Serial.println(F("Hardware reinitialized after config update"));
745757
}
746758

759+
static void resetTelemetryBaselines() {
760+
for (uint8_t i = 0; i < MAX_TANKS; ++i) {
761+
gTankState[i].lastReportedInches = -9999.0f;
762+
}
763+
}
764+
747765
static void applyConfigUpdate(const JsonDocument &doc) {
748766
bool hardwareChanged = false;
767+
bool telemetryPolicyChanged = false;
768+
float previousThreshold = gConfig.minLevelChangeInches;
749769

750770
if (doc.containsKey("site")) {
751771
strlcpy(gConfig.siteName, doc["site"].as<const char *>(), sizeof(gConfig.siteName));
@@ -759,6 +779,13 @@ static void applyConfigUpdate(const JsonDocument &doc) {
759779
if (doc.containsKey("sampleSeconds")) {
760780
gConfig.sampleSeconds = doc["sampleSeconds"].as<uint16_t>();
761781
}
782+
if (doc.containsKey("levelChangeThreshold")) {
783+
gConfig.minLevelChangeInches = doc["levelChangeThreshold"].as<float>();
784+
if (gConfig.minLevelChangeInches < 0.0f) {
785+
gConfig.minLevelChangeInches = 0.0f;
786+
}
787+
telemetryPolicyChanged = (fabsf(previousThreshold - gConfig.minLevelChangeInches) > 0.0001f);
788+
}
762789
if (doc.containsKey("reportHour")) {
763790
gConfig.reportHour = doc["reportHour"].as<uint8_t>();
764791
}
@@ -809,6 +836,8 @@ static void applyConfigUpdate(const JsonDocument &doc) {
809836

810837
if (hardwareChanged) {
811838
reinitializeHardware();
839+
} else if (telemetryPolicyChanged) {
840+
resetTelemetryBaselines();
812841
}
813842

814843
printHardwareRequirements(gConfig);
@@ -1001,8 +1030,11 @@ static void sampleTanks() {
10011030
evaluateAlarms(i);
10021031

10031032
if (gConfig.tanks[i].enableServerUpload && !gTankState[i].sensorFailed) {
1004-
float delta = fabs(inches - gTankState[i].lastReportedInches);
1005-
if (delta >= 0.5f || gTankState[i].lastReportedInches < 0.0f) {
1033+
const float threshold = gConfig.minLevelChangeInches;
1034+
const bool needBaseline = (gTankState[i].lastReportedInches < 0.0f);
1035+
const bool thresholdEnabled = (threshold > 0.0f);
1036+
const bool changeExceeded = thresholdEnabled && (fabs(inches - gTankState[i].lastReportedInches) >= threshold);
1037+
if (needBaseline || changeExceeded) {
10061038
sendTelemetry(i, "sample", false);
10071039
gTankState[i].lastReportedInches = inches;
10081040
}

TankAlarm-112025-Server-BluesOpta/FLEET_SETUP.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ The client configuration file (`/client_config.json` on LittleFS) includes:
4646
"site": "Tank Farm A",
4747
"deviceLabel": "Tank-01",
4848
"serverFleet": "tankalarm-server",
49-
"sampleSeconds": 300,
49+
"sampleSeconds": 1800,
50+
"levelChangeThreshold": 0,
5051
"reportHour": 5,
5152
"reportMinute": 0,
5253
"dailyEmail": "[email protected]",
@@ -70,7 +71,7 @@ The client configuration file (`/client_config.json` on LittleFS) includes:
7071
}
7172
```
7273

73-
Server-managed SMS contacts have been removed from the client schema; per-tank `alarmSms` flags now simply request that the server escalate alerts via its own contact list.
74+
Server-managed SMS contacts have been removed from the client schema; per-tank `alarmSms` flags now simply request that the server escalate alerts via its own contact list. The `levelChangeThreshold` value (in inches) controls when the client sends change-based telemetry. `0` disables those transmissions, so by default only daily reports and alarms consume data until you enable a per-site threshold via the server console.
7475

7576
**Key field:** `serverFleet` - Must match the server's fleet name in Notehub
7677

@@ -126,7 +127,7 @@ The server configuration file (`/server_config.json` on LittleFS) includes:
126127
- Configuration saved to flash
127128

128129
2. **Monitor telemetry:**
129-
- Wait for sample interval (~5 minutes default)
130+
- Wait for sample interval (~30 minutes default; lower if you configured a smaller value)
130131
- Server web UI should show tank levels
131132
- Check Notehub Events to see note traffic
132133

TankAlarm-112025-Server-BluesOpta/SETUP.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ The server’s intranet dashboard lets you push JSON configuration updates direc
111111
{
112112
"client": "dev:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
113113
"config": {
114-
"sampleSeconds": 600,
114+
"sampleSeconds": 1800,
115+
"levelChangeThreshold": 0,
115116
"tanks": [
116117
{
117118
"id": "A",

TankAlarm-112025-Server-BluesOpta/TankAlarm-112025-Server-BluesOpta.ino

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,8 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
473473
<label class="field"><span>Site Name</span><input id="siteName" type="text" placeholder="Site Name" required></label>
474474
<label class="field"><span>Device Label</span><input id="deviceLabel" type="text" placeholder="Device Label" required></label>
475475
<label class="field"><span>Server Fleet</span><input id="serverFleet" type="text" value="tankalarm-server"></label>
476-
<label class="field"><span>Sample Seconds</span><input id="sampleSeconds" type="number" value="300"></label>
476+
<label class="field"><span>Sample Seconds</span><input id="sampleSeconds" type="number" value="1800"></label>
477+
<label class="field"><span>Level Change Threshold (in)</span><input id="levelChangeThreshold" type="number" step="0.1" value="0" placeholder="0 = disabled"></label>
477478
<label class="field"><span>Report Hour</span><input id="reportHour" type="number" value="5"></label>
478479
<label class="field"><span>Report Minute</span><input id="reportMinute" type="number" value="0"></label>
479480
<label class="field"><span>Daily Email</span><input id="dailyEmail" type="email"></label>
@@ -639,11 +640,13 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
639640
}
640641
641642
document.getElementById('downloadBtn').addEventListener('click', () => {
643+
const levelChange = parseFloat(document.getElementById('levelChangeThreshold').value);
642644
const config = {
643645
site: document.getElementById('siteName').value.trim(),
644646
deviceLabel: document.getElementById('deviceLabel').value.trim() || 'Client-112025',
645647
serverFleet: document.getElementById('serverFleet').value.trim() || 'tankalarm-server',
646-
sampleSeconds: parseInt(document.getElementById('sampleSeconds').value, 10) || 300,
648+
sampleSeconds: parseInt(document.getElementById('sampleSeconds').value, 10) || 1800,
649+
levelChangeThreshold: Math.max(0, isNaN(levelChange) ? 0 : levelChange),
647650
reportHour: parseInt(document.getElementById('reportHour').value, 10) || 5,
648651
reportMinute: parseInt(document.getElementById('reportMinute').value, 10) || 0,
649652
dailyEmail: document.getElementById('dailyEmail').value.trim(),
@@ -1771,6 +1774,7 @@ static const char CLIENT_CONSOLE_HTML[] PROGMEM = R"HTML(
17711774
<label class="field"><span>Device Label</span><input id="deviceLabelInput" type="text" placeholder="Device label"></label>
17721775
<label class="field"><span>Server Fleet</span><input id="routeInput" type="text" placeholder="tankalarm-server"></label>
17731776
<label class="field"><span>Sample Seconds</span><input id="sampleSecondsInput" type="number" min="30" step="30"></label>
1777+
<label class="field"><span>Level Change Threshold (in)</span><input id="levelChangeThresholdInput" type="number" min="0" step="0.1" placeholder="0 = disabled"></label>
17741778
<label class="field"><span>Report Hour (0-23)</span><input id="reportHourInput" type="number" min="0" max="23"></label>
17751779
<label class="field"><span>Report Minute (0-59)</span><input id="reportMinuteInput" type="number" min="0" max="59"></label>
17761780
<label class="field"><span>SMS Primary</span><input id="smsPrimaryInput" type="text" placeholder="+1234567890"></label>
@@ -1886,6 +1890,7 @@ static const char CLIENT_CONSOLE_HTML[] PROGMEM = R"HTML(
18861890
deviceLabel: document.getElementById('deviceLabelInput'),
18871891
route: document.getElementById('routeInput'),
18881892
sampleSeconds: document.getElementById('sampleSecondsInput'),
1893+
levelChangeThreshold: document.getElementById('levelChangeThresholdInput'),
18891894
reportHour: document.getElementById('reportHourInput'),
18901895
reportMinute: document.getElementById('reportMinuteInput'),
18911896
smsPrimary: document.getElementById('smsPrimaryInput'),
@@ -2214,7 +2219,8 @@ static const char CLIENT_CONSOLE_HTML[] PROGMEM = R"HTML(
22142219
site: client ? (client.site || '') : '',
22152220
deviceLabel: client ? `${((client.site || 'Client')).replace(/\s+/g, '-')}-${client.tank || tankId || 'A'}` : 'Client-112025',
22162221
serverFleet: 'tankalarm-server',
2217-
sampleSeconds: 300,
2222+
sampleSeconds: 1800,
2223+
levelChangeThreshold: 0,
22182224
reportHour: 5,
22192225
reportMinute: 0,
22202226
dailyEmail: serverDefaults.dailyEmail || '',
@@ -2297,7 +2303,8 @@ static const char CLIENT_CONSOLE_HTML[] PROGMEM = R"HTML(
22972303
els.site.value = config.site || '';
22982304
els.deviceLabel.value = config.deviceLabel || '';
22992305
els.route.value = config.serverFleet || '';
2300-
els.sampleSeconds.value = valueOr(config.sampleSeconds, 300);
2306+
els.sampleSeconds.value = valueOr(config.sampleSeconds, 1800);
2307+
els.levelChangeThreshold.value = valueOr(config.levelChangeThreshold, 0);
23012308
els.reportHour.value = valueOr(config.reportHour, 5);
23022309
els.reportMinute.value = valueOr(config.reportMinute, 0);
23032310
els.dailyEmail.value = config.dailyEmail || '';
@@ -2341,7 +2348,8 @@ static const char CLIENT_CONSOLE_HTML[] PROGMEM = R"HTML(
23412348
site: els.site.value.trim(),
23422349
deviceLabel: els.deviceLabel.value.trim(),
23432350
serverFleet: els.route.value.trim() || 'tankalarm-server',
2344-
sampleSeconds: parseInt(els.sampleSeconds.value, 10) || 300,
2351+
sampleSeconds: parseInt(els.sampleSeconds.value, 10) || 1800,
2352+
levelChangeThreshold: Math.max(0, parseFloat(els.levelChangeThreshold.value) || 0),
23452353
reportHour: parseInt(els.reportHour.value, 10) || 5,
23462354
reportMinute: parseInt(els.reportMinute.value, 10) || 0,
23472355
dailyEmail: els.dailyEmail.value.trim(),

0 commit comments

Comments
 (0)