Programowanie Arduino, czyli nauka podlewania

Mamy już gotową część sprzętową systemu nawadniania i wiemy jak będzie się komunikować z systemem narzędnym działającym na bazie OpenWRT. Pora zabrać się za oprogramowanie. Przygotowując je należy pamiętać, że posłuży ono za podstawę budowy kolejnych elementów inteligentranego domu (lub jak kto woli, automatyki domowej). Poza tym system podlewania także ma dodatkowe funkcje, co trzeba przewidzieć.

Podsumujmy, co jest do zrobienia:

  1. Obsługa Ethernet Shield
  2. Klient www na potrzeby zgłaszania zdarzeń do systemu nadrzędnego (mastera)
  3. Obsługa serwera www – do przyjmowania komunikatów
  4. Sterowanie czterema przekaźnikami (dwa do podlewania, dwa na razie wolne)
  5. Pilnowanie zmian czterech wejść (dwa kontaktrony na drzwiach i dwa czujniki zalania)
  6. Realizacja różnych faz podlewania ogrodu

Zabawę rozpoczynamy więc od sieci. Trzeba zaimportować pliki nagłówkowe biblioteki SPI i Ethernet, skonfigurować adresy IP (naszego urządzenia, mastera i węzła zapasowego). Utworzymy też zmienne używane przez klienta i serwer i zaincjujemy serwer. Przy okazji nadamy naszemu urządzeniu identyfikator zgodnie z koncepcją komunikacji:

#include <SPI.h>
#include <Ethernet.h>

// konfiguracja
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,15,220);
byte gateway[] = { 192, 168, 15, 1 };
byte subnet[] = { 255, 255, 255, 0 };
#define ID 'a' // identyfikator urządzenia

IPAddress master(192,168,15,1);
IPAddress backup(192,168,15,2);

EthernetServer server(80);
EthernetClient aclient; // arduino jako klient

void setup()
{
  // konfiguracja ip i start serwera
  Ethernet.begin(mac, ip, gateway, subnet);
  server.begin();
}

Mamy już solidne podstawy do przygotowania komunikacji. Zajmijmy się w następnym kroku funkcjami do wysyłania komunikatów (wg naszego protokołu) do systemu nadrzędnego. Dla przypomnienia – będziemy łączyć się przez protokół HTTP, więc wyślemy specyficzne dla niego nagłówki. Trzeba także pamiętać, że w przypadku niedostępności głównego adresu, będziemy przesyłać informację do węzła zapasowego.

boolean sendMessageTo(char cmd, String param, IPAddress dest)
{
  if (aclient.connect(dest, 80)) 
  {
    aclient.print("GET /cmd.php?c=");
    aclient.print(ID);    
    aclient.print(cmd);
    aclient.print(param);
    aclient.print(" HTTP/1.0");
    aclient.println();
    aclient.println();    
    aclient.stop();
    return(true);
  } 
  else 
  {
    return(false);
  }  
}

int sendMessage(char cmd, String param)
{
  // wysyłamy komunikat na główny adres, 
  // a jak się nie uda, to na zapasowy
  if(!sendMessageTo(cmd, param, master)) 
  {
    sendMessageTo(cmd, param, backup);
  }
}

Funkcje do wysyłania są gotowe, więc pora zająć się obsługą połączeń przez serwer. W głównej pętli zajmiemy się przyjęciem połączenia, jeżeli oczekują jakieś dane.

void loop()
{
  // przypisujemy klienta jeżli czeka
  EthernetClient actclient = server.available();

  if( actclient ) getClientData(actclient); // jakiś czeka i ma dane do odczytania
}

