Skip to content

Commit 91d0eca

Browse files
Copilotdorkmo
andcommitted
Remove maxValue field completely from sensor configuration
- Remove maxValue from TankConfig struct - Remove MIN_VALID_MAX_VALUE constant - Remove maxValue from config loading/saving/updating - Remove maxValue from telemetry output - Remove maxValue clamping from sensor calculations - Update validateSensorReading to use native sensor ranges or alarm thresholds - Remove legacy mode from analog sensors (now requires native range config) - Update README documentation to remove all maxValue references Co-authored-by: dorkmo <[email protected]>
1 parent f7d52ac commit 91d0eca

File tree

2 files changed

+17
-71
lines changed

2 files changed

+17
-71
lines changed

TankAlarm-112025-Client-BluesOpta/README.md

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,10 @@ The client creates a default configuration on first boot. You can update configu
8484
- **Low Alarm**: Threshold in inches for low level alert
8585
- **Analog Pin**: Arduino Opta analog input (A0-A7, I1-I8)
8686
- **Sensor Type**: "voltage" (0-10V), "current" (4-20mA), or "digital" (float switch)
87-
- **Min Value**: Minimum sensor value (e.g., 0.0V or 4.0mA)
88-
- **Max Value**: Maximum sensor value (e.g., 10.0V or 20.0mA)
89-
- **Min Inches**: Tank level in inches at minimum sensor value
90-
- **Max Inches**: Tank level in inches at maximum sensor value
9187

9288
### 4-20mA Current Loop Sensor Configuration
9389

94-
For 4-20mA current loop sensors, two mounting options are supported. The implementation uses the sensor's **native measurement range** (sensorRangeMin/Max/Unit) for accurate conversions, making `maxValue` **optional** (used only for clamping/validation).
90+
For 4-20mA current loop sensors, two mounting options are supported. The implementation uses the sensor's **native measurement range** (sensorRangeMin/Max/Unit) for accurate pressure-to-height conversions.
9591

9692
#### Pressure Sensor (Bottom-Mounted)
9793
Used for sensors like the Dwyer 626-06-CB-P1-E5-S1 (0-5 PSI) mounted near the bottom of the tank.
@@ -105,7 +101,6 @@ Used for sensors like the Dwyer 626-06-CB-P1-E5-S1 (0-5 PSI) mounted near the bo
105101
- `sensorRangeMax`: Maximum pressure at 20mA (e.g., 5 for 0-5 PSI)
106102
- `sensorRangeUnit`: Pressure unit - "PSI", "bar", "kPa", "mbar", or "inH2O"
107103
- **Sensor Mount Height**: Height of sensor above tank bottom (usually 0-2 inches)
108-
- **Max Value**: **Optional** - Maximum expected tank level for clamping (0 to disable clamping)
109104

