Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/Loic74650/PoolMaster
Browse files Browse the repository at this point in the history
  • Loading branch information
Loic74650 committed Jul 25, 2023
2 parents 80a5d27 + ff1167d commit bc79310
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 3 deletions.
Binary file modified Nextion/PoolMaster.tft
Binary file not shown.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,31 @@ b/ the ORP reading is strongly affected by the pH value and the water temperatur
c/ prefer platinium ORP probes for setpoints >500mV (ie. Pools and spas)<br />
e/ the response time of ORP sensors can be fast in reference buffer solutions (10 secs) and yet very slow in pool water (minutes or more) as it depends on the water composition <br /><br />

<h4>Details on the PID regulation</h4>
<p>
In this project the Arduino PID library is used to start/stop the chemicals pumps in a cyclic manner, similar to a "PWM" signal.<br />
The period of the cycle is defined by the WINDOW SIZE parameter which is fixed (in Milliseconds). What the PID library does is vary the duty cycle of the cycle.<br />

If the computed error in the PID loop is null or negative, the duty cycle is set to zero (the ouput of the function PID.Compute()) and the pump is never actuated.<br />
If the error is positive, the duty cycle is set to a value between 0 and a max value (equal to the WINDOW SIZE, ie. the pump is running full time).<br />

So in practice the ouput of the PID.Compute() function is a duration in milliseconds during which the pump will be activated for every cycle.<br />
If for instance, the WINDOW SIZE is set to 3600000ms (ie. one hour) and the output of the PID is 600000ms (ie. 10mins), the pump will be activated for 10mins at the begining of every hour cycle.<br /><br />

On the default Kp,Ki,Kd parameter values of the PID:<br />
By default in this project, Ki and Kd are null for stability reasons and so the PID loop is only a P loop, ie. a proportional loop.<br />
Adding some Ki and Kd to the PID loop may theoretically increase regulation performance but is also more complex to adjust and could result in instabilities. Since a P-only loop worked well enough and that safety considerations should be taken seriously in this project, I left it as is.<br />

For my 50m3 pool the Kp default values are 2000000 for the pH loop and 4500 for the Orp loop. They were chosen experimentally in the following way:<br />

I experimentally checked how much chemical was required to change the measured parameter (pH or Orp) by a certain amount. For instance I determined that 83ml of acid changed the pH by 0.1 for my 50m3 pool. The flow rate of the acid pump being 1.5L/hour, we can then determine for how many minutes the pump should be activated if the pH error is 0.1, which are (0.083*60/1.5) = 3.3minutes or roughly 200000ms.<br />
And so for an error of 1 in the pH PID loop, the pump needs to be activated 10 times longer, ie. during 2000000ms, which should be taken as the Kp value. The same reasoning goes for the Kp value of the Orp PID loop.<br /><br />

On the WINDOW SIZE:<br />
Various parameters influence the speed at which an injected chemical in the pool water will result in a variation in the measured pH or Orp. <br />
Experimentally I measured that in my case it can take up to 30minutes and therefore the injection cycle period should be at least 30mins or longer in order not to inject more chemical over the following cycles thinking that it required more when in fact the chemical reactions simply needed more time to take effect, which would eventually result in overshooting.
So in my case I setlled for a safe one hour WINDOW SIZE (ie. 3600000ms) <br /><br />


<p align="center"> <img src="/docs/PoolMaster.jpg" width="702" title="Overview"> </p> <br /><br />
<p align="center"> <img src="/docs/PoolMasterBox_pf.jpg" width="702" title="Overview"> </p> <br /><br />
Expand Down
11 changes: 8 additions & 3 deletions source/PoolMaster/PoolMaster.ino
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,12 @@
https://github.com/johnrickman/LiquidCrystal_I2C (rev 1.1.2)
https://github.com/thijse/Arduino-EEPROMEx (rev 1.0.0)
https://github.com/EinarArnason/ArduinoQueue
https://github.com/Loic74650/Pump (rev 0.0.1)
https://github.com/PaulStoffregen/Time (rev 1.5) -> /!\ Bug: in file "Time.cpp" "static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31};" must be replaced by "static volatile const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31};"
https://github.com/adafruit/RTClib (rev 1.2.0)
https://github.com/thomasfredericks/Bounce2 (rev 2.5.2)
https://github.com/fasteddy516/ButtonEvents (rev 1.0.1)
https://github.com/TrippyLighting/EthernetBonjour
https://github.com/Seithan/EasyNextionLibrary (rev 1.0.3)
https://github.com/Seithan/EasyNextionLibrary (rev 1.0.6)
http://arduiniana.org/libraries/streaming/ (rev 5)
https://github.com/tardate/TextFinder
Expand All @@ -140,7 +139,7 @@
#include <ArduinoJson.h>
#include <EEPROMex.h>
#include "ArduinoQueue.h"
#include <Pump.h>
#include "Pump.h"
#include <ButtonEvents.h>
#include <Bounce2.h>
#include "EasyNextionLibrary.h" // Include EasyNextionLibrary
Expand Down Expand Up @@ -462,21 +461,27 @@ void setup()