Należałoby teraz chwilę posnuć rozważania na temat bezpieczeństwa. Arduino wykonuje komendy sekwencyjnie (co nie przeszkadza wykonywać wielu zadań niemalże w tym samym czasie). Jeżeli natomiast wywołujemy funkcję odbierającą dane z sieci, warto pomyśleć nad jakimś timeoutem, czyli czasem, po którym stwierdzimy, że trzeba odrzucić klienta i wrócić do głównej pętli. Jaki czas będzie sensowny, żeby nie przegapić otwarcia drzwi? Sekunda chyba będzie dobra. Nie da się w takim czasie otworzyć drzwi, wejść i zamknąć drzwi, nawet mając pełną synchronizację, nijak. Czy sekunda jest wystarczająca z punktu widzenia czasu trwania operacji? W zupełności. Jedno odwołanie trwa około 30 milisekund i przy próbach wstrzymania przez zawieszenie sesji http nie udało mi się go przedłużyć. Można więc uznać, że to bezpieczne. Przyjmijmy więc komunikat od klienta.

// żeby klient nie przytrzymał za długo programu - taki czas akceptujemy
const int servertimeout=1000;

// pobiera zapytanie od mastera, wyrzuca na timeout
// interesuje nas tylko pierwsza linia, a tam naprawdę 
// drugi wyraz poza pierwszym znakiem
boolean getClientData(EthernetClient client) // odbieramy zapytanie od mastera
{
  unsigned long timeout=millis()+servertimeout;
  unsigned long time=millis();

  boolean cmdBegin=false;
  boolean cmdEnd=false;
  boolean cmdToRun=false;
  boolean headersSent=false;
  String cmd("");

   while (client.connected()) 
   {
      // przy timeoucie wyrzucamy klienta
      if(timeout<millis())
      {
        client.stop();
        return(false);
      }

      if (client.available()) 
      {
        char c = client.read();

        // dopisujemy znaki do komendy (między pierwszym / a spacją)
        if( (c==' ' || c==',') && cmdBegin ) 
        {
          if(c==' ') cmdEnd=true;
          cmdToRun=true;
          if(!headersSent)
          {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/plain");
            client.println("Connection: close");
            client.println();          
            headersSent=true;
          }
        }
        if( cmdBegin && !cmdEnd )
        {
          cmd+=c;
        }        
        if( c=='/') cmdBegin=true;

        if( cmdToRun )
        {
          client.println( runCommand(cmd) );
          cmd="";
          cmdToRun=false;
        }

        if (c == '\n')
        {
          break;
        }
      }
   }
   delay(1);
   client.stop();
   return(true);
}

Krótkie wyjaśnienie powyższego kodu, którego działanie pewnie nie jest bardzo oczywiste. W pierwszej linii zapytania szukamy adresu wywoływanej strony, który jest w drugim wyrazie (czyli po spacji). Następnie pomijamy znak „/”, po którym następują komunikaty rozdzielone przecinkami. Potrafimy je przyjąć, trzeba jeszcze je zinterpretować i coś odpowiedzieć (w kolejnych liniach), do czego służy funkcja runCommand, która w podstawowej wersji może wyglądać jak poniżej.

String runCommand(String cmdstring)
{
  int w;
  String ret="";

  // nie ma co sprawdzać gdy długość < 3
  if( cmdstring.length()<3 ) return("Error");

  // podział na kod urządzenia, komendę i jej parametry
  char dest=cmdstring.charAt(0);
  char cmd=cmdstring.charAt(1);
  String param=cmdstring.substring(2);

  // nie do mnie
  if( dest!=ID ) return("Error");

  switch(cmd)
  {
    case 'x': // jakaśtam hipotetyczna, nieprzydatna funkcja
      return("zwracam to");
      break;

    default:
      return("Error");
  }
}

Część komunikacyjną mamy gotową. Potrafimy odebrać komunikat np, „ax0” i odpowiedzieć na niego tekstem „zwracam to”. Pomyślmy więc jak obsłużyć wejścia i wyjścia, mając na względzie, że powinno być to możliwie czytelne i łatwo konfigurowalne (zakładając konfigurację na poziomie kodu źródłowego). Prowadzimy więc tablice zawierające numery wejść i wyjść. Do indeksów tych tablic będziemy się odwoływać operując na wejściach i wyjściach. Do wejść podłączone będą konaktrony i czujniki zalania. Do wyjść – przekaźniki.

