Pump monitor and control - UPDATE

UPDATE:

1. I changed the Master Control button to use Virtual Pin7. This way when the Timer Starts/Stops the pump, the Master Power button reflects the state correctly.

2. I changed the way the Power saving mode work in order to circumnavigate the use of delay() that was blocking the Current sensor readings. This was done by adding another scheduler to set Power Save times and utilising and IF statement to determine which timer should be activated.

A project that address two simple needs:

1.) Remote control of Pumps in agricultural areas - Boron
2.) Energy saving and convenient way to control Swimming Pool pumps - Photon


PLEASE BE CAREFUL - HIGH VOLTAGE

The scope was to create a device with as few as possible external components such as external PSU and CS. Hence the use of the AC/DC converter to be able to power the device directly from the actual current being monitored/controlled.

Goal:

Aside from obvious convenience, the hope is that if users can see in real time the amount of power they are consuming running equipment like swimming pool pumps, they might optimise the run times of these equipment. The holiday mode implementation should also greatly reduce the power consumption during times that the pool is not in use.

Bill of materials:

First things First:

The purpose of this build was to assist people in easily control High Voltage devices from a mobile device. The device will measure the power consumption, control a load connected to it as well as monitor two external sensors. For the purposes of this Project Share, we will look at the Pool Pump control switch.

PCB Design:

Disclaimer: Even though thoroughly tested, I am by no means and expert when it comes to designing PCB’s so please be careful when following these instructions.

There are a some factors to keep in mind when working with higher Voltages and Current on PCB’s. One of the key things is to make sure the traces on the PCB can support the current that will be drawn by the load connected to it. I have found this PCB trace width calculator to be very helpful:

PCB Trace width calculator

As you can see, there is a clear distinction between the 240V traces and the normal traces. I also tried the keep the 240V traces away from the normal traces as far as I could by placing them on the bottom layer of the PCB. Due to the self imposed size constraint, it was not always possible though.

In addition to this, I opted for 2oz copper weight on the traces allowing for a 10-15 degree temperature rise above ambient when at full load.

You can find my Eagle files here:

EAGLE Board File
EAGLE Schematic

The Tough Part: (work in progress)

C++ … my Achilles heel :joy:

Below is my code, I am sure you will be able to write much neater and more efficient code. If not, this works. I am working to improve the code and will post the edit as soon as I am done.

STARTUP(WiFi.selectAntenna(ANT_EXTERNAL));      //Set to use external antenna//

// //  Project:        High Current Pump Switch 
// //                  A project by FireFli (PTY) LTD

// //  Date:           17 February 2020
// //  Compiled by:    Henk Goosen - Kragtig.com
// //                  Friedl Basson - FireFli (PTY) LTD
// //                                      
// //  Details:        Remote control via High Current relay.
// //                  Monitor power consumption.
// //                  Post result to BLynk

// //  Product:        Ladybug
// //  Firmware:       V1.0.2

#include <spark-dallas-temperature.h>   //  Temperature
#include <OneWire.h>                    //  Temperature
#include <blynk.h>
#include <cmath>
#include <Adafruit_Sensor.h>

char auth[] = "YOUR_BLYNK_AUTH_CODE";

// Establish Ubidots Webhook
const char* WEBHOOK_NAME = "Amp";       
   
#define CURRENT_SENSOR A5
#define BLYNK_PRINT Serial
#define ONE_WIRE_BUS A3                 //  Temperature

OneWire oneWire(ONE_WIRE_BUS);          //  Temperature
DallasTemperature sensors(&oneWire);    //  Temperature
    
int led = D7;

int redPin = D3;       
int greenPin = D1;    
int bluePin = D2;

int i = 0;
int d;

int normalrun;                          //Timer - Normal Run times
int powersaving;                        //Timer - Power Saving Run times
int widgetTimerEnable;                  //Timer - Enable/Disable Power Saving Mode

int MasterPSU;                          //Timer - Master PSU


