@@ -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