Automatické řízení bazénu

Automatické řízení bazénu pomocí Arduino vzniklo postupem času, kdy jsem začínal pouze s automatickým dávkováním chloru (chlornan sodný) pomocí programovatelného relé z Číny.

Nedávno jsem narazil na „výukovou“ sadu Arduino a řekl si, že by tam šel automatizovat celý bazén. Tak jsem si na zkoušku objednal pár komponent a začal zkoušet.

Plán byl takový:

1/ Automatický provoz -  měl by mít za úkol ranní a večerní filtrování bazénu, přičemž přitom přidá požadovanou dávku chloru. Mimo tyto časové úseky bude hlídat teplotu bazénu a solárního ohřevu a dle podmínek bude ohřívat vodu v bazénu.

2/ Ruční provoz – po zmáčknutí tlačítka, přeruší automatický provoz a zapne pouze čerpadlo filtru. Tato funkce je zejména pro čištění bazénu, nebo pro promíchání přidaných chemikálií.

3/ Ruční dávka chloru – tato funkce je pro přidání chloru mimo automatický režim a je využívána víceméně po čištění bazénu, kdy se dopouští nová voda a je nutné vyrovnat hladinu chloru

4/ vypnutí – toto tlačítko vypne veškeré běžící funkce do doby, než se některá opět ručně spustí.

Vedlejší funkce:

Na LCD displeji se bude zobrazovat aktuální čas, teplota vody bazénu, aktuální zapnutá funkce a teplota solárního panelu (pouze při automatu).

Použité vybavení:

1/ Arduino NANO
2/ DS3231
3/ LCD 16x2 s I2C
4/ relé modul se 4 relé (stačily by tři)
5/ motorový kulový ventil 1“
6/ teplotní dvě teplotní čidla DS18B20
7/ peristaltické čerpadlo
8/ nezbytné součástky jako jsou 4 tlačítka, 4 LED, 5 rezistorů

Celé zapojení je velmi jednoduché. Tlačítka jsou pomocí funkce pinMode nastaveny jako INPUT_PULLUP na pinech 9,10,11,12,. Výstupy jsou pak na pinech A0-A3 a 8 (power LED). Obě tepelné čidla sdílí přes rezistor pin číslo 2. A nakonec LCD a RCT sdílí piny A4 a A5 (SCL,SDA). Na výstupech A0-A3 je zapojen relé modul a zároveň stavové LED, které dle aktuální činnosti svítí nebo nesvítí. Ty pak spínají hlavní filtrační čerpadlo, čerpadlo chloru a otevírají nebo zavírají ventil solárního ohřevu.

Rezistory k LED jsou 330Ω, k teplotním čidlům je pak rezistor 4k7Ω. LCD 1602 a RTC (DS3231) jsou připojeny pomocí SLC a SDA pinů (LCD na schématu nemá I2C řadič).

Program:

#include "Wire.h"
#include <OneWire.h>
OneWire oneWire(2);

// ----------- Display ------------
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3F,16,2);
// ----------- Display end ------------


// -------- Nastavení čidel -------------
#include <DallasTemperature.h>
DallasTemperature sensors(&oneWire);
DeviceAddress BAZEN = { 0x28, 0xFF, 0x99, 0x70, 0x01, 0x17, 0x03, 0x6F };
DeviceAddress PANEL = { 0x28, 0xFF, 0xF1, 0x88, 0x01, 0x17, 0x03, 0x2D };
// -------- Nastavení čidel konec-------------

// ------- Nastavení vstupů a výstupů--------
int releFiltr = A0; // hlavní čerpadlo
int releChlor = A1; // čerpadlo chloru
int releVentil = A2; // ventil ohřevu
int ledOn = A3; // Zelená (dvoubarevná LED)
int ledOff = 8; // červená (dvoubarevná LED)

int buttonOn = 12; // zapnutí automatu
int buttonOff = 11; // vypnutí všeho
int buttonFiltr = 10; // zapnutí filtrace
int buttonChlor = 9; // Dávka chloru

// nastavení proměnných pro teplotu a funkce
float Tbazen;
float Tpanel;
boolean filtraceAuto, cisteni, chlor;