//Simple Power unit Cost START//
int Fin;
BLYNK_WRITE(V6)
{
  Fin = param.asInt();                   // Assigning incoming value from pin V6 to variable
} 
//Simple Power unit Cost END//

//NEW MASTER PSU  - START//
BLYNK_WRITE(V7)
{
  MasterPSU = param.asInt();            // Assigning incoming value from pin V7 to variable
    if (MasterPSU == 1) {
        digitalWrite(redPin, LOW);
        digitalWrite(bluePin, HIGH);
        digitalWrite(led, HIGH);
    
    }else if (MasterPSU == 0) {
        digitalWrite(redPin, HIGH);
        digitalWrite(bluePin, LOW);
        digitalWrite(led, LOW);
    }            
} 
//NEW MASTER PSU  - END//

//NEW TIMER IDEA - START//
BLYNK_WRITE(V10)                        // Holiday Mode Timer Widget Enable Button
{                                       
    widgetTimerEnable = param.asInt();
}


BLYNK_WRITE(V0)                        // Normal Mode Timer Widget
{                                      
     normalrun = param.asInt();

if (normalrun == 1 && widgetTimerEnable == 0) {
        // Do Power Saving & Timer On stuff (Timer & Power Saving ON)
        digitalWrite(redPin, LOW);
        digitalWrite(bluePin, HIGH);
        digitalWrite(led, HIGH);
        Blynk.virtualWrite(V7,HIGH);
        
 }else if (normalrun == 0 && widgetTimerEnable == 0) {
        digitalWrite(redPin, HIGH);
        digitalWrite(bluePin, LOW);
        digitalWrite(led, LOW);
        Blynk.virtualWrite(V7,LOW);
    }
}

BLYNK_WRITE(V1)                        // Holiday Mode Timer Widget 
{                                     
      powersaving = param.asInt();

if (powersaving == 1 && widgetTimerEnable == 1) {   
        digitalWrite(redPin, LOW);
        digitalWrite(greenPin, HIGH);
        digitalWrite(led, HIGH);
        Blynk.virtualWrite(V7,HIGH);

 }else if (powersaving == 0 && widgetTimerEnable == 1) {   
        digitalWrite(redPin, HIGH);
        digitalWrite(greenPin, LOW);
        digitalWrite(led, LOW);
        Blynk.virtualWrite(V7,LOW);
    }
}
//NEW TIMER IDEA - END/

float amplitude_current;     // Float amplitude current
float effective_value;       // Float effective current

float Celsius = 0;          //  Temperature

void setup() {

    sensors.begin();        //  Temperature   

    Serial.begin(115200);
    Blynk.begin(auth);
    
    pinMode(led, OUTPUT);
    
    pinMode(redPin, OUTPUT);
    pinMode(bluePin, OUTPUT);
    pinMode(greenPin, OUTPUT);
    
}

void pins_init()
{
    pinMode(CURRENT_SENSOR, INPUT);
}

const int Resolution = 3277;  
const float FullScaleAmps = 40.0;
const int ZeroValue = 2047;    
const float MilliVoltsPerAmp = float(Resolution) / FullScaleAmps;


float getRms()
{
    const int numSamples = 1000;
    // The integer value (between 0 and 4095) that we read from the sensor
    int sensorValue;
    // The timestamp of the "previous" sample
    int ts_prev = 0;
    // The difference between the "present" and "previous" timestamps
    int ts_delta = 0;
    // We keep the value of the first timestamp to use in the final
    // averaging step. Alternatively we could simply sum up all the
    // deltas from the start to the end.
    int ts_first = 0;
    // The integaer value of the "prevous" sample.
    int value_prev = 0;
    // Total elapsed time for the 1000 samples.
    int elapsedTime = 0;
    // The timestamp as read from the sensor.
    uint32_t timeStamp = 0;
    // The measured values (integers) are converted to floats for the calculation.
    float val_now = 0.0;
    float val_prev = 0.0;
    float accum = 0.0;
    float avgValue = 0.0;
    float rms = 0.0;
    
    for (int k=0; k<numSamples; k++) {
        sensorValue = analogRead(CURRENT_SENSOR);
        timeStamp = millis();
        if (ts_prev > 0) {
            ts_delta = timeStamp - ts_prev;
            if (ts_delta < 0) {
                // If we detect an overflow we return an invalid RMS current value immediately
                return -1;
            } else {
                val_now = (float(sensorValue) - float(ZeroValue)) / MilliVoltsPerAmp;
                val_prev = (float(value_prev) - float(ZeroValue)) / MilliVoltsPerAmp;
                accum += 0.5 * ts_delta * (val_now*val_now + val_prev*val_prev);
            }
        } else {
            // This is only executed the first time round the loop
            ts_first = timeStamp;
        }
        ts_prev = timeStamp;
        value_prev = sensorValue;
    }
  
    elapsedTime = timeStamp - ts_first;
    avgValue = accum / float(elapsedTime);
    rms = sqrt(avgValue);
    return rms;
}

    int debug;