// tablica stanów wejść
int input[]={LOW, LOW, LOW, LOW};
int input_pins[]={5,6,7,8};
#define INPUT_SIZE 4 // liczba elementów w tablicy

// tablica (domyślnych) stanów wyjść - stan wysoki wyłącza przekaźnik
int output[]={HIGH, HIGH, HIGH, HIGH};
int output_pins[]={0,1,2,3};
#define OUTPUT_SIZE 4

Wyjściom przypisujemy stany wysokie, bo oznacza to wyłączony przekaźnik, zgodnie z tym co pisałem o zastosowanych modułach przekaźnikowych. Wejść trzeba pilnować. Jeżeli zmieni się stan któregoś, to należy wysłać powiadomienie. Zakładając, że w tablicy inputs mamy spodziewane stany wejść, a input_pins definiują, gdzie ich szukać w Arduino, musimy dodać jedną funkcję do głównej pętli (loop). Jeżeli zmieni się stan, to wyślemy komunikat „i” z numerem wejścia (numerując od 0) i jego aktualnym stanem.

// sprawdza stany wejść, jeżeli nastąpiła zmiana, to powiadamia
void checkInputs()
{
  int inp, w;

  for(w=0; w<INPUT_SIZE; w++ )
  {
    inp=digitalRead(input_pins[w]);
    if(inp!=input[w])
    {
      input[w]=inp;
      sendMessage('i', String(String(w)+inp) );
    }
  }
}

Można też dopisać obsługę pierwszego prawdziwego komunikatu – zapytania o stan wejść. Do funkcji runCommand w sekcji switch dopiszemy:

    case 'i': // pytanie o stan wejść
      for(w=0; w<INPUT_SIZE; w++)
        if( input[w]==HIGH ) ret+="1"; else ret+="0";
      return(ret);
      break;

W odpowiedzi na komunikat wysyłamy ciąg zer i jedynek odpowiadających stanowi kolejnych wejść (tak naprawdę zawartość tablicy inputs). Analogicznie tworzy się zapytanie o stan wyjść (komenda „o”). Dla pełnego obrazu dołóżmy jesszcze komendy „l” i „h” zmieniające stan wyjścia na niski i wysoki.

    case 'o': // pytanie o stan wyjść
      for(w=0; w<OUTPUT_SIZE; w++ )
        if( output[w]==HIGH ) { ret+="1"; } else ret+="0";
      return(ret);
      break;

    case 'h': // ustawienie stanu wyjścia na wysoki
      w=param.toInt();
      if( w<0 || w>OUTPUT_SIZE ) return("Error");
      output[w]=HIGH;
      digitalWrite(output_pins[w], HIGH);
      return("1");
      break;

    case 'l': // ustawienie stanu wyjścia na niski
      w=param.toInt();
      if( w<0 || w>OUTPUT_SIZE ) return("Error");
      output[w]=LOW;
      digitalWrite(output_pins[w], LOW);
      return("0");
      break;

Czego jeszcze nie ma? Fragmentu funkcji setup, inicjującej wejścia i wjścia samego Arduino.

  // konfigurujemy wyjścia i ustalamy stany
  for(w=0; w<OUTPUT_SIZE; w++ )
  {
    pinMode(output_pins[w], OUTPUT);
    digitalWrite(output_pins[w], output[w]);
  }

  // konfigurujemy wejścia (z wewnętrzym rezystorem pull-up)
  // i ustalamy aktualny stan
  for(w=0; w<INPUT_SIZE; w++ )
  {
//    pinMode(input_pins[w], INPUT_PULLUP);
    pinMode(input_pins[w], INPUT);
    digitalWrite(input_pins[w], HIGH); // ustawia pull-up
    input[w]=digitalRead(input_pins[w]);
  }

Mamy więc gotowe operacje na wyjściach i wejściach. Obsługa wyjść jest trochę „na wyrost”, jako, że na razie nie będziemy zmieniać ich stanów. Zajmować się będzie tym wyłącznie część odpowiedzialna za planowanie podlewania. Pora przejść do tego zagadnienia. Zdefiniujemy tablicę zawierającą numery wyjść (indeksów z tabeli inputs), biorących udział w procesie. Wprowadzimy też zmienne określające na jak długo należy zaplanować poszczególne fazy nawadniania i jakie powinny być momenty ich przełączenia wg wewnętrznego zegara Arduino. Przyda się też aktualny i ostatni stan procesu.