// nastavení časování
long previousMillis = 0;
long previousMillisTemp = 0;
long previousMillisTempP = 0;
long interval = 1000; // čas snímání teploty (ms)
long previousButton = 0;
long intervalButton = 50; // čas omezení prokliku tlačítka

// ------------ Nastavení hodin ------------
#define DS3231_I2C_ADDRESS 0x68
byte decToBcd(byte val){
  return( (val/10*16) + (val%10) );
}
byte bcdToDec(byte val){
  return( (val/16*10) + (val%16) );
}
// ------------ Nastavení hodin konec------------

void setup(){
  Wire.begin();
  Serial.begin(9600);

  // -------- nastavení pinů ------------
  pinMode(releFiltr, OUTPUT); 
  pinMode(releChlor, OUTPUT); 
  pinMode(releVentil, OUTPUT);
  pinMode(ledOn, OUTPUT);
  pinMode(ledOff, OUTPUT);

  pinMode(buttonOn, INPUT_PULLUP); 
  pinMode(buttonOff, INPUT_PULLUP); 
  pinMode(buttonFiltr, INPUT_PULLUP); 
  pinMode(buttonChlor, INPUT_PULLUP);
  
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0,0);
  lcd.print("Automat. bazen");
  lcd.setCursor(0,1);
  lcd.print("Dušan Vala");
  delay(2000);
  lcd.setCursor(0,1);
  lcd.print("                ");
  lcd.setCursor(0,1);
  lcd.print("Vypnuto");
  digitalWrite(ledOff, HIGH);
   
  // ---------- POUZE PRO NASTAVENÍ ČASU! ---------------------
  // sekundy, minuty, hodiny, den v týdnu (1- neděle), datum, měsíc, rok
  //setDS3231time(0,8,19,7,16,12,17);
  // ---------- POUZE PRO NASTAVENÍ ČASU! KONEC ---------------

  
//nastavení rozlišení snímání čidel (9 - 0,5°C 10 - 0,25°C..)
sensors.begin();
sensors.setResolution(9);

}
// ---------- POUZE PRO NASTAVENÍ ČASU! ---------------------
/*void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte
dayOfMonth, byte month, byte year){
  // sets time and date data to DS3231
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set next input to start at the seconds register
  Wire.write(decToBcd(second)); // set seconds
  Wire.write(decToBcd(minute)); // set minutes
  Wire.write(decToBcd(hour)); // set hours
  Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
  Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
  Wire.write(decToBcd(month)); // set month
  Wire.write(decToBcd(year)); // set year (0 to 99)
  Wire.endTransmission();
}*/

void readDS3231time(byte *second, byte *minute, byte *hour){
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0);
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
}
void displayTime(){
  // ------- Hodiny ---------
  byte second, minute, hour;
  readDS3231time(&second, &minute, &hour); 
  lcd.setCursor(0,0);
  lcd.print(hour, DEC);
  lcd.print(":");
  if (minute<10){
    lcd.print("0");
  }
  lcd.print(minute, DEC);
  lcd.print(":");
  if (second<10){
  lcd.print("0");
  }
  lcd.print(second, DEC);  
  //--------- Hodiny konec ---------- 
}

