Skip to content

Commit 7c6f847

Browse files
Copilotdorkmo
andcommitted
Add raw sensorMa to telemetry and remove percent from client
Co-authored-by: dorkmo <[email protected]>
1 parent ccf999b commit 7c6f847

File tree

2 files changed

+61
-38
lines changed

2 files changed

+61
-38
lines changed

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ struct ClientConfig {
283283

284284
struct TankRuntime {
285285
float currentInches;
286+
float currentSensorMa; // Raw sensor reading in milliamps (for 4-20mA sensors)
286287
float lastReportedInches;
287288
float lastDailySentInches;
288289
bool highAlarmLatched;
@@ -1556,13 +1557,17 @@ static float readTankSensor(uint8_t idx) {
15561557
// Use explicit bounds check for current loop channel
15571558
int16_t channel = (cfg.currentLoopChannel >= 0 && cfg.currentLoopChannel < 8) ? cfg.currentLoopChannel : 0;
15581559
if (cfg.maxValue < 0.1f) {
1560+
gTankState[idx].currentSensorMa = 0.0f;
15591561
return 0.0f;
15601562
}
15611563
float milliamps = readCurrentLoopMilliamps(channel);
15621564
if (milliamps < 0.0f) {
15631565
return gTankState[idx].currentInches; // keep previous on failure
15641566
}
15651567

1568+
// Store raw mA reading for telemetry
1569+
gTankState[idx].currentSensorMa = milliamps;
1570+
15661571
// Handle different 4-20mA sensor types
15671572
float levelInches;
15681573
if (cfg.currentLoopType == CURRENT_LOOP_ULTRASONIC) {
@@ -1850,11 +1855,14 @@ static void sendTelemetry(uint8_t idx, const char *reason, bool syncNow) {
18501855
bool activated = (state.currentInches > DIGITAL_SWITCH_THRESHOLD);
18511856
doc["activated"] = activated; // Boolean state: true = switch activated
18521857
doc["levelInches"] = state.currentInches; // 1.0 or 0.0
1853-
doc["percent"] = activated ? 100.0f : 0.0f; // 100% when activated, 0% when not
1858+
} else if (cfg.sensorType == SENSOR_CURRENT_LOOP) {
1859+
doc["sensorType"] = "currentLoop";
1860+
doc["maxValue"] = cfg.maxValue;
1861+
doc["levelInches"] = state.currentInches;
1862+
doc["sensorMa"] = state.currentSensorMa; // Raw 4-20mA reading
18541863
} else {
18551864
doc["maxValue"] = cfg.maxValue;
18561865
doc["levelInches"] = state.currentInches;
1857-
doc["percent"] = (cfg.maxValue > 0.1f) ? (state.currentInches / cfg.maxValue * 100.0f) : 0.0f;
18581866
}
18591867
doc["reason"] = reason;
18601868
doc["time"] = currentEpoch();
@@ -2147,7 +2155,10 @@ static bool appendDailyTank(DynamicJsonDocument &doc, JsonArray &array, uint8_t
21472155
t["label"] = cfg.name;
21482156
t["tank"] = cfg.tankNumber;
21492157
t["levelInches"] = state.currentInches;
2150-
t["percent"] = (cfg.maxValue > 0.1f) ? (state.currentInches / cfg.maxValue * 100.0f) : 0.0f;
2158+
// Include raw sensor mA for current loop sensors
2159+
if (cfg.sensorType == SENSOR_CURRENT_LOOP && state.currentSensorMa >= 4.0f) {
2160+
t["sensorMa"] = state.currentSensorMa;
2161+
}
21512162
t["high"] = cfg.highAlarmThreshold;
21522163
t["low"] = cfg.lowAlarmThreshold;
21532164

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

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ struct TankRecord {
227227
uint8_t tankNumber;
228228
float heightInches;
229229
float levelInches;
230-
float percent;
230+
float sensorMa; // Raw 4-20mA sensor reading (0 if not available)
231+
float percent; // Calculated on server from levelInches/heightInches
231232
bool alarmActive;
232233
char alarmType[24];
233234
double lastUpdateEpoch;
@@ -2603,7 +2604,7 @@ static const char CALIBRATION_HTML[] PROGMEM = R"HTML(
26032604
</div>
26042605
</label>
26052606
<label class="field">
2606-
<span>Sensor Reading (mA) <em style="font-weight: normal; color: var(--muted);">- Auto-calculated from telemetry</em></span>
2607+
<span>Sensor Reading (mA) <em style="font-weight: normal; color: var(--muted);">- From telemetry</em></span>
26072608
<input type="number" id="sensorReading" step="0.01" min="4" max="20" placeholder="Auto-filled when tank selected">
26082609
<small id="sensorAutoInfo" style="color: var(--muted); margin-top: 4px; display: block;"></small>
26092610
</label>
@@ -2622,9 +2623,9 @@ static const char CALIBRATION_HTML[] PROGMEM = R"HTML(
26222623
</div>
26232624
</form>
26242625
<div class="info-box">
2625-
<strong>How it works:</strong> Each calibration reading pairs a verified tank level (measured with a stick gauge, sight glass, or other method) with the current 4-20mA sensor reading. With at least 2 data points at different levels, the system calculates a linear regression to determine the actual relationship between sensor output and tank level. This learned calibration replaces the theoretical maxValue-based calculation.
2626+
<strong>How it works:</strong> Each calibration reading pairs a verified tank level (measured with a stick gauge, sight glass, or other method) with the raw 4-20mA sensor reading. With at least 2 data points at different levels, the system calculates a linear regression to determine the actual relationship between sensor output and tank level. This learned calibration replaces the theoretical maxValue-based calculation.
26262627
<br><br>
2627-
<strong>Sensor reading auto-calculation:</strong> When you select a tank, the sensor reading (mA) is automatically calculated from the latest telemetry data. You can override this value if you have a more accurate reading from a loop meter.
2628+
<strong>Raw sensor reading:</strong> The mA reading is now captured directly from the sensor and sent in telemetry. When you select a tank, it will be auto-filled from the latest telemetry. If no raw reading is available, you can enter it manually from a loop meter.
26282629
</div>
26292630
</div>
26302631
@@ -2795,6 +2796,7 @@ static const char CALIBRATION_HTML[] PROGMEM = R"HTML(
27952796
label: t.label || `Tank ${t.tank}`,
27962797
heightInches: t.heightInches || 0,
27972798
levelInches: t.levelInches || 0,
2799+
sensorMa: t.sensorMa || 0,
27982800
percent: t.percent || 0,
27992801
lastUpdate: t.lastUpdate || 0
28002802
});
@@ -2810,15 +2812,6 @@ static const char CALIBRATION_HTML[] PROGMEM = R"HTML(
28102812
});
28112813
}
28122814
2813-
// Calculate estimated mA from tank percent (for 4-20mA pressure sensors)
2814-
function estimateSensorMa(percent) {
2815-
if (typeof percent !== 'number' || !isFinite(percent)) return 0;
2816-
// Clamp percent to 0-100 range
2817-
const clampedPercent = Math.max(0, Math.min(100, percent));
2818-
// 4mA = 0%, 20mA = 100%
2819-
return 4 + (clampedPercent / 100) * 16;
2820-
}
2821-
28222815
// Auto-populate sensor reading when tank is selected
28232816
function onTankSelect() {
28242817
const tankKey = document.getElementById('tankSelect').value;
@@ -2833,13 +2826,18 @@ static const char CALIBRATION_HTML[] PROGMEM = R"HTML(
28332826
28342827
// Find the tank data
28352828
const tank = tanks.find(t => `${t.client}:${t.tank}` === tankKey);
2836-
if (tank && tank.heightInches > 0) {
2837-
const estimatedMa = estimateSensorMa(tank.percent);
2838-
if (estimatedMa >= 4 && estimatedMa <= 20) {
2839-
sensorInput.value = estimatedMa.toFixed(2);
2829+
if (tank) {
2830+
// Use raw sensorMa if available from telemetry
2831+
if (tank.sensorMa && tank.sensorMa >= 4 && tank.sensorMa <= 20) {
2832+
sensorInput.value = tank.sensorMa.toFixed(2);
28402833
if (sensorInfo) {
28412834
const lastUpdateDate = tank.lastUpdate ? new Date(tank.lastUpdate * 1000).toLocaleString() : 'unknown';
2842-
sensorInfo.textContent = `Auto-calculated from last telemetry (${tank.percent.toFixed(1)}% @ ${lastUpdateDate})`;
2835+
sensorInfo.textContent = `From last telemetry @ ${lastUpdateDate}`;
2836+
}
2837+
} else {
2838+
sensorInput.value = '';
2839+
if (sensorInfo) {
2840+
sensorInfo.textContent = 'No raw mA reading available - enter manually';
28432841
}
28442842
}
28452843
}
@@ -6182,6 +6180,7 @@ static void sendTankJson(EthernetClient &client) {
61826180
obj["tank"] = gTankRecords[i].tankNumber;
61836181
obj["heightInches"] = gTankRecords[i].heightInches;
61846182
obj["levelInches"] = gTankRecords[i].levelInches;
6183+
obj["sensorMa"] = gTankRecords[i].sensorMa;
61856184
obj["percent"] = gTankRecords[i].percent;
61866185
obj["alarm"] = gTankRecords[i].alarmActive;
61876186
obj["alarmType"] = gTankRecords[i].alarmType;
@@ -6266,6 +6265,7 @@ static void sendClientDataJson(EthernetClient &client) {
62666265
clientObj["label"] = rec.label;
62676266
clientObj["tank"] = rec.tankNumber;
62686267
clientObj["levelInches"] = rec.levelInches;
6268+
clientObj["sensorMa"] = rec.sensorMa;
62696269
clientObj["percent"] = rec.percent;
62706270
clientObj["lastUpdate"] = rec.lastUpdateEpoch;
62716271
clientObj["alarmType"] = rec.alarmType;
@@ -6282,6 +6282,7 @@ static void sendClientDataJson(EthernetClient &client) {
62826282
tankObj["tank"] = rec.tankNumber;
62836283
tankObj["heightInches"] = rec.heightInches;
62846284
tankObj["levelInches"] = rec.levelInches;
6285+
tankObj["sensorMa"] = rec.sensorMa;
62856286
tankObj["percent"] = rec.percent;
62866287
tankObj["alarm"] = rec.alarmActive;
62876288
tankObj["alarmType"] = rec.alarmType;
@@ -6593,9 +6594,27 @@ static void handleTelemetry(JsonDocument &doc, double epoch) {
65936594
strlcpy(rec->site, doc["site"] | "", sizeof(rec->site));
65946595
strlcpy(rec->label, doc["label"] | "Tank", sizeof(rec->label));
65956596
rec->levelInches = doc["levelInches"].as<float>();
6596-
rec->percent = doc["percent"].as<float>();
6597+
6598+
// Handle raw sensor mA reading if provided (from 4-20mA sensors)
6599+
if (doc.containsKey("sensorMa")) {
6600+
rec->sensorMa = doc["sensorMa"].as<float>();
6601+
} else {
6602+
rec->sensorMa = 0.0f; // Not available
6603+
}
6604+
6605+
// Calculate percent on server from level/height (backwards compatible)
6606+
if (doc.containsKey("percent")) {
6607+
rec->percent = doc["percent"].as<float>();
6608+
} else if (rec->heightInches > 0.1f) {
6609+
rec->percent = (rec->levelInches / rec->heightInches) * 100.0f;
6610+
} else {
6611+
rec->percent = 0.0f;
6612+
}
6613+
65976614
if (doc.containsKey("heightInches")) {
65986615
rec->heightInches = doc["heightInches"].as<float>();
6616+
} else if (doc.containsKey("maxValue")) {
6617+
rec->heightInches = doc["maxValue"].as<float>();
65996618
}
66006619
rec->lastUpdateEpoch = (epoch > 0.0) ? epoch : currentEpoch();
66016620
}
@@ -6874,6 +6893,7 @@ static void sendDailyEmail() {
68746893
obj["label"] = gTankRecords[i].label;
68756894
obj["tank"] = gTankRecords[i].tankNumber;
68766895
obj["levelInches"] = gTankRecords[i].levelInches;
6896+
obj["sensorMa"] = gTankRecords[i].sensorMa;
68776897
obj["percent"] = gTankRecords[i].percent;
68786898
obj["alarm"] = gTankRecords[i].alarmActive;
68796899
obj["alarmType"] = gTankRecords[i].alarmType;
@@ -6921,6 +6941,7 @@ static void publishViewerSummary() {
69216941
obj["tank"] = gTankRecords[i].tankNumber;
69226942
obj["heightInches"] = gTankRecords[i].heightInches;
69236943
obj["levelInches"] = gTankRecords[i].levelInches;
6944+
obj["sensorMa"] = gTankRecords[i].sensorMa;
69246945
obj["percent"] = gTankRecords[i].percent;
69256946
obj["alarm"] = gTankRecords[i].alarmActive;
69266947
obj["alarmType"] = gTankRecords[i].alarmType;
@@ -7742,28 +7763,19 @@ static void handleCalibrationPost(EthernetClient &client, const String &body) {
77427763
// Optional fields
77437764
float sensorReading = doc["sensorReading"].as<float>();
77447765
if (!doc.containsKey("sensorReading") || sensorReading < 4.0f || sensorReading > 20.0f) {
7745-
// Auto-calculate sensor reading from tank telemetry data
7766+
// Try to get raw sensorMa from tank record (sent directly from client)
77467767
sensorReading = 0.0f;
77477768

7748-
// Look up tank record to get percent from latest telemetry
7769+
// Look up tank record to get raw sensorMa from latest telemetry
77497770
for (uint8_t i = 0; i < gTankRecordCount; ++i) {
77507771
if (strcmp(gTankRecords[i].clientUid, clientUid) == 0 &&
77517772
gTankRecords[i].tankNumber == tankNumber) {
7752-
// Use stored percent if available, otherwise calculate from level/height
7753-
float percent = gTankRecords[i].percent;
7754-
if (percent <= 0.0f && gTankRecords[i].heightInches > 1.0f) {
7755-
// Percent not set, calculate from level/height as fallback
7756-
percent = (gTankRecords[i].levelInches / gTankRecords[i].heightInches) * 100.0f;
7757-
}
7758-
// Convert percent (0-100) to mA (4-20)
7759-
if (percent >= 0.0f) {
7760-
if (percent > 100.0f) percent = 100.0f;
7761-
sensorReading = 4.0f + (percent / 100.0f) * 16.0f;
7762-
Serial.print(F("Auto-calculated sensor reading from telemetry: "));
7773+
// Use raw sensorMa if available (sent directly from client device)
7774+
if (gTankRecords[i].sensorMa >= 4.0f && gTankRecords[i].sensorMa <= 20.0f) {
7775+
sensorReading = gTankRecords[i].sensorMa;
7776+
Serial.print(F("Using raw sensorMa from telemetry: "));
77637777
Serial.print(sensorReading, 2);
7764-
Serial.print(F(" mA ("));
7765-
Serial.print(percent, 1);
7766-
Serial.println(F("%)"));
7778+
Serial.println(F(" mA"));
77677779
}
77687780
break;
77697781
}

0 commit comments

Comments
 (0)