Introduction: Automate Submersible Pump With Start Cap, Wifi, ESP8266, Nodemcu, Home Assistant, MQTT
Automate Submersible Pump that has a run capacitor and start capacitor and single phase.
This project can be customized as needed.
In this process, normal manual control (start and stop buttons) will also work in the panel as well as smart control.
Pre-requisites: Knowledge of ESP or Arduino programming
Supplies
There are a few types of Pump Control Panels available.
Please watch this video (especially if the image doesn't match your panel deisng) to learn a little about its working. The Stop switch in this case is a Normally Connected Switch.
If yours don't have a wire between the start and stop switch then yours probably looks like the one in the image which has an overload relay,
Things you need
- NodeMCU or D1 Mini or other microcontrollers
- Solid State Relay 40Amps (high amps must be needed)
- Solid State Relay 2Amps
- 1channel Normal 10Amps Relay Module with NO NC connections
- Hi-Link 5V 10W or 5W Power Supply
- LED (Optional)
- IR Receiver (Optional)
- OLED Screen (Optional)
- Ultrasonic Sensor to detect water level or any other sensor you want
- Cat6 Shielded cable to connect to the sensor in the water tank from the microcontroller
- A box to keep all this stuff inside
Step 1: Connections
PANEL BOX CONNECTION PART
Make a connection from the Ammeter output to one input of both your Solid State Relays.
In your Start Switch, there are 4 terminals, out of which 2 are connected together and those have +ve AC supply. One of the terminals is connected to the Start Capacitor. Connect a wire from this terminal to the output of the 40A Solide State Relay.
The last terminal will be connected from the start switch to the overload relay or the contactor depending on the model, Make a connection between this terminal and the output of the 2Amps Solid State Relay.
Now, we will need the NC and Com of our normal relay and connect as needed.
- If your model is as on the video and has a wire between the start and stop buttons. Connect 2 wires from two terminals of the stop button to one to NC and another one to Com
- If your model is as on the image and has an overload relay. First, remove the wire between 95 and 93, then make a connection from 95 to NC and then 93 to Com or vice-versa.
CONTROLLER CONNECTION PART
Give AC power to Hi-Link Module for powering the controller.
Connect all GND together except LED. LED anode will go to 5v or 3.3v via a resistance.
Pump Off Relay (Normal Relay) runs on active LOW and will connect to D3.
Pump ON Relay (Solid State Relay 2A) runs on active HIGH and will connect to D6.
Start Cap Relay (Solid State Relay 40A) runs on active HIGH and will connect to D5.
LED connects to D0.
IR Receiver if used connects to D4.
OLED if used, connects to SCL-> D1, SDA->D2.
Step 2: Start and Stop Pump Code
int LED = D0;
int OffRelay = D3;
int OnRelay = D6;
int startCap = D5;
bool ptimer = false;
void setup() {
Serial.begin(9600);
pinMode(LED, OUTPUT);
pinMode(OffRelay, OUTPUT);
pinMode(OnRelay, OUTPUT);
pinMode(startCap, OUTPUT);
digitalWrite(OffRelay, LOW); //this will stop the pump if running (this is done, because during boot some pins sends HIGH or LOW depending on the pin used, if OnRelay pin receives HIGH during boot even for a millisecond, pump may start
digitalWrite(OnRelay, LOW);
digitalWrite(startCap, LOW);
digitalWrite(LED, HIGH);
delay(2000);
digitalWrite(OffRelay, HIGH); //off relay
}
void loop(){
//use own logic
}
void startSequence(){
if(ptimer == false) {
Serial.println("starting");
ptimer = true;
digitalWrite(LED, LOW);
digitalWrite(OnRelay, HIGH);
digitalWrite(startCap, HIGH);
delay(2000);
digitalWrite(startCap, LOW);
digitalWrite(OnRelay, LOW);
Serial.println("started");
}
}
ptimer variable checks if the pump already started before or not.
this function will activate the relay for 2 secs only, which is like pressing the start button for 2 secs.
void stopSequence(){
Serial.println("stopping");
ptimer = false;
digitalWrite(OffRelay, LOW);
digitalWrite(LED, HIGH);
delay(3000);
digitalWrite(OffRelay, HIGH);
Serial.println("stopped");
}
this will activate the stop relay for 3 secs, which is like pressing the stop button for 3 secs.
You can the stopSequence() anytime, and it will stop the pump even if the pump is started manually by pressing the button on the panel.
Step 3: Tank Sensor Part
#define echoPin D8
#define trigPin D7
unsigned long previousMillis = 0;
const long interval = 30000; //update interval is 30 secs
void setup(){
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
digitalWrite(trigPin, LOW);
}
void loop(){
if (millis() - previousMillis >= interval) {
previousMillis = millis();
float distance = readTankLevel();
//distance calibration needed beforehand, need to know total length of tank, in this case 120 cm, 10cm is offset
distance = distance - 10;
if(distance<1){
distance = 0;
}
if(distance > 109){
distance = 110;
}
float waterLevel = 1000 - (distance * 9.09); //calibration needed, in this case 1 cm fills up 9.09 Litres
//do what you want with waterlevel
}
}
float readTankLevel(){
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long duration = pulseIn(echoPin, HIGH);
float distance = duration * 0.017;
Serial.println(distance);
return distance;
}
Step 4: Extra Steps
Code Explanation:
The pump is filled about 95% when full, this is done intentionally in this code.
Since the water tank is far away from the panel, a different microcontroller is used to read the water level, and that information is sent to Home Assitant via MQTT, home assistant automation decides at which level to start and stop the pump and sends MQTT message to start or stop the pump.
This code also takes input from an IR Receiver to start and stop the pump and can set a timer (in minutes) for how long the pump will be on, and this part will work even if WiFi and other communication disconnects.
Home Assistant also calculates and sends a timer value during automation start, this redundancy is done in case Wifi or communication disconnects after the pump starts, so the controller will have a timer to stop the pump after a specific time, and also you get an idea of how much time left to fill the tank.
OLED displays the water level as sent via MQTT and also shows the remaining timer during the on, and Last ON status as sent via MQTT.
The tank Sensor also sends a stop message in MQTT when the water level reaches 98%, which is helpful in case the pump is manually started from the panel.
FEEL FREE TO CUSTOMIZE AS NEEDED.
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <IRrecv.h>
#include <ArduinoOTA.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void callback(char* topic, byte* payload, unsigned int length);
void displayStatus(String msg, int tsize);
const unsigned char water_icon [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x07, 0xc0, 0x07, 0xc0, 0x0f, 0xe0, 0x0f, 0xf0, 0x1f, 0xf0,
0x1f, 0xf0, 0x1f, 0xf0, 0x1f, 0xf0, 0x0f, 0xe0, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00
};
//spi pins -> scl-d1, sda-d2
int LED = D0;
int OffRelay = D3;
int OnRelay = D6;
int startCap = D5;
uint16_t RECV_PIN = D4;
IRrecv irrecv(RECV_PIN);
decode_results results;
bool onlineMode = false;
const char* ssid = "Amit";
const char* password = "*******";
const char* mqttServer = "MQTT IP";
const char* mqttUsername = "MQTT NAME";
const char* mqttPassword = "MQTT PASS";
const char* mqttClientId = "pump";
#define subNode "/node/pump"
#define sub "/node/pump/switch/status"
#define pub "/node/pump/switch/update"
#define subTime "/node/pump/timer/status"
#define pubTime "/node/pump/timer/update"
#define subStatus "/node/pump/status"
#define subWater "/node/pump/level"
WiFiClient wifiClient;
PubSubClient client(mqttServer, 1883, callback, wifiClient);
bool ptimer = false;
unsigned long startTime = millis ();
int intervalMin = 10; //10min timer default
unsigned long interval = intervalMin*60000;
String waterLevel = "Loading";
int watchInterval = 1000;
unsigned long watchIntervalLast = 0;
long watchSecondsLimit = 600;
String lastOn = "";
void setup() {
Serial.begin(9600);
irrecv.enableIRIn(); //ir
delay(10);
pinMode(LED, OUTPUT);
pinMode(OffRelay, OUTPUT);
pinMode(OnRelay, OUTPUT);
pinMode(startCap, OUTPUT);
digitalWrite(OffRelay, LOW); //on will stop the pump if running
digitalWrite(OnRelay, LOW);
digitalWrite(startCap, LOW);
digitalWrite(LED, HIGH);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
display.clearDisplay();
display.setTextSize(2);
display.display();
display.drawBitmap(0, 0, water_icon, 15, 15, SSD1306_WHITE);
display.setCursor(20,0);
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
display.println(waterLevel);
display.display();
//ota
ArduinoOTA.setHostname("Node - Pump");
ArduinoOTA.setPassword("some pass");
ArduinoOTA.begin();
WiFi.begin(ssid, password);
delay(2000);
digitalWrite(OffRelay, HIGH); //off relay
}
void startSequence(){
if(ptimer == false) {
Serial.println("starting");
ptimer = true;
displayStatus("Starting", 2);
if(onlineMode){
client.publish(pub, "0", true);
}
startTime = millis();
digitalWrite(LED, LOW);
digitalWrite(OnRelay, HIGH);
digitalWrite(startCap, HIGH);
delay(2000);
digitalWrite(startCap, LOW);
digitalWrite(OnRelay, LOW);
Serial.println("started");
display.fillRect(0, 17, 132, 16, SSD1306_BLACK);
display.display();
}
}
void stopSequence(){
Serial.println("stopping");
ptimer = false;
displayStatus("Stopping", 2);
if(onlineMode){
client.publish(pub, "1", true);
}
digitalWrite(OffRelay, LOW);
digitalWrite(LED, HIGH);
delay(3000);
digitalWrite(OffRelay, HIGH);
Serial.println("stopped");
}
void loop(){
if(WiFi.status() == WL_CONNECTED){
if(client.connected()){
onlineMode = true;
}else{
Serial.print("Attempting MQTT connection...");
if (client.connect(mqttClientId, mqttUsername, mqttPassword, subNode, 0, true, "offline")) {
client.publish(subNode, "online", true);
//update timer and status in HA if pump is alreay running
if(ptimer){
long elapsedtime = (millis() - startTime)/60000;
int rtime = intervalMin - elapsedtime;
client.publish(pubTime, String(rtime).c_str(), true);
client.publish(pub, "0", true);
}else{
client.publish(pub, "1", true);
}
Serial.print("\tMQTT Connected");
onlineMode = true;
client.subscribe(sub);
client.subscribe(subTime);
client.subscribe(subStatus);
client.subscribe(subWater);
}else{
onlineMode = false;
display.setCursor(20,0);
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
display.println("No WiFi");
display.display();
}
}
}else{
Serial.println("No wifi.");
onlineMode = false;
display.setCursor(20,0);
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
display.println("No WiFi");
display.display();
}
if (irrecv.decode(&results)) {
unsigned int ircode = results.value;
Serial.println(ircode);
if(ircode == 33480735){
if(onlineMode){
client.publish(sub, "1", true);
}else{
stopSequence();
}
}else if(ircode == 33441975){
if(onlineMode){
client.publish(sub, "0", true);
}else{
startSequence();
}
}else if(ircode == 33448095){
if(ptimer == false){
intervalMin = intervalMin+1;
interval = intervalMin*60000;
watchSecondsLimit = (intervalMin*60) - 2;
if(onlineMode){
client.publish(pubTime, String(intervalMin).c_str(), true);
}
String msg = "Timer: "+ String(intervalMin);
displayStatus(msg, 2);
delay(500);
displayStatus(lastOn, 1);
}
}else if(ircode == 33464415){
if(ptimer == false){
intervalMin = intervalMin-1;
interval = intervalMin*60000;
watchSecondsLimit = (intervalMin*60) - 2;
if(onlineMode){
client.publish(pubTime, String(intervalMin).c_str(), true);
}
String msg = "Timer: "+ String(intervalMin);
displayStatus(msg, 2);
delay(500);
displayStatus(lastOn, 1);
}
}
irrecv.resume();
}
if(onlineMode){
client.loop();
}
if(ptimer){
if (millis () - watchIntervalLast > watchInterval){
watchIntervalLast = millis ();
int rmin = watchSecondsLimit/60;
int rsec = watchSecondsLimit%60;
watchSecondsLimit = watchSecondsLimit - 1;
String srmin = String(rmin);
if(rmin < 10){
srmin = "0"+srmin;
}
String srsec = String(rsec);
if(rsec < 10){
srsec = "0"+srsec;
}
String msg = "ON: " +srmin+ ":" +srsec;
displayTimer(msg);
}
if (millis () - startTime < interval){
digitalWrite(LED, LOW);
}else{
stopSequence();
}
}
ArduinoOTA.handle();
delay(10);
}
void callback(char* topic, byte* payload, unsigned int length) {
String topicStr = topic;
payload[length] = '\0';
Serial.println("Callback update.");
Serial.print("Topic: ");
Serial.println(topicStr);
Serial.println(String((char*) payload));
if (topicStr == sub) {
if(payload[0] == '0'){
startSequence();
}else if (payload[0] == '1'){
stopSequence();
}
}else if (topicStr == subTime) {
if(ptimer == false){
intervalMin = String((char*) payload).toInt();
interval = (intervalMin)*60000;
watchSecondsLimit = (intervalMin*60) - 2; // 2 sec delay in startsequnce will be gone
Serial.println("Interval set to min: ");
Serial.println(intervalMin);
}
client.publish(pubTime, String(intervalMin).c_str(), true);
}else if(topicStr == subStatus){
lastOn = "Last On: " + String((char*) payload);
if(ptimer == false){
displayStatus(lastOn, 1);
}
}else if(topicStr == subWater){
waterLevel = String((char*) payload) + " L";
display.fillRect(19, 0, 132, 16, SSD1306_BLACK);
display.display();
display.setCursor(25,0);
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
display.println(waterLevel);
display.display();
}
}
void displayStatus(String msg, int tsize){
display.fillRect(0, 17, 132, 16, SSD1306_BLACK);
display.display();
display.setCursor(0,17);
display.setTextSize(tsize);
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
display.println(msg);
display.display();
}
void displayTimer(String msg){
display.setCursor(0,17);
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
display.println(msg);
display.display();
}
Sensor Part
void loop(void) {
if(WiFi.status() == WL_CONNECTED){
if(client.connected()){
onlineMode = true;
}else{
if (client.connect(mqttClientId, mqttUsername, mqttPassword, subNode, 0, true, "offline")) {
client.publish(subNode, "online", true);
onlineMode = true;
}else{
onlineMode = false;
}
}
}else{
onlineMode = false;
}
if(onlineMode){
client.loop();
if (millis() - previousMillis >= interval) {
previousMillis = millis();
float distance = readTankLevel();
client.publish("/node/pump/distance", String(distance).c_str(), true);
distance = distance - 10;
if(distance<1){
distance = 0;
}
if(distance > 109){
distance = 110;
}
float waterLevel = 1000 - (distance * 9.09);
client.publish("/node/pump/level", String(waterLevel).c_str(), true);
if(waterLevel > 980){
client.publish("/node/pump/switch/status", "1", true); //stop pump
}
}
}
ArduinoOTA.handle();
delay(100);
}
void callback(char* topic, byte* payload, unsigned int length) {
}
float readTankLevel(){
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long duration = pulseIn(echoPin, HIGH);
float distance = duration * 0.017;
Serial.println(distance);
return distance;
}
HA Automations:
alias: Pump Auto Start
description: Pump Auto Start When Water Level below 401 L
trigger:
- platform: mqtt
topic: /node/pump/level
condition:
- condition: not
conditions:
- condition: state
entity_id: switch.pump_switch
state: unavailable
- condition: state
entity_id: switch.pump_switch
state: "on"
- condition: numeric_state
entity_id: sensor.tank_water_level
below: 600
action:
- service: input_number.set_value
target:
entity_id: input_number.pump_slider
data:
value: >-
{{((1000 - (states.sensor.tank_water_level.state | int)) * 0.025) |
float}}
- wait_for_trigger:
- platform: mqtt
topic: /node/pump/timer/update
timeout:
hours: 0
minutes: 0
seconds: 5
milliseconds: 0
continue_on_timeout: false
- delay:
hours: 0
minutes: 0
seconds: 2
milliseconds: 0
- service: switch.turn_on
data: {}
target:
entity_id: switch.pump_switch
- service: notify.alexa_media
data:
target: media_player.amit_s_echo
title: Pump Started
message: >-
Pump Auto Started for {{ states.input_number.pump_slider.state | int }}
minutes
data:
method: all
type: announce
mode: single