Node Red and Tank Level Sensors

Hello,

I have a mostly off grid solar electric system in the Philippines. We have an open shallow well that I want to monitor when we pull water from it. So far I have had good success writing code for an ESP32 processor with Wifi that measures voltage from a hydrostatic transducer planned to be at the bottom of the well.

This is the sketch I wrote for the ESP32:

#include <WiFi.h>
#include <PubSubClient.h>

// — 1. Network & Broker Configuration —
const char* ssid = “Isla1”;
const char* password = “####”;
const char* mqtt_server = “192.168.1.97”; // Your Cerbo GX IP
const char* mqtt_topic = “well/level/inches”;

// — 2. Hardware & Calibration Settings —
const int sensorPin = 32; // Connected to IO32 on your 38-pin board
const float zeroOffsetMV = 4.0; // Your measured 4mV “dry” offset
const float scaleFactor = 26.074; // 26.074 mV per inch (from 1.06V @ 40.5")

// Variables for smoothing
static float filteredInches = 0;
const float filterAlpha = 0.1; // Adjust (0.0 to 1.0) for more/less smoothing

WiFiClient espClient;
PubSubClient client(espClient);

void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(“.”);
}

Serial.println(“\nWiFi connected”);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}

void reconnect() {
while (!client.connected()) {
Serial.print(“Attempting MQTT connection…”);
// Create a unique client ID
String clientId = “ESP32Well-”;
clientId += String(random(0xffff), HEX);

if (client.connect(clientId.c_str())) {
  Serial.println("connected");
} else {
  Serial.print("failed, rc=");
  Serial.print(client.state());
  Serial.println(" try again in 5 seconds");
  delay(5000);
}

}
}

void setup() {
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);

// Set ADC resolution to 12-bit (0-4095) for better accuracy
analogReadResolution(12);
}

void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();

// 1. Read the Sensor
int rawADC = analogRead(sensorPin);
float voltage = (rawADC / 4095.0) * 3.3; // Convert ADC to Volts
float currentMV = voltage * 1000.0; // Convert to Millivolts

// 2. Apply Calibration
float adjustedMV = currentMV - zeroOffsetMV;
if (adjustedMV < 0) adjustedMV = 0; // Floor at zero
float currentInches = adjustedMV / scaleFactor;

// 3. Apply Smoothing Filter (Exponential Moving Average)
//filteredInches = (filteredInches * (1.0 - filterAlpha)) + (currentInches * filterAlpha);
filteredInches = currentInches; // <— This makes the output “Live”
// 4. Create the JSON Payload
// Declaring as a char array fixes the “invalid conversion” error
char msgBuffer[64];
snprintf(msgBuffer, sizeof(msgBuffer), “{“value”: %.1f}”, filteredInches);

// 5. Publish to Cerbo
if (client.publish(mqtt_topic, msgBuffer)) {
Serial.print("Sent: ");
Serial.println(msgBuffer);
} else {
Serial.println(“MQTT Publish Failed”);
}

delay(2000); // 2-second updates for well level is ideal
}

The sketch reads a voltage on one of its analog input pins and transmits it to my Cerbo GX using MQTT. The Cerbo is running Venus OS Large and Node Red.

I have a short function that does some conversion and displays a number that represents depth of water in inches. It all works but there is nothing in the Venus OS or Node Red that allows me to express that number in inches instead of a volume percentage.

Her is the short function:

// — CONFIGURATION —
const alpha = 0.15;
const maxDepthInches = 60.0;
const sensorOffset = 5.0;

// — PROCESSING —
let rawInches = parseFloat(msg.payload.value);

if (!isNaN(rawInches)) {
// 1. Conditional Offset Logic
let correctedInches;
if (rawInches === 0) {
correctedInches = 0;
} else {
correctedInches = rawInches + sensorOffset;
}

// 2. Exponential Moving Average (EMA) Smoothing
let lastSmoothedInches = context.get('lastInches') || correctedInches;
let smoothedInches = (alpha * correctedInches) + ((1 - alpha) * lastSmoothedInches);
context.set('lastInches', smoothedInches);

// 3. Display as Inches instead of Percentage
// By passing smoothedInches to /Level, the Cerbo will display the numeric depth.
let displayValue = parseFloat(smoothedInches.toFixed(1));

// 4. Victron D-Bus Formatting
// Convert to "Virtual Cubic Meters" for standard tank capacity logic
let virtualRemaining = smoothedInches / 1000.0;
let virtualCapacity = maxDepthInches / 1000.0;

// 5. Final JSON Object
msg.payload = {
    "/Level": displayValue,      
    "/Remaining": parseFloat(virtualRemaining.toFixed(4)), 
    "/Capacity": parseFloat(virtualCapacity.toFixed(4)),   
    "/Status": 0                                       
};

return msg;

}
return null;

Does anyone out there know how to get the system do display inches?

Thanks in advance for any insight.

Best regards,

Tom