//-------------------- LOOP --------------
void loop(){
  // ------------- ZAPNUTÍ AUTOMATIKY --------
  if (digitalRead(buttonOn) == LOW) {
      unsigned long currentMillis = millis();
        if(currentMillis - previousButton > intervalButton) {
              previousButton = currentMillis; 
              sensors.requestTemperatures();
              off();
              digitalWrite(ledOn, HIGH);
              digitalWrite(ledOff, LOW);
              lcd.setCursor(0,1);
              lcd.print("                ");
              lcd.setCursor(0,1);
              lcd.print("Automat");
              filtraceAuto = true;
        }
  }
  
  // ----------- POUZE FILTRACE / ČÍŠTĚNÍ  ------------
  if (digitalRead(buttonFiltr) == LOW) {
      unsigned long currentMillis = millis(); 
        if(currentMillis - previousButton > intervalButton) {
              previousButton = currentMillis; 
              sensors.requestTemperatures();    
              off();
              digitalWrite(ledOn, HIGH);
              digitalWrite(ledOff, LOW);
              lcd.setCursor(0,1);
              lcd.print("                ");
              lcd.setCursor(0,1);
              lcd.print("Cisteni/Filtrace");
              cisteni = true;
        }
   }
  
   // -------- DÁVKOVÁNÍ CHLORU --------
   if (digitalRead(buttonChlor) == LOW) {
       unsigned long currentMillis = millis();
         if(currentMillis - previousButton > intervalButton) {
               previousButton = currentMillis; 
               sensors.requestTemperatures();     
               off();
               digitalWrite(ledOn, HIGH);
               digitalWrite(ledOff, LOW);
               digitalWrite(releChlor, HIGH);
               lcd.setCursor(0,1);
               lcd.print("                ");
               lcd.setCursor(0,1);
               lcd.print("Davka chloru");
               chlor = true;
        }
   }

// ------------- VYPNUTO -----------
    if (digitalRead(buttonOff) == LOW)  {
        unsigned long currentMillis = millis();  
          if(currentMillis - previousButton > intervalButton) {
                previousButton = currentMillis; 
                sensors.requestTemperatures();
                off();
          }
    }
  
  filtraceChlor(); // načtení funkce denního programu
  filtrace(); // funkce pro filtraci nebo čištění
  displayTime(); // zobrazení hodin
  bazenTemp(); // zobrazení teploty bazénu
    if(filtraceAuto) { // pokud je aktivní automatický režim,
        panelTemp(); // je zobrazena teplota soláního panelu
    }
}
//-------------------- LOOP END ----------------

// ---------- VYPNUTÍ VŠECH FUNKCÍ --------------
void off(){
      digitalWrite(releFiltr, LOW);
      digitalWrite(releChlor, LOW);
      digitalWrite(releVentil, LOW);
      digitalWrite(ledOn, LOW);
      digitalWrite(ledOff, HIGH);
      cisteni = false;
      filtraceAuto = false;
      chlor = false;
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("Vypnuto");
}

// zapnutí pouze filtrace nebo dávky chloru
void filtrace(){
  if(cisteni || (chlor)){
      digitalWrite(releFiltr, HIGH); //zapni čerpadlo
      digitalWrite(releVentil, LOW); //a vypni ventil pokud by byl otevřený
      if(chlor) {
         byte sec, minuty, hod;
         readDS3231time(&sec, &minuty, &hod);
         if((sec >= 5 & (sec < 45))) {
        digitalWrite(releChlor, HIGH);
         } else {
              if(sec == 59) {
                lcd.setCursor(0,1);
                lcd.print("                ");
                lcd.setCursor(0,1);
                lcd.print("Automat");
                digitalWrite(releFiltr, LOW);
                filtraceAuto = true;
                chlor = false;
              }
              digitalWrite(releChlor, LOW);
         }
      }
  } else {
    if(!filtraceAuto){
    digitalWrite(releFiltr, LOW);
    }
  }
}

/* ------------ AUTOMATICKÁ FILTRACE ----------
 *  Ráno a večer zapne filtraci a nadávkuje chlor
 *  Mimo časovou filtraci zapíná denní program ohřevu bazénu
 */
void filtraceChlor(){
 byte sec, minuty, hod;
 readDS3231time(&sec, &minuty, &hod);
  if(filtraceAuto & (!cisteni & (!chlor))) {

  // --------------- VEČER / RÁNO------------
    if((hod >= 21 & (hod < 22) || (hod >= 5 & (hod < 6)))) { // filtrace pojede mezi 21-22hod večer a mezi 5-6hod ráno
      digitalWrite(releFiltr, HIGH); //zapni čerpadlo       
      if((minuty == 0) & (sec >=5) & (sec <=45)) { // u každé filtrace bude přidán chlor po dobu 40s
          digitalWrite(releChlor, HIGH);      
        } else {
            digitalWrite(releChlor, LOW);
            }
      
      } else {
        teplota(); // mimo daný čas zapne denní režim ohřevu bazénu
    }
  }
}