// wyjścia do sterownia podlewaniem (piny z tablicy output_pins)
// (pierwszy załączanie zasilania, drugi przełączanie elktrozaworu)
int watering_outputs[]={3,1};
#define WATERING_SIZE 2

// zmienne globalne
unsigned long wateringtime1=0; // czas nawadniania trawnika w sekundach
unsigned long wateringtime2=0; // czas nawadniania pozostałych w sekundach
unsigned long time;

// na potrzeby liczników kiedy zacząć i kiedy skończyć
unsigned long watering1start=0;
unsigned long watering2start=0;
unsigned long watering2stop=0;
int wateringState=3; // 0 - jeszcz nie podlewamy, ale zaplanowane, 1 - trawnik, 2 - reszta, 3 - już po
int lastWateringState=3;

Teraz dodamy obługę komunikatów przypisujących zmiennym czasy faz nawadniania (switch w funkcji runCommand) oraz startujących proces.

    case 't': // ustawienie czasu nawadniania trawnika
      wateringtime1=param.toInt();
      return(String(wateringtime1));
      break;

    case 'r': // ustawienie czasu nawadniania pozostałych
      wateringtime2=param.toInt();
      return(String(wateringtime2));
      break;

    case 's': // rozpoczęcie nawadniania wg zadanych czasów
      startWatering();
      return("OK");
      break;

Sama obsługa procesu znajdzie się w funkcji checkWatering, wywoływanej w głównej pętli. Razem ze startWatering i zmodyfikowaną główną pętlą, dochodzi nam trochę linii kodu.

void startWatering()
{
  time=millis();

  watering1start=time+1000;
  watering2start=watering1start+wateringtime1*1000;
  watering2stop=watering2start+wateringtime2*1000;  

  // jeżeli podczas podlewania miałby przekręcić się licznik czasu
  // to trochę przeniesiemy podlewanie
  if( watering2stop<watering1start )
  {
    watering1start+=wateringtime1*1000+wateringtime2*1000;
    watering2start+=wateringtime1*1000+wateringtime2*1000;
    watering2stop+=wateringtime1*1000+wateringtime2*1000;
  }

  wateringState=0;
}

void checkWatering()
{
  // jeżeli już po podlewaniu to nie tracimy czasu na obliczenia
  if( wateringState==3 ) return;

  time=millis();

  if( time<watering1start || (time>(unsigned long)20*24*60*60*1000 && watering1start<(unsigned long)24*60*60*1000) ) wateringState=0;
  else if( time >= watering1start && time < watering2start ) wateringState=1;
  else if( time >= watering2start && time < watering2stop ) wateringState=2;
  else wateringState=3;

  // powiadomienie o rozpoczęciu/zakończeniu nawadniania
  if( lastWateringState!=wateringState && wateringState!=0 ) sendMessage('w', String(wateringState) );
  lastWateringState=wateringState;

  switch(wateringState)
  {
    case 0:
      output[output_pins[watering_outputs[0]]]=HIGH;
      digitalWrite(output_pins[watering_outputs[0]], HIGH);
      return;
      break;    

    case 1:
      output[watering_outputs[0]]=LOW;
      output[watering_outputs[1]]=LOW;
      digitalWrite(output_pins[watering_outputs[1]], LOW);
      digitalWrite(output_pins[watering_outputs[0]], LOW);
      return;
      break;

    case 2:
      output[watering_outputs[0]]=LOW;
      output[watering_outputs[1]]=HIGH;
      digitalWrite(output_pins[watering_outputs[1]], HIGH);
      digitalWrite(output_pins[watering_outputs[0]], LOW);    
      return;
      break;

    case 3:
      output[watering_outputs[0]]=HIGH;
      digitalWrite(output_pins[watering_outputs[0]], HIGH);
      output[watering_outputs[1]]=HIGH;
      digitalWrite(output_pins[watering_outputs[1]], HIGH);
      watering1start=0;
      watering2start=0;
      watering2stop=0;
      return;
      break;    
  }
}