//Ethernet client check loop
SoftTimer.add(&t1);
t1.init();

//Orp regulation loop
SoftTimer.add(&t2);
t2.init();

//PH regulation loop
SoftTimer.add(&t3);
t3.init();

//Publish loop
SoftTimer.add(&t4);
t4.init();

//Generic loop
SoftTimer.add(&t5);
t5.init();

//Button loop
SoftTimer.add(&t6);
t6.init();

//display remaining RAM space. For debug
Serial << F("[memCheck]: ") << freeRam() << F("b") << _endl;
Expand Down
179 changes: 179 additions & 0 deletions source/PoolMaster/Pump.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#include "Arduino.h"
#include "Pump.h"

//Constructor
//PumpPin is the Arduino relay output pin number to be switched to start/stop the pump
//TankLevelPin is the Arduino digital input pin number connected to the tank level switch
//Interlockpin is the Arduino digital input number connected to an "interlock".
//If this input is LOW, pump is stopped and/or cannot start. This is used for instance to stop
//the Orp or pH pumps in case filtration pump is not running
//IsRunningSensorPin is the pin which is checked to know whether the pump is running or not.
//It can be the same pin as "PumpPin" in case there is no sensor on the pump (pressure, current, etc) which is not as robust.
//This option is especially useful in the case where the filtration pump is not managed by the Arduino.
//FlowRate is the flow rate of the pump in Liters/Hour, typically 1.5 or 3.0 L/hour for peristaltic pumps for pools. This is used to compute how much of the tank we have emptied out
//TankVolume is used here to compute the percentage fill used
Pump::Pump(uint8_t PumpPin, uint8_t IsRunningSensorPin, uint8_t TankLevelPin,
uint8_t Interlockpin, double FlowRate, double TankVolume, double TankFill)
{
pumppin = PumpPin;
isrunningsensorpin = IsRunningSensorPin;
tanklevelpin = TankLevelPin;
interlockpin = Interlockpin;
flowrate = FlowRate; //in Liters per hour
tankvolume = TankVolume; //in Liters
tankfill = TankFill; // in percent
StartTime = 0;
LastStartTime = 0;
StopTime = 0;
UpTime = 0;
UpTimeError = 0;
MaxUpTime = DefaultMaxUpTime;
CurrMaxUpTime = MaxUpTime;
}

//Call this in the main loop, for every loop, as often as possible
void Pump::loop()
{
if(digitalRead(isrunningsensorpin) == PUMP_ON)
{
UpTime += millis() - StartTime;
StartTime = millis();
}

if((CurrMaxUpTime > 0) && (UpTime >= CurrMaxUpTime))
{
Stop();
UpTimeError = true;
}

if(!this->Pump::TankLevel()) this->Pump::Stop();

if(interlockpin != NO_INTERLOCK)
{
if(digitalRead(interlockpin) == INTERLOCK_NOK)
Stop();
}
}

//Switch pump ON if over time was not reached, tank is not empty and interlock is OK
bool Pump::Start()
{
if((digitalRead(isrunningsensorpin) == PUMP_OFF)
&& !UpTimeError
&& this->Pump::TankLevel()
&& ((interlockpin == NO_INTERLOCK) || (digitalRead(interlockpin) == INTERLOCK_OK))) //if((digitalRead(pumppin) == false))
{
digitalWrite(pumppin, PUMP_ON);
StartTime = LastStartTime = millis();
return true;
}
else return false;
}

//Switch pump OFF
bool Pump::Stop()
{
if(digitalRead(isrunningsensorpin) == PUMP_ON)
{
digitalWrite(pumppin, PUMP_OFF);
UpTime += millis() - StartTime;
return true;
}
else return false;
}