// ------------- DENNÍ PROGRAM OHŘEVU -------------
void teplota(){ 
  unsigned long currentMillis = millis();
  
  if(currentMillis - previousMillis > interval) {
      previousMillis = currentMillis; 
      sensors.requestTemperatures();
      Tbazen = sensors.getTempC(BAZEN); //teplota bazenu
      Tpanel = sensors.getTempC(PANEL); //teplota panelu
      float hystereze = 2; //rozdíl teplot pro zapnutí a vypnutí

      if(Tbazen == -127 || (Tpanel == -127)){ // pokud není čidlo připojeno hlásí teplotu -127
              lcd.setCursor(9, 0);
              lcd.print("Chyba cidla!");
              } else {
                   if(Tbazen <= 24) {//pokud je teplota bazénu menší
                      if((Tpanel - 28) > hystereze) { //a teplota panelu větší
                            digitalWrite(releFiltr, HIGH); // zapni čerpadlo
                            digitalWrite(releVentil, HIGH); // a uzavři ventil
                      } else {
                        if((28 - Tpanel) > hystereze) { // pokud teplota panelu klesne
                          digitalWrite(releFiltr, LOW); // vypni čerpadlo
                        }
                      }
              
                  } else {
                    digitalWrite(releFiltr, LOW); // vypni čerpadlo
                    digitalWrite(releVentil, LOW); // otevři ventil
                  }
                }
  }
}

// ---------- TEPLOTA BAZÉNU ----------- 
void bazenTemp(){
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillisTemp > interval) {
          previousMillisTemp = currentMillis; 
          //načtení teploty z čidla
          sensors.requestTemperatures();
          Tbazen = sensors.getTempC(BAZEN);
          if(Tbazen == -127){
              lcd.setCursor(9, 0);
              lcd.print("Error");
          } else {
               lcd.setCursor(9, 0);
               lcd.print(Tbazen);
               lcd.print((char)223);
               lcd.print("C");
            }
  }
}

// ---------- TEPLOTA SOLÁRNÍHO PANELU ----------- 
void panelTemp(){
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillisTempP > interval) {
          previousMillisTempP = currentMillis; 
          //načtení teploty z čidla
          sensors.requestTemperatures();
          Tpanel = sensors.getTempC(PANEL);
          if(Tpanel == -127){
              lcd.setCursor(9, 1);
              lcd.print("Error");
          } else {
               lcd.setCursor(9, 1);
               lcd.print(Tpanel);
               lcd.print((char)223);
               lcd.print("C");
            }
  }
}

Systém krom dávkování chloru je myslím celkem jednoduchý a jasný. Ta dávka chloru mimo program chce ještě doladit. Takhle vím, že ji mám spustit mezi nultou a pátou sekundou aby prošla celá. Zatím nevím jak líp to udělat. Jsem naprostý začátečník a tohle je můj naprosto první program co jsem zkoušel. Určitě by tam šlo udělat něco líp nebo jinak, ale pro mé potřeby to zatím stačí a myslím, že tím nepohrdne ani někdo další kdo nad něčím podobným přemýšlí. :)

Další vývoj projektu je aktualizován na webu https://github.com/Rellik12/BazenBot

Další podobné články

ROBOTICKÉ RAMENO

Stavebnice obsahuje všechny potřebné díly na sestavení robotnického ramene včetně spojovacího materiálu, pouze je nutné dokoupit čtyři kusy MIKRO SERV SG90. Dále je nutné dokoupit řídící jednotku já jsem použil domácí zásoby ARDUINO NANO a pro něho pak modul ARDUINO NANO IO SHIELD pro jednoduchost zapojení. Díly pro sestavení ramene jdou dobře tzv. vylamovat „vypadávají skoro sami. K servům pokud použijete nové tak doporučuji je před montáží odzkoušet zda jsou funkční v plném rozsahu tj. od 0° do 180°, po namontování a zjištění že servo nefunguje to pak opravdu dost zahýbá s nervy. 

Electronic TiltMaze

Cílem tohoto projektu je vytvoření jednoduchého ovládacího systému, který umožňuje naklápění dvou servomotorů pomocí analogového joysticku. Platforma řízená servomotory může simulovat pohyb například v ose X a Y — tedy naklánění doleva/doprava a dopředu/dozadu. Tento systém může sloužit jako základ pro různé aplikace:

- Manuální ovládání kamery nebo senzoru (např. na pohyblivé konstrukci nebo robotovi)
- Interaktivní ovládací panel pro školní projekty nebo herní ovladač