void loop()
{
  // przypisujemy klienta jeżli czeka
  EthernetClient actclient = server.available();

  checkInputs(); // sprawdzamy stany wejść w poszukiwaniu zmian

  checkWatering(); // sprawdzamy czy nie trzeba zacząć lub przerwać podlewania

  if( actclient ) getClientData(actclient); // jakiś czeka i ma dane do odczytania

}

Po drobnych dodatkach (jak wysyłanie informacji o resecie urządzenia) zbieramy cały kod razem:

#include <SPI.h>
#include <Ethernet.h>

// konfiguracja
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,15,220);
byte gateway[] = { 192, 168, 15, 1 };
byte subnet[] = { 255, 255, 255, 0 };
#define ID 'a' // identyfikator urządzenia

IPAddress master(192,168,15,1);
IPAddress backup(192,168,15,2);

// żeby klient nie przytrzymał za długo programu - taki czas akceptujemy
const int servertimeout=1000;

// tablica stanów wejść
int input[]={LOW, LOW, LOW, LOW};
int input_pins[]={5,6,7,8};
#define INPUT_SIZE 4 // liczba elementów w tablicy

// tablica (domyślnych) stanów wyjść - stan wysoki wyłącza przekaźnik
int output[]={HIGH, HIGH, HIGH, HIGH};
int output_pins[]={0,1,2,3};
#define OUTPUT_SIZE 4

// wyjścia do sterownia podlewaniem (piny z tablicy output_pins)
// (pierwszy załączanie zasilania, drugi przełączanie zaworu)
int watering_outputs[]={3,1};
#define WATERING_SIZE 2

// zmienne globalne
unsigned long wateringtime1=0; // czas nawadniania trawnika w sekundach
unsigned long wateringtime2=0; // czas nawadniania pozostałych w sekundach
unsigned long time;

// na potrzeby liczników kiedy zacząć i kiedy skończyć
unsigned long watering1start=0;
unsigned long watering2start=0;
unsigned long watering2stop=0;
int wateringState=3; // 0 - jeszcz nie podlewamy, ale zaplanowane, 1 - trawnik, 2 - reszta, 3 - już po
int lastWateringState=3;

EthernetServer server(80);
EthernetClient aclient; // arduino jako klient

//uruchamia komenda i zwraca czy się udało
String runCommand(String cmdstring)
{
  int w;
  String ret="";

  // nie ma co sprawdzać gdy długość < 3
  if( cmdstring.length()<3 ) return("Error");

  // podział na kod urządzenia, komendę i jej parametry
  char dest=cmdstring.charAt(0);
  char cmd=cmdstring.charAt(1);
  String param=cmdstring.substring(2);

  // nie do mnie
  if( dest!=ID ) return("Error");

  switch(cmd)
  {
    case 'i': // pytanie o stan wejść
      for(w=0; w<INPUT_SIZE; w++)
        if( input[w]==HIGH ) ret+="1"; else ret+="0";
      return(ret);
      break;

    case 'o': // pytanie o stan wyjść
      for(w=0; w<OUTPUT_SIZE; w++ )
        if( output[w]==HIGH ) { ret+="1"; } else ret+="0";
      return(ret);
      break;

    case 't': // ustawienie czasu nawadniania trawnika
      wateringtime1=param.toInt();
      return(String(wateringtime1));
      break;

    case 'r': // ustawienie czasu nawadniania pozostałych
      wateringtime2=param.toInt();
      return(String(wateringtime2));
      break;

    case 's': // rozpoczęcie nawadniania wg zadanych czasów
      startWatering();
      return("OK");
      break;

    case 'q': // sprawdza stan nawadniania
      return(String(wateringState)+";"+String(time)+";"+watering1start+";"+watering2start+";"+watering2stop);
      break;

    case 'h': // ustawienie stanu wyjścia na wysoki
      w=param.toInt();
      if( w<0 || w>OUTPUT_SIZE ) return("Error");
      output[w]=HIGH;
      digitalWrite(output_pins[w], HIGH);
      return("1");
      break;

    case 'l': // ustawienie stanu wyjścia na niski
      w=param.toInt();
      if( w<0 || w>OUTPUT_SIZE ) return("Error");
      output[w]=LOW;
      digitalWrite(output_pins[w], LOW);
      return("0");
      break;

    default:
      return("Error");
  }
}