//Reset the tracking of running time
//This is typically called every day at midnight
void Pump::ResetUpTime()
{
StartTime = 0;
StopTime = 0;
UpTime = 0;
CurrMaxUpTime = MaxUpTime;
}

//Set a maximum running time (in millisecs) per day (in case ResetUpTime() is called once per day)
//Once reached, pump is stopped and "UpTimeError" error flag is raised
//Set "Max" to 0 to disable limit
void Pump::SetMaxUpTime(unsigned long Max)
{
MaxUpTime = Max;
CurrMaxUpTime = MaxUpTime;
}

//Clear "UpTimeError" error flag and allow the pump to run for an extra MaxUpTime
void Pump::ClearErrors()
{
if(UpTimeError)
{
CurrMaxUpTime += MaxUpTime;
UpTimeError = false;
}
}

//tank level status (true = full, false = empty)
bool Pump::TankLevel()
{
if(tanklevelpin == NO_TANK)
{
return true;
}
else if (tanklevelpin == NO_LEVEL)
{
return (this->Pump::GetTankFill() > 5.); //alert below 5%
}
else
{
return (digitalRead(tanklevelpin) == TANK_FULL);
}
}

//Return the percentage used since last reset of UpTime
double Pump::GetTankUsage()
{
float PercentageUsed = -1.0;
if((tankvolume != 0.0) && (flowrate !=0.0))
{
double MinutesOfUpTime = (double)UpTime/1000.0/60.0;
double Consumption = flowrate/60.0*MinutesOfUpTime;
PercentageUsed = Consumption/tankvolume*100.0;
}
return (PercentageUsed);
}

//Return the remaining quantity in tank in %. When resetting UpTime, SetTankFill must be called accordingly
double Pump::GetTankFill()
{
return (tankfill - this->Pump::GetTankUsage());
}

//Set Tank volume
//Typically call this function when changing tank and set it to the full volume
void Pump::SetTankVolume(double Volume)
{
tankvolume = Volume;
}

//Set flow rate of the pump in Liters/hour
void Pump::SetFlowRate(double FlowRate)
{
flowrate = FlowRate;
}

//Set tank fill (percentage of tank volume)
void Pump::SetTankFill(double TankFill)
{
tankfill = TankFill;
}

//interlock status
bool Pump::Interlock()
{
return (digitalRead(interlockpin) == INTERLOCK_OK);
}

//pump status
bool Pump::IsRunning()
{
return (digitalRead(isrunningsensorpin) == PUMP_ON);
}
66 changes: 66 additions & 0 deletions source/PoolMaster/Pump.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Pump - a simple library to handle home-pool filtration and peristaltic pumps
(c) Loic74 <[email protected]> 2017-2020
Features:
- keeps track of running time
- keeps track of Tank Levels
- set max running time limit
NB: all timings are in milliseconds
*/

#ifndef PUMP_h
#define PUMP_h
#define PUMP_VERSION "1.0.1"

//Constants used in some of the functions below
#define PUMP_ON 0
#define PUMP_OFF 1
#define TANK_FULL 1
#define TANK_EMPTY 0
#define INTERLOCK_OK 0
#define INTERLOCK_NOK 1
#define NO_LEVEL 170 // Pump with tank but without level switch
#define NO_TANK 255 // Pump without tank
#define NO_INTERLOCK 255

#define DefaultMaxUpTime 30*60*1000 //default value is 30mins

class Pump{
public:

Pump(uint8_t, uint8_t, uint8_t = NO_TANK, uint8_t = NO_INTERLOCK, double = 0., double = 0., double =100.);
void loop();
bool Start();
bool Stop();
bool IsRunning();
bool TankLevel();
double GetTankUsage();
void SetTankVolume(double Volume);
void SetFlowRate(double FlowRate);
bool Interlock();
void SetMaxUpTime(unsigned long Max);
void ResetUpTime();
void SetTankFill(double);
double GetTankFill();

void ClearErrors();

unsigned long UpTime;
unsigned long MaxUpTime;
unsigned long CurrMaxUpTime;
bool UpTimeError;
unsigned long StartTime;
unsigned long LastStartTime;
unsigned long StopTime;
double flowrate, tankvolume, tankfill;
private:

uint8_t pumppin;
uint8_t isrunningsensorpin;
uint8_t tanklevelpin;
uint8_t interlockpin;

};
#endif

0 comments on commit bc79310

Please sign in to comment.