Skip to content

Commit 08789b9

Browse files
Copilotdorkmo
andcommitted
Add sensor native range configuration (min/max/unit) for 4-20mA sensors
Co-authored-by: dorkmo <[email protected]>
1 parent 27ffcdf commit 08789b9

File tree

3 files changed

+89
-6
lines changed

3 files changed

+89
-6
lines changed

TankAlarm-112025-Client-BluesOpta/README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ Used for sensors like the Dwyer 626-06-CB-P1-E5-S1 (0-5 PSI) mounted near the bo
100100
- **How it works**: Measures the pressure of the liquid column above the sensor
101101
- 4mA = Empty tank (0 PSI / no liquid above sensor)
102102
- 20mA = Full tank (max PSI / max liquid height)
103+
- **Sensor Range**: The native measurement range (e.g., 0-5 PSI, 0-2 bar)
104+
- `sensorRangeMin`: Minimum value (typically 0)
105+
- `sensorRangeMax`: Maximum value (e.g., 5 for 0-5 PSI)
106+
- `sensorRangeUnit`: Unit of measurement ("PSI", "bar", etc.)
103107
- **Sensor Mount Height**: Height of sensor above tank bottom (usually 0-2 inches)
104108
- **Max Value**: Maximum liquid height the sensor can measure (corresponds to 20mA)
105109

@@ -109,6 +113,9 @@ Used for sensors like the Dwyer 626-06-CB-P1-E5-S1 (0-5 PSI) mounted near the bo
109113
- Tank height = 120 inches
110114
- Configuration:
111115
- `currentLoopType`: "pressure"
116+
- `sensorRangeMin`: 0
117+
- `sensorRangeMax`: 5
118+
- `sensorRangeUnit`: "PSI"
112119
- `sensorMountHeight`: 2.0
113120
- `maxValue`: 118.0 (tank height minus mount height: 120 - 2 = 118 inches)
114121

@@ -121,22 +128,30 @@ Used for sensors like the Siemens Sitrans LU240 mounted on top of the tank looki
121128
- **How it works**: Measures the distance from the sensor to the liquid surface
122129
- 4mA = Full tank (liquid close to sensor)
123130
- 20mA = Empty tank (liquid far from sensor)
131+
- **Sensor Range**: The native measurement range (e.g., 0-10 meters, 0-30 feet)
132+
- `sensorRangeMin`: Minimum distance (typically 0)
133+
- `sensorRangeMax`: Maximum distance (e.g., 10 for 0-10m)
134+
- `sensorRangeUnit`: Unit of measurement ("m", "ft", "in", etc.)
124135
- **Sensor Mount Height**: Distance from sensor to tank bottom when tank is empty
125136
- **Max Value**: Maximum liquid height (tank capacity)
126137

127-
**Example Configuration** (ultrasonic sensor on 10-foot tank):
138+
**Example Configuration** (ultrasonic sensor with 0-10m range on 10-foot tank):
128139
- Sensor mounted 124 inches above tank bottom (tank is 120 inches + 4 inch clearance)
129140
- Maximum tank fill level = 120 inches
130141
- Configuration:
131142
- `currentLoopType`: "ultrasonic"
143+
- `sensorRangeMin`: 0
144+
- `sensorRangeMax`: 10
145+
- `sensorRangeUnit`: "m"
132146
- `sensorMountHeight`: 124.0
133147
- `maxValue`: 120.0
134148

135149
**Calibration Tips for 4-20mA Sensors:**
136150
1. Record the actual mA output at known liquid levels (empty, half-full, full)
137151
2. Verify sensor mount height is accurate
138-
3. Check for temperature effects on readings
139-
4. Consider the specific gravity of the liquid (for pressure sensors)
152+
3. Enter the correct sensor native range (as specified in sensor datasheet)
153+
4. Check for temperature effects on readings
154+
5. Consider the specific gravity of the liquid (for pressure sensors)
140155

141156
### Float Switch Configuration (Digital Sensors)
142157
Float switches can be configured as either normally-open (NO) or normally-closed (NC):

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ struct TankConfig {
263263
CurrentLoopSensorType currentLoopType; // Pressure (bottom-mounted) or Ultrasonic (top-mounted)
264264
float sensorMountHeight; // For ultrasonic: distance from sensor to tank bottom (inches)
265265
// For pressure: height of sensor above tank bottom (inches, usually 0-2)
266+
float sensorRangeMin; // Minimum native sensor range (e.g., 0 for 0-5 PSI or 0-10m)
267+
float sensorRangeMax; // Maximum native sensor range (e.g., 5 for 0-5 PSI, 10 for 0-10m)
268+
char sensorRangeUnit[8]; // Unit for sensor range: "PSI", "bar", "m", "ft", "in", etc.
266269
};
267270

