# Node Red and Tank Level Sensors

**URL:** <https://community.victronenergy.com/t/node-red-and-tank-level-sensors/58976>\
**Category:** Node-RED\
**Tags:** node-red, vrm, cerbo\_gx, offgrid, diy, cerbo-gx\
**Created:** [7 June 2026 12:09 UTC](https://community.victronenergy.com/t/node-red-and-tank-level-sensors/58976 "2026-06-07T12:09:31Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![vanderbt](https://dub1.discourse-cdn.com/victron/user_avatar/community.victronenergy.com/vanderbt/32/3388_2.png) [@vanderbt](https://community.victronenergy.com/u/vanderbt)\
**Post date:** [7 June 2026 12:09 UTC](https://community.victronenergy.com/t/node-red-and-tank-level-sensors/58976/1 "2026-06-07T12:09:31Z")

</div>

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);

```auto
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;  
}

```auto
// 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