void temperature() {
    sensors.requestTemperatures();

    Celsius = sensors.getTempCByIndex(0);

    Serial.print(Celsius);
    Serial.println(" *C ");
    Blynk.virtualWrite(V2, Celsius);
}


void CS() {

    float sensor_value = getRms();
    float kWh = (sensor_value * 235)/1000;
    float Fin2 = (kWh*Fin);
 
  if (sensor_value >= 0) {

     char data[16];
     snprintf(data, sizeof(data), "%.3f", sensor_value);
 
     Particle.publish("Amp", String::format("{\"data\":%.3f}", sensor_value), PRIVATE); 
     Particle.publish("kWh", String::format("{\"data\":%.3f}", kWh), PRIVATE); 
     Particle.publish("Fin", String::format("{\"data\":%.3f}", kWh), PRIVATE);
     
        Blynk.virtualWrite(V5, sensor_value);
        Blynk.virtualWrite(V4, kWh);
        Blynk.virtualWrite(V3, Fin2);
     
     for(uint32_t ms = millis(); millis() - ms < 5000; Particle.process());
    
    } else {
     
    } 
}

void loop() {

    Blynk.run();
    CS();
    temperature();
    
}

The Dashboard:

As this is more of a consumer product, I opted to go with BLYNK. I think there is more value in being able to control these devices from a mobile device rather a web based dashboard. I am a complete newbie to BLYNK so unfortunately cannot be of much assistance there. If you need help with creating additional layouts, best to approach the forum… just make sure you are a master at C++ and that your void loop() is spotless clean else you might run into some trouble :relaxed:

I used the free version as this I am still in prototype stage.

On this APP I have the following:

  1. Manual ON/OFF Switch
  2. Simple Scheduler/Timer for Power saving mode and one fore Normal Mode
  3. Holiday Mode button
  4. Push Notifications
  5. Electricity Unit price selector
  6. Estimated spent (per hour) display
  7. kWh display
  8. Current drawn display
  9. Pool Temperature display

Operation:

The pump can be switched on/off at any time using the manual switch. In addition to this, there is a simple scheduler that can be used to set daily run times. BLYNK also offers a more comprehensive Scheduler that will allow you to set days of the week as well, but I have not been able to make it work as it requires more complex coding.

The Holiday Mode button will activate static power saving mode, effectively reducing the runtime to the to the hard coded minutes per day.

A push notification is sent to the mobile device if the switch has been offline for (in my case) more than 5 minutes.

The device make use of simple RGB indicator led. RED means device is online and waiting for run time. BLUE is when connected load is running in normal mode. GREEN is indicating that the load is running in Holiday (energy saving) Mode.

Future scope:

I am planning to add couple more features i.e. preventative maintenance indicator, slider to dynamically set Holiday Mode run time and wireless temperature sensor.

I hope you enjoyed the project. Stay curious, Keep Innovating!

@Joe

10 Likes

This is awesome!!

1 Like

Thanks, appreciate the comment :blush:

I need to make some improvement to the code… will probably never stop learning, waaayyyy to curious and always want to figure things out, haha.

2 Likes