268271
struct ClientConfig {
@@ -647,6 +650,9 @@ static void createDefaultConfig(ClientConfig &cfg) {
647650
strlcpy(cfg.tanks[0].digitalSwitchMode, "NO", sizeof(cfg.tanks[0].digitalSwitchMode)); // Default: normally-open
648651
cfg.tanks[0].currentLoopType = CURRENT_LOOP_PRESSURE; // Default: pressure sensor (most common)
649652
cfg.tanks[0].sensorMountHeight = 0.0f; // Default: sensor at tank bottom
653+
cfg.tanks[0].sensorRangeMin = 0.0f; // Default: 0 (e.g., 0 PSI or 0 meters)
654+
cfg.tanks[0].sensorRangeMax = 5.0f; // Default: 5 (e.g., 5 PSI for typical pressure sensor)
655+
strlcpy(cfg.tanks[0].sensorRangeUnit, "PSI", sizeof(cfg.tanks[0].sensorRangeUnit)); // Default: PSI
650656
}
651657

652658
static bool loadConfigFromFlash(ClientConfig &cfg) {
@@ -791,6 +797,11 @@ static bool loadConfigFromFlash(ClientConfig &cfg) {
791797
}
792798
// Load sensor mount height (for calibration) - validate non-negative
793799
cfg.tanks[i].sensorMountHeight = t["sensorMountHeight"].is<float>() ? fmaxf(0.0f, t["sensorMountHeight"].as<float>()) : 0.0f;
800+
// Load sensor native range settings
801+
cfg.tanks[i].sensorRangeMin = t["sensorRangeMin"].is<float>() ? t["sensorRangeMin"].as<float>() : 0.0f;
802+
cfg.tanks[i].sensorRangeMax = t["sensorRangeMax"].is<float>() ? t["sensorRangeMax"].as<float>() : 5.0f;
803+
const char *rangeUnitStr = t["sensorRangeUnit"].as<const char *>();
804+
strlcpy(cfg.tanks[i].sensorRangeUnit, rangeUnitStr ? rangeUnitStr : "PSI", sizeof(cfg.tanks[i].sensorRangeUnit));
794805
}
795806

796807
return true;
@@ -868,6 +879,10 @@ static bool saveConfigToFlash(const ClientConfig &cfg) {
868879
}
869880
// Save sensor mount height (for calibration)
870881
t["sensorMountHeight"] = cfg.tanks[i].sensorMountHeight;
882+
// Save sensor native range settings
883+
t["sensorRangeMin"] = cfg.tanks[i].sensorRangeMin;
884+
t["sensorRangeMax"] = cfg.tanks[i].sensorRangeMax;
885+
t["sensorRangeUnit"] = cfg.tanks[i].sensorRangeUnit;
871886
}
872887