boolean sendMessageTo(char cmd, String param, IPAddress dest)
{
  if (aclient.connect(dest, 80)) 
  {
    aclient.print("GET /cmd.php?c=");
    aclient.print(ID);    
    aclient.print(cmd);
    aclient.print(param);
    aclient.print(" HTTP/1.0");
    aclient.println();
    aclient.println();    
    aclient.stop();
    return(true);
  } 
  else 
  {
    return(false);
  }  
}

int sendMessage(char cmd, String param)
{
  // wysyłamy komunikat na główny adres, 
  // a jak się nie uda, to na zapasowy
  if(!sendMessageTo(cmd, param, master)) 
  {
    sendMessageTo(cmd, param, backup);
  }
}

// sprawdza stany wejść, jeżeli nastąpiła zmiana, to powiadamia
void checkInputs()
{
  int inp, w;

  for(w=0; w<INPUT_SIZE; w++ )
  {
    inp=digitalRead(input_pins[w]);
    if(inp!=input[w])
    {
      input[w]=inp;
      sendMessage('i', String(String(w)+inp) );
    }
  }
}

// pobiera zapytanie od mastera, wyrzuca na timeout
// interesuje nas tylko pierwsza linia, a tam naprawdę drugi wyraz poza pierwszym znakiem
boolean getClientData(EthernetClient client) // odbieramy zapytanie od mastera
{
  unsigned long timeout=millis()+servertimeout;
  unsigned long time=millis();

  boolean cmdBegin=false;
  boolean cmdEnd=false;
  boolean cmdToRun=false;
  boolean headersSent=false;
  String cmd("");

   while (client.connected()) 
   {
      // przy timeoucie wyrzucamy klienta
      if(timeout<millis())
      {
        client.stop();
        return(false);
      }

      if (client.available()) 
      {
        char c = client.read();

        // dopisujemy znaki do komendy (między pierwszym / a spacją)
        if( (c==' ' || c==',') && cmdBegin ) 
        {
          if(c==' ') cmdEnd=true;
          cmdToRun=true;
          if(!headersSent)
          {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/plain");
            client.println("Connection: close");
            client.println();          
            headersSent=true;
          }
        }
        if( cmdBegin && !cmdEnd )
        {
          cmd+=c;
        }        
        if( c=='/') cmdBegin=true;

        if( cmdToRun )
        {
          client.println( runCommand(cmd) );
          cmd="";
          cmdToRun=false;
        }

        if (c == '\n')
        {
          break;
        }
      }
   }
   delay(1);
   client.stop();
   return(true);
}

void setup()
{
  int w=0; // licznik

  // konfiguracja ip i start serwera
  Ethernet.begin(mac, ip, gateway, subnet);
  server.begin();

  // konfigurujemy wyjścia i ustalamy stany
  for(w=0; w<OUTPUT_SIZE; w++ )
  {
    pinMode(output_pins[w], OUTPUT);
    digitalWrite(output_pins[w], output[w]);
  }

  // konfigurujemy wejścia (z wewnętrzym rezystorem pull-up)
  // i ustalamy aktualny stan
  for(w=0; w<INPUT_SIZE; w++ )
  {
//    pinMode(input_pins[w], INPUT_PULLUP);
    pinMode(input_pins[w], INPUT);
    digitalWrite(input_pins[w], HIGH); // ustawia pull-up
    input[w]=digitalRead(input_pins[w]);
  }

  delay(5000); // dajmy trochę czasu

  sendMessage('r', String(ID) ); // powiadom serwer o restarcie/włączeniu
}