110105
**Pressure-to-Height Conversion:**
111106
The system automatically converts pressure to inches using these factors:
@@ -118,16 +113,12 @@ The system automatically converts pressure to inches using these factors:
118113
**Example Configuration** (0-5 PSI sensor on 120" tank):
119114
- Sensor mounted 2 inches above tank bottom
120115
- Max sensor range = 5 PSI = ~138 inches of water column
121-
- Tank capacity = 120 inches
122-
- Configuration (minimal - no maxValue needed):
116+
- Configuration:
123117
- `currentLoopType`: "pressure"
124118
- `sensorRangeMin`: 0
125119
- `sensorRangeMax`: 5
126120
- `sensorRangeUnit`: "PSI"
127121
- `sensorMountHeight`: 2.0
128-
- `maxValue`: 0 (or omit entirely - not required for 4-20mA sensors)
129-
130-
> **Note:** For 4-20mA sensors, `maxValue` is **completely optional**. The sensor's native range (`sensorRangeMin`/`sensorRangeMax`/`sensorRangeUnit`) provides all the information needed to calculate the liquid level. Set `maxValue` only if you want to clamp readings to a maximum tank capacity.
131122

132123
**How It Works:**
133124
1. 4mA → 0 PSI → 0 inches of liquid above sensor
@@ -146,7 +137,6 @@ Used for sensors like the Siemens Sitrans LU240 mounted on top of the tank looki
146137
- `sensorRangeMax`: Maximum distance at 20mA (e.g., 10m)
147138
- `sensorRangeUnit`: Distance unit - "m", "cm", "ft", or "in"
148139
- **Sensor Mount Height**: Distance from sensor to tank bottom when tank is empty (in inches)
149-
- **Max Value**: **Optional** - Maximum expected liquid level for clamping (0 to disable)
150140

151141
**Distance Unit Conversion:**
152142
The system automatically converts distance to inches using:
@@ -156,14 +146,12 @@ The system automatically converts distance to inches using:
156146

157147
**Example Configuration** (ultrasonic sensor with 0.5-10m range on 10-foot tank):
158148
- Sensor mounted 124 inches above tank bottom (tank is 120" + 4" clearance)
159-
- Maximum tank fill level = 120 inches
160-
- Configuration (minimal - no maxValue needed):
149+
- Configuration:
161150
- `currentLoopType`: "ultrasonic"
162151
- `sensorRangeMin`: 0.5 (blind spot in meters)
163152
- `sensorRangeMax`: 10.0 (max range in meters)
164153
- `sensorRangeUnit`: "m"
165154
- `sensorMountHeight`: 124.0
166-
- `maxValue`: 0 (or omit entirely - not required)
167155

168156
**How It Works:**
169157
1. 4mA → 0.5m (19.7") → liquid level = 124" - 19.7" = 104.3" (nearly full)
@@ -190,7 +178,7 @@ Float switches can be configured as either normally-open (NO) or normally-closed
190178

191179
### Analog Voltage Sensor Configuration
192180

193-
For analog voltage sensors (like the Dwyer 626 series with voltage output), the system now supports the same native range configuration as 4-20mA sensors. This allows you to specify both the voltage range and pressure range for accurate pressure-to-height conversion.
181+
For analog voltage sensors (like the Dwyer 626 series with voltage output), the system supports the same native range configuration as 4-20mA sensors. This allows you to specify both the voltage range and pressure range for accurate pressure-to-height conversion.
194182

195183
**Supported Voltage Output Configurations:**
196184
- 0-10V (default)
@@ -206,7 +194,6 @@ For analog voltage sensors (like the Dwyer 626 series with voltage output), the
206194
- `sensorRangeMin` / `sensorRangeMax`: Pressure range in native units
207195
- `sensorRangeUnit`: Pressure unit - "PSI", "bar", "kPa", "mbar", or "inH2O"
208196
- `sensorMountHeight`: Height of sensor above tank bottom (inches)
209-
- `maxValue`: **Optional** - Maximum expected tank level for clamping (0 to disable)
210197

211198
**Example Configuration** (Dwyer 626 with 1-5V output, 0-5 PSI range):
212199
- Configuration:
@@ -217,11 +204,6 @@ For analog voltage sensors (like the Dwyer 626 series with voltage output), the
217204
- `sensorRangeMax`: 5
218205
- `sensorRangeUnit`: "PSI"
219206
- `sensorMountHeight`: 2.0
220-
- `maxValue`: 0 (or omit entirely - not required)
221-
222-
> **Note:** For analog voltage sensors with native range configured, `maxValue` is **completely optional**. The sensor's voltage range and pressure range provide all the information needed to calculate the liquid level. Set `maxValue` only if you want to clamp readings to a maximum tank capacity.
223-
224-
**Legacy Mode:** If `analogVoltageMin`/`analogVoltageMax` and `sensorRangeMin`/`sensorRangeMax` are not configured, the system falls back to the legacy behavior where `maxValue` directly represents the tank height.
225207

226208
## Operation
227209

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

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,6 @@ static size_t strlcpy(char *dst, const char *src, size_t size) {
230230
#define MBAR_TO_INCHES_WATER 0.4015f // 1 mbar = 0.4015 inches of water
231231
#endif
232232

233-
// Minimum value threshold for configuration validation
234-
#ifndef MIN_VALID_MAX_VALUE
235-
#define MIN_VALID_MAX_VALUE 0.1f // maxValue must be > this to be considered valid
236-
#endif
237-
238233
static const uint8_t NOTECARD_I2C_ADDRESS = 0x17;
239234
static const uint32_t NOTECARD_I2C_FREQUENCY = 400000UL;
240235

@@ -280,7 +275,6 @@ struct TankConfig {
280275
int16_t currentLoopChannel; // 4-20mA channel index (-1 if unused)
281276
int16_t rpmPin; // Hall effect RPM sensor pin (-1 if unused)
282277
uint8_t pulsesPerRevolution; // For RPM sensors: pulses per revolution (default 1)
283-
float maxValue; // Tank height (inches) for level sensors, or max RPM for RPM sensors
284278
float highAlarmThreshold; // High threshold for triggering alarm (inches or RPM)
285279
float lowAlarmThreshold; // Low threshold for triggering alarm (inches or RPM)
286280
float hysteresisValue; // Hysteresis band (default 2.0)
@@ -674,7 +668,6 @@ static void createDefaultConfig(ClientConfig &cfg) {
674668
cfg.tanks[0].currentLoopChannel = -1;
675669
cfg.tanks[0].rpmPin = -1; // No RPM sensor by default
676670
cfg.tanks[0].pulsesPerRevolution = 1; // Default: 1 pulse per revolution
677-
cfg.tanks[0].maxValue = 120.0f;
678671
cfg.tanks[0].highAlarmThreshold = 100.0f;
679672
cfg.tanks[0].lowAlarmThreshold = 20.0f;
680673
cfg.tanks[0].hysteresisValue = 2.0f; // 2 unit hysteresis band
@@ -788,9 +781,6 @@ static bool loadConfigFromFlash(ClientConfig &cfg) {
788781
cfg.tanks[i].currentLoopChannel = t["loopChannel"].is<int>() ? t["loopChannel"].as<int>() : -1;
789782
cfg.tanks[i].rpmPin = t["rpmPin"].is<int>() ? t["rpmPin"].as<int>() : -1;
790783
cfg.tanks[i].pulsesPerRevolution = t["pulsesPerRev"].is<uint8_t>() ? max((uint8_t)1, t["pulsesPerRev"].as<uint8_t>()) : 1;
791-
// Support both old field names (heightInches) and new (maxValue) for backwards compatibility
792-
cfg.tanks[i].maxValue = t["maxValue"].is<float>() ? t["maxValue"].as<float>() :
793-
(t["heightInches"].is<float>() ? t["heightInches"].as<float>() : 120.0f);
794784
cfg.tanks[i].highAlarmThreshold = t["highAlarm"].is<float>() ? t["highAlarm"].as<float>() : 100.0f;
795785
cfg.tanks[i].lowAlarmThreshold = t["lowAlarm"].is<float>() ? t["lowAlarm"].as<float>() : 20.0f;
796786
cfg.tanks[i].hysteresisValue = t["hysteresis"].is<float>() ? t["hysteresis"].as<float>() : 2.0f;
@@ -888,7 +878,6 @@ static bool saveConfigToFlash(const ClientConfig &cfg) {
888878
t["loopChannel"] = cfg.tanks[i].currentLoopChannel;
889879
t["rpmPin"] = cfg.tanks[i].rpmPin;
890880
t["pulsesPerRev"] = cfg.tanks[i].pulsesPerRevolution;
891-
t["maxValue"] = cfg.tanks[i].maxValue;
892881
t["highAlarm"] = cfg.tanks[i].highAlarmThreshold;
893882
t["lowAlarm"] = cfg.tanks[i].lowAlarmThreshold;
894883
t["hysteresis"] = cfg.tanks[i].hysteresisValue;
@@ -1323,12 +1312,6 @@ static void applyConfigUpdate(const JsonDocument &doc) {
13231312
if (t.containsKey("pulsesPerRev")) {
13241313
gConfig.tanks[i].pulsesPerRevolution = max((uint8_t)1, t["pulsesPerRev"].as<uint8_t>());
13251314
}
1326-
// Support both old field name (heightInches) and new (maxValue)
1327-
if (t.containsKey("maxValue")) {
1328-
gConfig.tanks[i].maxValue = t["maxValue"].as<float>();
1329-
} else if (t.containsKey("heightInches")) {
1330-
gConfig.tanks[i].maxValue = t["heightInches"].as<float>();
1331-
}
13321315
gConfig.tanks[i].highAlarmThreshold = t["highAlarm"].is<float>() ? t["highAlarm"].as<float>() : gConfig.tanks[i].highAlarmThreshold;
13331316
gConfig.tanks[i].lowAlarmThreshold = t["lowAlarm"].is<float>() ? t["lowAlarm"].as<float>() : gConfig.tanks[i].lowAlarmThreshold;
13341317
gConfig.tanks[i].hysteresisValue = t["hysteresis"].is<float>() ? t["hysteresis"].as<float>() : gConfig.tanks[i].hysteresisValue;
@@ -1509,14 +1492,14 @@ static bool validateSensorReading(uint8_t idx, float reading) {
15091492
maxValid = (cfg.sensorRangeMax * conversionFactor + cfg.sensorMountHeight) * 1.1f;
15101493
}
15111494
minValid = -maxValid * 0.1f;
1512-
// If maxValue is provided and smaller, use it for clamping
1513-
if (cfg.maxValue > MIN_VALID_MAX_VALUE && cfg.maxValue * 1.1f < maxValid) {
1514-
maxValid = cfg.maxValue * 1.1f;
1515-
}
1495+
} else if (cfg.sensorType == SENSOR_DIGITAL) {
1496+
// Digital sensors have simple 0/1 values
1497+
minValid = -0.5f;
1498+
maxValid = 1.5f;
15161499
} else {
1517-
// For legacy analog and other sensors, use maxValue (allow 10% margin)
1518-
minValid = -cfg.maxValue * 0.1f;
1519-
maxValid = cfg.maxValue * 1.1f;
1500+
// For RPM and other sensors without native range, use alarm thresholds as reference
1501+
maxValid = cfg.highAlarmThreshold * 2.0f; // Allow up to 2x high alarm as valid
1502+
minValid = -maxValid * 0.1f;
15201503
}
15211504

15221505
if (reading < minValid || reading > maxValid) {
@@ -1629,32 +1612,19 @@ static float readTankSensor(uint8_t idx) {
16291612
}
16301613
case SENSOR_ANALOG: {
16311614
// Analog voltage sensor (e.g., Dwyer 626 with 0-10V, 1-5V, 0-5V output)
1632-
// Uses the same pressure-to-height conversion as current loop sensors
1615+
// Uses pressure-to-height conversion based on sensor native range
16331616
//
16341617
// Configuration:
16351618
// - analogVoltageMin/Max: Voltage output range (e.g., 0-10V, 1-5V)
16361619
// - sensorRangeMin/Max: Pressure range in sensorRangeUnit (e.g., 0-5 PSI)
16371620
// - sensorMountHeight: Height of sensor above tank bottom (inches)
1638-
// - maxValue: Optional clamping (0 = no clamping)
16391621

16401622
// Use explicit bounds check for channel (A0602 has channels 0-7)
16411623
int channel = (cfg.primaryPin >= 0 && cfg.primaryPin < 8) ? cfg.primaryPin : 0;
16421624

16431625
// Validate that we have a valid sensor range configured
16441626
if (cfg.sensorRangeMax <= cfg.sensorRangeMin || cfg.analogVoltageMax <= cfg.analogVoltageMin) {
1645-
// Fallback: legacy mode using maxValue for direct height mapping
1646-
if (cfg.maxValue < MIN_VALID_MAX_VALUE) {
1647-
return 0.0f;
1648-
}
1649-
float total = 0.0f;
1650-
const uint8_t samples = 8;
1651-
for (uint8_t i = 0; i < samples; ++i) {
1652-
int raw = analogRead(channel);
1653-
total += (float)raw / 4095.0f; // 12-bit resolution, 0-10V range
1654-
delay(2);
1655-
}
1656-
float avg = total / samples;
1657-
return linearMap(avg, 0.05f, 0.95f, 0.0f, cfg.maxValue);
1627+
return 0.0f; // Invalid configuration
16581628
}
16591629

16601630
// Read voltage (Opta A0602 analog inputs: 0-10V mapped to 0-4095)
@@ -1689,16 +1659,14 @@ static float readTankSensor(uint8_t idx) {
16891659
// Total height from tank bottom = liquid above sensor + sensor mount height
16901660
float levelInches = liquidAboveSensor + cfg.sensorMountHeight;
16911661

1692-
// Clamp: minimum is 0 (empty tank), maximum is tank capacity (if provided)
1662+
// Clamp: minimum is 0 (empty tank)
16931663
if (levelInches < 0.0f) levelInches = 0.0f;
1694-
if (cfg.maxValue > MIN_VALID_MAX_VALUE && levelInches > cfg.maxValue) levelInches = cfg.maxValue;
16951664

16961665
return levelInches;
16971666
}
16981667
case SENSOR_CURRENT_LOOP: {
16991668
// Use explicit bounds check for current loop channel
17001669
int16_t channel = (cfg.currentLoopChannel >= 0 && cfg.currentLoopChannel < 8) ? cfg.currentLoopChannel : 0;
1701-
// Note: maxValue is optional for current loop sensors - we use sensorRangeMin/Max instead
17021670
// Validate that we have a valid sensor range configured
17031671
if (cfg.sensorRangeMax <= cfg.sensorRangeMin) {
17041672
gTankState[idx].currentSensorMa = 0.0f;
@@ -1738,9 +1706,8 @@ static float readTankSensor(uint8_t idx) {
17381706
// Calculate liquid level: tank height - distance from sensor to surface
17391707
levelInches = cfg.sensorMountHeight - distanceInches;
17401708

1741-
// Clamp to valid range (0 to maxValue)
1709+
// Clamp to valid range (0 minimum)
17421710
if (levelInches < 0.0f) levelInches = 0.0f;
1743-
if (cfg.maxValue > MIN_VALID_MAX_VALUE && levelInches > cfg.maxValue) levelInches = cfg.maxValue;
17441711
} else {
17451712
// Pressure sensor mounted near BOTTOM of tank (e.g., Dwyer 626-06-CB-P1-E5-S1)
17461713
// 4mA = sensorRangeMin (e.g., 0 PSI), 20mA = sensorRangeMax (e.g., 5 PSI)
@@ -1769,9 +1736,8 @@ static float readTankSensor(uint8_t idx) {
17691736
// Total height from tank bottom = liquid above sensor + sensor mount height
17701737
levelInches = liquidAboveSensor + cfg.sensorMountHeight;
17711738

1772-
// Clamp: minimum is 0 (empty tank), maximum is tank capacity (if provided)
1739+
// Clamp: minimum is 0 (empty tank)
17731740
if (levelInches < 0.0f) levelInches = 0.0f;
1774-
if (cfg.maxValue > MIN_VALID_MAX_VALUE && levelInches > cfg.maxValue) levelInches = cfg.maxValue;
17751741
}
17761742
return levelInches;
17771743
}
@@ -1839,7 +1805,7 @@ static float readTankSensor(uint8_t idx) {
18391805

18401806
gRpmLastReading[idx] = rpm;
18411807

1842-
// Return RPM value (maxValue field is used as max RPM for scaling/alarms)
1808+
// Return RPM value (use highAlarmThreshold for max expected RPM)
18431809
return rpm;
18441810
}
18451811
default:
@@ -2034,11 +2000,9 @@ static void sendTelemetry(uint8_t idx, const char *reason, bool syncNow) {
20342000
doc["levelInches"] = state.currentInches; // 1.0 or 0.0
20352001
} else if (cfg.sensorType == SENSOR_CURRENT_LOOP) {
20362002
doc["sensorType"] = "currentLoop";
2037-
doc["maxValue"] = cfg.maxValue;
20382003
doc["levelInches"] = state.currentInches;
20392004
doc["sensorMa"] = state.currentSensorMa; // Raw 4-20mA reading
20402005
} else {
2041-
doc["maxValue"] = cfg.maxValue;
20422006
doc["levelInches"] = state.currentInches;
20432007
}
20442008
doc["reason"] = reason;

0 commit comments

Comments
 (0)