873888
#if defined(ARDUINO_OPTA) || defined(ARDUINO_ARCH_MBED)
@@ -1336,6 +1351,17 @@ static void applyConfigUpdate(const JsonDocument &doc) {
13361351
if (t.containsKey("sensorMountHeight")) {
13371352
gConfig.tanks[i].sensorMountHeight = fmaxf(0.0f, t["sensorMountHeight"].as<float>());
13381353
}
1354+
// Handle sensor native range settings
1355+
if (t.containsKey("sensorRangeMin")) {
1356+
gConfig.tanks[i].sensorRangeMin = t["sensorRangeMin"].as<float>();
1357+
}
1358+
if (t.containsKey("sensorRangeMax")) {
1359+
gConfig.tanks[i].sensorRangeMax = t["sensorRangeMax"].as<float>();
1360+
}
1361+
if (t.containsKey("sensorRangeUnit")) {
1362+
const char *unitStr = t["sensorRangeUnit"].as<const char *>();
1363+
strlcpy(gConfig.tanks[i].sensorRangeUnit, unitStr ? unitStr : "PSI", sizeof(gConfig.tanks[i].sensorRangeUnit));
1364+
}
13391365
}
13401366
}
13411367

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

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,21 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
749749
<option value="ultrasonic">Ultrasonic Sensor (Top-Mounted)</option>
750750
</select>
751751
</label>
752+
<label class="field sensor-range-field" style="display: none;"><span><span class="sensor-range-label">Sensor Range</span><span class="tooltip-icon sensor-range-tooltip" tabindex="0" data-tooltip="Native measurement range of the sensor (e.g., 0-5 PSI for pressure, 0-10m for ultrasonic). This is the range that corresponds to 4-20mA output.">?</span></span>
753+
<div style="display: flex; gap: 8px; align-items: center;">
754+
<input type="number" class="sensor-range-min" value="0" step="0.1" style="width: 70px;" placeholder="Min">
755+
<span>to</span>
756+
<input type="number" class="sensor-range-max" value="5" step="0.1" style="width: 70px;" placeholder="Max">
757+
<select class="sensor-range-unit" style="width: 70px;">
758+
<option value="PSI">PSI</option>
759+
<option value="bar">bar</option>
760+
<option value="m">m</option>
761+
<option value="ft">ft</option>
762+
<option value="in">in</option>
763+
<option value="cm">cm</option>
764+
</select>
765+
</div>
766+
</label>
752767
<label class="field sensor-mount-height-field" style="display: none;"><span><span class="mount-height-label">Sensor Mount Height (in)</span><span class="tooltip-icon mount-height-tooltip" tabindex="0" data-tooltip="For pressure sensors: height of sensor above tank bottom (usually 0-2 inches). For ultrasonic sensors: distance from sensor to tank bottom when empty.">?</span></span><input type="number" class="sensor-mount-height" value="0" step="0.1" min="0"></label>
753768
<label class="field height-field"><span><span class="height-label">Height (in)</span><span class="tooltip-icon height-tooltip" tabindex="0" data-tooltip="Maximum height or capacity of the tank in inches. Used to calculate fill percentage and set alarm thresholds relative to tank size.">?</span></span><input type="number" class="tank-height" value="120"></label>
754769
</div>
@@ -763,14 +778,16 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
763778
<div class="current-loop-sensor-info" style="display: none; background: var(--chip); border: 1px solid var(--card-border); border-radius: 8px; padding: 12px; margin-top: 8px; font-size: 0.9rem; color: var(--muted);">
764779
<div class="pressure-sensor-info">
765780
<strong>Pressure Sensor (Bottom-Mounted):</strong> Installed near the bottom of the tank, this sensor measures the pressure of the liquid column above it. Examples: Dwyer 626-06-CB-P1-E5-S1 (0-5 PSI).<br>
766-
• 4mA = Empty tank (0 PSI)<br>
767-
• 20mA = Full tank (max PSI)<br>
781+
• 4mA = Empty tank (0 pressure)<br>
782+
• 20mA = Full tank (max pressure)<br>
783+
• Sensor Range: The native pressure range (e.g., 0-5 PSI, 0-2 bar)<br>
768784
• Mount Height: Distance from sensor to tank bottom (usually 0-2 inches)
769785
</div>
770786
<div class="ultrasonic-sensor-info" style="display: none;">
771787
<strong>Ultrasonic Sensor (Top-Mounted):</strong> Mounted on top of the tank, this sensor measures the distance from the sensor to the liquid surface. Examples: Siemens Sitrans LU240.<br>
772788
• 4mA = Full tank (liquid close to sensor)<br>
773789
• 20mA = Empty tank (liquid far from sensor)<br>
790+
• Sensor Range: The native distance range (e.g., 0-10m, 0-30ft)<br>
774791
• Sensor Mount Height: Distance from sensor to tank bottom when empty
775792
</div>
776793
</div>
@@ -1035,6 +1052,7 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
10351052
const switchModeField = card.querySelector('.switch-mode-field');
10361053
const currentLoopTypeField = card.querySelector('.current-loop-type-field');
10371054
const sensorMountHeightField = card.querySelector('.sensor-mount-height-field');
1055+
const sensorRangeField = card.querySelector('.sensor-range-field');
10381056
10391057
// Digital Input (Float Switch) - type === 0
10401058
if (type === 0) {
@@ -1048,6 +1066,7 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
10481066
// Hide current loop fields
10491067
currentLoopTypeField.style.display = 'none';
10501068
sensorMountHeightField.style.display = 'none';
1069+
sensorRangeField.style.display = 'none';
10511070
// Update alarm section for digital sensors
10521071
alarmThresholdsGrid.style.display = 'none';
10531072
digitalAlarmGrid.style.display = 'grid';
@@ -1060,8 +1079,9 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
10601079
digitalInfoBox.style.display = 'none';
10611080
currentLoopInfoBox.style.display = 'block';
10621081
switchModeField.style.display = 'none';
1063-
// Show current loop type and mount height fields
1082+
// Show current loop type, sensor range, and mount height fields
10641083
currentLoopTypeField.style.display = 'flex';
1084+
sensorRangeField.style.display = 'flex';
10651085
sensorMountHeightField.style.display = 'flex';
10661086
alarmThresholdsGrid.style.display = 'grid';
10671087
digitalAlarmGrid.style.display = 'none';
@@ -1078,6 +1098,7 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
10781098
switchModeField.style.display = 'none';
10791099
currentLoopTypeField.style.display = 'none';
10801100
sensorMountHeightField.style.display = 'none';
1101+
sensorRangeField.style.display = 'none';
10811102
alarmThresholdsGrid.style.display = 'grid';
10821103
digitalAlarmGrid.style.display = 'none';
10831104
alarmSectionTitle.textContent = 'Alarm Thresholds';
@@ -1092,6 +1113,7 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
10921113
switchModeField.style.display = 'none';
10931114
currentLoopTypeField.style.display = 'none';
10941115
sensorMountHeightField.style.display = 'none';
1116+
sensorRangeField.style.display = 'none';
10951117
alarmThresholdsGrid.style.display = 'grid';
10961118
digitalAlarmGrid.style.display = 'none';
10971119
alarmSectionTitle.textContent = 'Alarm Thresholds';
@@ -1109,6 +1131,10 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
11091131
const mountHeightTooltip = card.querySelector('.mount-height-tooltip');
11101132
const heightLabel = card.querySelector('.height-label');
11111133
const heightTooltip = card.querySelector('.height-tooltip');
1134+
const sensorRangeLabel = card.querySelector('.sensor-range-label');
1135+
const sensorRangeTooltip = card.querySelector('.sensor-range-tooltip');
1136+
const sensorRangeUnit = card.querySelector('.sensor-range-unit');
1137+
const sensorRangeMax = card.querySelector('.sensor-range-max');
11121138
11131139
if (currentLoopType === 'ultrasonic') {
11141140
pressureInfo.style.display = 'none';
@@ -1117,13 +1143,23 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
11171143
mountHeightTooltip.setAttribute('data-tooltip', 'Distance from the ultrasonic sensor to the tank bottom when empty. This is used to calculate the actual liquid level.');
11181144
heightLabel.textContent = 'Tank Height (in)';
11191145
heightTooltip.setAttribute('data-tooltip', 'Maximum liquid height in the tank. When the tank is full, the liquid level equals this value.');
1146+
sensorRangeLabel.textContent = 'Sensor Range';
1147+
sensorRangeTooltip.setAttribute('data-tooltip', 'Native distance range of the ultrasonic sensor (e.g., 0-10m). This is the measurement range that corresponds to the 4-20mA output.');
1148+
// Set defaults for ultrasonic sensors
1149+
sensorRangeUnit.value = 'm';
1150+
sensorRangeMax.value = '10';
11201151
} else {
11211152
pressureInfo.style.display = 'block';
11221153
ultrasonicInfo.style.display = 'none';
11231154
mountHeightLabel.textContent = 'Sensor Mount Height (in)';
11241155
mountHeightTooltip.setAttribute('data-tooltip', 'Height of the pressure sensor above the tank bottom (usually 0-2 inches). This offset is added to the measured level.');
11251156
heightLabel.textContent = 'Max Measured Height (in)';
11261157
heightTooltip.setAttribute('data-tooltip', 'Maximum liquid height the sensor can measure (corresponds to 20mA / full sensor scale). Does not include the sensor mount height offset.');
1158+
sensorRangeLabel.textContent = 'Sensor Range';
1159+
sensorRangeTooltip.setAttribute('data-tooltip', 'Native pressure range of the sensor (e.g., 0-5 PSI, 0-2 bar). This is the measurement range that corresponds to the 4-20mA output.');
1160+
// Set defaults for pressure sensors
1161+
sensorRangeUnit.value = 'PSI';
1162+
sensorRangeMax.value = '5';
11271163
}
11281164
};
11291165
@@ -1218,8 +1254,14 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
12181254
if (sensor === 'current') {
12191255
const currentLoopType = card.querySelector('.current-loop-type').value;
12201256
const sensorMountHeight = parseFloat(card.querySelector('.sensor-mount-height').value) || 0;
1257+
const sensorRangeMin = parseFloat(card.querySelector('.sensor-range-min').value) || 0;
1258+
const sensorRangeMax = parseFloat(card.querySelector('.sensor-range-max').value) || 5;
1259+
const sensorRangeUnit = card.querySelector('.sensor-range-unit').value || 'PSI';
12211260
tank.currentLoopType = currentLoopType; // 'pressure' or 'ultrasonic'
12221261
tank.sensorMountHeight = sensorMountHeight;
1262+
tank.sensorRangeMin = sensorRangeMin;
1263+
tank.sensorRangeMax = sensorRangeMax;
1264+
tank.sensorRangeUnit = sensorRangeUnit;
12231265
}
12241266
12251267
// Handle alarms differently based on sensor type

0 commit comments

Comments
 (0)