void startWatering()
{
  time=millis();

  watering1start=time+1000;
  watering2start=watering1start+wateringtime1*1000;
  watering2stop=watering2start+wateringtime2*1000;  

  // jeżeli podczas podlewania miałby przekręcić się licznik czasu
  // to trochę przeniesiemy podlewanie
  if( watering2stop<watering1start )
  {
    watering1start+=wateringtime1*1000+wateringtime2*1000;
    watering2start+=wateringtime1*1000+wateringtime2*1000;
    watering2stop+=wateringtime1*1000+wateringtime2*1000;
  }

  wateringState=0;
}

void checkWatering()
{
  // jeżeli już po podlewaniu to nie tracimy czasu na obliczenia
  if( wateringState==3 ) return;

  time=millis();

  if( time<watering1start || (time>(unsigned long)20*24*60*60*1000 && watering1start<(unsigned long)24*60*60*1000) ) wateringState=0;
  else if( time >= watering1start && time < watering2start ) wateringState=1;
  else if( time >= watering2start && time < watering2stop ) wateringState=2;
  else wateringState=3;

  // powiadomienie o rozpoczęciu/zakończeniu nawadniania
  if( lastWateringState!=wateringState && wateringState!=0 ) sendMessage('w', String(wateringState) );
  lastWateringState=wateringState;

  switch(wateringState)
  {
    case 0:
      output[watering_outputs[0]]=HIGH;
      digitalWrite(output_pins[watering_outputs[0]], HIGH);
      return;
      break;    

    case 1:
      output[watering_outputs[0]]=LOW;
      output[watering_outputs[1]]=LOW;
      digitalWrite(output_pins[watering_outputs[1]], LOW);
      digitalWrite(output_pins[watering_outputs[0]], LOW);
      return;
      break;

    case 2:
      output[watering_outputs[0]]=LOW;
      output[watering_outputs[1]]=HIGH;
      digitalWrite(output_pins[watering_outputs[1]], HIGH);
      digitalWrite(output_pins[watering_outputs[0]], LOW);    
      return;
      break;

    case 3:
      output[watering_outputs[0]]=HIGH;
      digitalWrite(output_pins[watering_outputs[0]], HIGH);
      output[watering_outputs[1]]=HIGH;
      digitalWrite(output_pins[watering_outputs[1]], HIGH);
      watering1start=0;
      watering2start=0;
      watering2stop=0;
      return;
      break;    
  }
}

void loop()
{

  // przypisujemy klienta jeżli czeka
  EthernetClient actclient = server.available();

  checkInputs(); // sprawdzamy stany wejść w poszukiwaniu zmian

  checkWatering(); // sprawdzamy czy nie trzeba zacząć lub przerwać podlewania

  if( actclient ) getClientData(actclient); // jakiś czeka i ma dane do odczytania

}

Uzbierało się trochę linii kodu, mam nadzieję, że jednak dość przejrzystego. Po skompilowaniu, całość zajmuje 16KB, co biorąc pod uwagę, że wchodzą w to biblioteki, mamy spory zapas do rozbudowy, nawet w przypadku słabszego Arduino Uno. Główną częścią inteligentnego domu będzie sterował Arduino Mega 2560. To jednak temat na inny wpis. Do opisania została nam wcześniej część po stronie routera, choćby była na razie w szczątkowej formie i podsumowanie całości.

Ten wpis został opublikowany w kategorii Inteligentny Dom i oznaczony tagami , , . Dodaj zakładkę do bezpośredniego odnośnika.

0 odpowiedzi na Programowanie Arduino, czyli nauka podlewania

  1. Dj.Kot pisze:

    Możesz wyjaśnić dlaczego pomimo wpisania poprawnych parametrów routera niełączy się z routerem ?

  2. Dj.Kot pisze:

    Wiem o co już mu chodzi – tylko nie wiem jak wpisać poprawnie adres mac do routera bo na obecną chwile ,, nie połączy się z internetem..

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *