Sterowanie oświetleniem przez sieć LAN #3

in pl-artykuly •  6 years ago  (edited)

W poprzednich częściach cyklu na temat mojego zdalnego sterowania oświetleniem po sieci LAN, opisałem zastosowane rozwiązania sprzętowe i software-owe od strony aplikacji sterującej urządzeniem. Dzisiaj chciałbym powiedzieć trochę na temat kodu mikro-kontrolera.

praca2.jpg
Program dla sterownika został zrealizowany w Arduino IDE. Wybrałem tego typu rozwiązanie ze względu na mnogość gotowych bibliotek ułatwiających komunikację po sieci. W przypadku Arduino ich użycie jest bardzo proste i przyjemne.Jedyną komplikacją jest konieczność posiadania programatora ISP poprzez który można aktualizować program Atmegi bezpośrednio ze środowiska, chociaż na upartego można po zaprogramowaniu po prostu wyjąć procesor z Arduino i osadzić w docelowym układzie. Ja polecam jednak rozwiązanie z programatorem, tym bardziej, że często on się przydaje a jego nabycie nie stanowi dużego wydatku. Osobiście używam USB-ASP. Projekt obwodu musi oczywiście posiadać odpowiednie wyjście standadu Kanda przy pomocy którego można połączyć programator z mikro-kontrolerem.

kanda.png

Program dla sterownika jest bardzo prosty. Analizę kodu dla Arduino najlepiej rozpoczynać w głównej pętli loop.
W pętli tej cyklicznie sprawdzamy, czy pojawiły się dane UDP. Jeśli rozmiar odebranego pakietu > 0, kod sprawdza
zgodność 10 znakowej preambuły z danymi zapamiętanymi w ee-promie urządzenia. Dzięki temu, inne dane UDP jakie trafią do sterownika nie będą miały wpływu na jego działanie. Program zinterpretuje takie dane jako błąd i odpowie kodem błędu, jaki zostanie odesłany do hosta z aplikacją użytkownika, którą omówiłem w poprzedniej części. Jeżeli preambuła w odebranym pakiecie jest zgodna, następuje wywołanie metody checktype, której zadaniem jest parsowanie pakietu i sprawdzenie jakiego typu dana została właśnie odebrana przez nasz sterownik. Rozkazy wysyłane są do sterownika prostym protokołem znakowym. Jak już wspominałem 10 znaków początkowych w odebranym pakiecie to preambuła. Kolejny 11 znak w pakiecie to właśnie typ rozkazu:

  • "r" sygnał przełączania wyjść przekaźnikowych
  • "h" sygnał zmiany preambuły
  • "i" sygnał zmiany ip sterownika
  • "b" sygnał zmiany bramy domyślnej sterownika
  • "s" żądanie statusu sterownika
  • "v" żądanie odesłania wersji firmware sterownika
  • "x" reset do domyślnych ustawień
  • "p" zmiana portu sterownika

W zależności od typu odebranego rozkazu, wywoływana jest odpowiednia metoda. Załóżmy, że sterownik odbiera komendę przełączania wyjść "r". Po jej odebraniu pakiet zostaje przekazany do metody steruj_wyjściem .Metoda ta jako argument otrzymuje wskaźnik do zmiennej bufor, w jakiej jest odebrany pakiet danych. Po jej wywołaniu analizowany jest 10 znak pakietu. Może on zawierać znak '1' albo '0' co odpowiednio oznacza włączenie, albo wyłączenie zadanego wyjścia. Numer wyjścia przekazywany jest w znaku 11 pakietu i może zawierać znaki '1' -'6' zależnie od numeru wyjścia, które użytkownik chce zdalnie włączyć lub wyłączyć. Następnie urządzenie zmienia stan odpowiedniego wyjścia, sterując dany przekaźnik. Po każdej zmianie stanu dowolnego z wyjść, sterownik odpowiada na adres IP hosta z jakiego odebrano komendę o przełączeniu, wysyłając mu status wszystkich portów w postaci 6 znaków mogących być '1' lub '0' (return_status) poprzedzony znakiem 's'. Dzięki temu serwer aplikacji użytkownika jest w stanie rozpoznać rodzaj odebranej danej jako status sterownika.

Poza zmianą stanu wyjść, urządzenie daje możliwość zmiany pozostałych, wspomnianych parametrów, takich jak zmiana adresacji IP,bramy i portu, resetu ustawień. Starałem się je w miarę czytelnie opisywać w kodzie przy pomocy komentarzy.

Cały projekt został przeze mnie stworzony wiele lat temu. Gdybym budował go dzisiaj, na pewno zrobiłbym wiele rzeczy inaczej, zwłaszcza jeśli chodzi o rozwiązanie software-owe. Niemniej jednak rozwiązanie działa, program robi co do niego należy.

Kompletny kod mikro-kontrolera poniżej:

#include <EEPROM.h>
#include <SPI.h>        
#include <Ethernet.h>
#include <Udp.h>       
char haslo[]="0000000000"; //default preamble
byte mac[]={0x90,0xA2,0xDA,0x00,0x11,0x63}; //Wiznet macaddress
byte ip[]= {192,168,2,17 }; //default ip
byte brama[] = { 192, 168, 2, 1 };   //default gateway
unsigned int cel_port=64000; // port of remote host with user app
unsigned int localPort = 80;      //default listening port od this device
byte zdalnyIp[4];      //remote client ip
unsigned int remotePort; 
int power(int x, int y) //power function
{
  byte value=x;
  for(int i=0;i<=y;i++)
  {
    if(i==0)
    value=1;
    else
    value=value*x;
  }
  return value;
}
char buforDanych[64]; //received data buffer
int stan=0;
void data_read(char typ) //read data from eeprom
{
  byte i=0;
  switch(typ)
  {
    case 'i':
    if (EEPROM.read(1)==1) //1 - value already exist
    {
       for (i=20;i<=23;i++)
      {
        ip[i-20]=EEPROM.read(i); //read ip from eerom
      }
    }
    else // ip value not exist - remember
    {
      ip[0]=192;
      ip[1]=168;
      ip[2]=2;
      ip[3]=17;
    }
    break;
    case 'b':
     if (EEPROM.read(2)==1) //value already exist
    {
       for (i=24;i<=27;i++)
      {
        brama[i-24]=EEPROM.read(i); //read gateway from eeprom
      }
    }  
    else //gateway datanot exist - remember 
    {
       brama[0]=192;
       brama[1]=168;
       brama[2]=2;
       brama[3]=1;  
    }   
    break;
    case 'p': //read port number from eeprom
     if (EEPROM.read(3)==1) //port number value is exist
         {  
            switch(EEPROM.read(28))
            {
              case 1:
              //cel_port=1200;
              localPort = 1200;
              break;
              case 2:
               // cel_port=2400;
                localPort = 2400;
              break;
              case 3:
                //cel_port=4800;
                localPort = 4800;
              break;
              case 4:
               // cel_port=9600;
                localPort = 9600;
              break;
              case 5:
               // cel_port=19200;
                localPort = 19200;
              break;
              case 6:
                //cel_port=38400;
                localPort = 38400;
              break;
              case 7:
               // cel_port=64000;
                localPort = 64000;
              break;
              case 8:
                //cel_port=80;
                localPort = 80;
              break;      
            }
         }
         else //port value not exist in eeprom, use default value 80
         {
           localPort=80; 
           //cel_port=localPort;
         }
    
    case 'h':
    if (EEPROM.read(0)==1)//preamble is set in eeprom
    {
      for (i=10;i<=19;i++)
      {
        haslo[i-10]=EEPROM.read(i); //lod preamble from eeprom to variable
      }    
    }   
    else //if value not exist in eeprom default value is using
    {
      for (i=0;i<=9;i++)
      {
        haslo[i]='0';
      }
    }
    break;
  }
}
void pass_write(char *haslo) //save preamble to eeprom
{
  byte adres=0;
  byte i=0;
   for (adres=10;adres<=19;adres++)
    {
    EEPROM.write(adres,haslo[i]);
    i++;
    }
     EEPROM.write(0,1); //change preamble flag to 1 - preamble is saved in eeprom
}

void ip_write(byte *numer ,char typ)  //save setings to eeprom
{
  byte adres=0;
  byte i=0;
  switch(typ)
  {
    case 'i'://ip
    for (adres=20;adres<=23;adres++)
    {
    EEPROM.write(adres,numer[i]);
    i++;
    }
    EEPROM.write(1,1);//change ip saved flag to 1
    break;
    case 'b'://brama
     for (adres=24;adres<=27;adres++)
    {
    EEPROM.write(adres,numer[i]);
    i++;
    }
     EEPROM.write(2,1); //change gate flag to 1
    break;   
  }
}

void port_write(int value)
{
    EEPROM.write(3,1); //change port flag to 1  
    EEPROM.write(28,value);
}

void set_default()  //load default settings from eeprom
{
  //change data flags to 0 - default settings will be used
  EEPROM.write(0,0); //zero na tej pozycji oznacza ze mozna zapisywac nowe haslo.Uzyte zostanie domyslne haslo 
  EEPROM.write(1,0); //mozna zapisywac ip.Uzyte bedzie domyslne ip 192.0.2.17
  EEPROM.write(2,0);//mozna zapiszywac brame.Uzyta bedzie brama domyslna 192.0.2.1
  EEPROM.write(3,0) ;//mozna wpisac port.0 powoduje ustawienie portu domyslnego  
}

byte bin2dec(char *bin)//bin to dec function
{
    byte wynik=0;
    for (int i=0;i<8;i++)
    {

        if (bin[i]=='1')
        {
        wynik=wynik +power(2,i);
        }
    }
return wynik;
}
void set_ip(char *bufor,char typ) 
{
boolean rodzaj;
byte i=11; //od 11 znaku zaczyna sie ip 0-9 haslo 10 -ID od 11 4x8 znakow to ip
byte a=0;
char oktet[32];
byte num=0;
byte numer_oktetu=0;
 Udp.sendPacket("Zmiana adresu IP", zdalnyIp, cel_port);  
 for (a==0;a<=33;a++)
 {
   oktet[a]=' ';//czyszczenie tablicy oktet
 }
 a=0;
while(i<=strlen(bufor))
{
  if (a==8)//odczyt 8 znakow 4x 8
  {
    a=0;
    num=bin2dec(oktet); //zamiana odebranych danych binarnych na dziesietne
    Udp.sendPacket(oktet, zdalnyIp, cel_port); 
    if (typ=='i')
    {
      rodzaj=true;
    ip[numer_oktetu]=num; //zmiana ip sterownika
    }
    if (typ=='b')
    {
      rodzaj=false;
      brama[numer_oktetu]=num; //zmiana ip bramy
    }
    num=0;
    numer_oktetu++; 
  }
  oktet[a]=bufor[i]; //zapisanie ip do tablicy oktet
  a++; //kolejny bajt
  i++; //kolejny bajt
}
if (rodzaj==true) //jesli bylo zmieniane ip
{
ip_write(ip,'i'); //zapis ip do eeprom
Udp.sendPacket("i", zdalnyIp, cel_port);
}
else // jesli byla zmieniana brama to
{
  ip_write(brama,'b');//zapis bramy do eeprom
  Udp.sendPacket("b", zdalnyIp, cel_port);
}

 Ethernet.begin(mac,ip,brama);
  Udp.begin(localPort);

}
void set_pass(char *bufor) //ustawia nowe haslo
{
      for (int i=0;i<=9;i++)
  {
    //od 12 bajtu zaczyna sie nowe haslo w ciagu
 haslo[i]=bufor[i+11]; 
                       // 10         10
  }
      pass_write(haslo); //zapisanie hasla do eepromu.
      Udp.sendPacket(haslo, zdalnyIp, cel_port);
      Udp.sendPacket("h", zdalnyIp, cel_port);
}
void steruj_wyjsciem(char *bufor) //analizuje 12 i 13 bajt paczki danych - rozkaz otwacia albo zamkniecia wyjscia  //parsing received packet data - on/off output
{
     
 if (bufor[11]=='1') //pierwszy bajt 1 - wlaczenie portu
    {
    
       switch (bufor[12])
       {
          case '1':       
         digitalWrite(0,1); 
         break;
         case '2':
         digitalWrite(1,1);
         break;
         case '3':
         digitalWrite(2,1);
         break;
         case '4':
         digitalWrite(3,1);
         break;
         case '5':
         digitalWrite(4,1);
         break;
         case '6':
         digitalWrite(5,1);
         break;
       }  
    }
    
    if (bufor[11]=='0') //pierwszy bajt rozkazu= 0 wylaczenie
    {
   switch (bufor[12])
   {
     case '1':
     digitalWrite(0,0);
     break;
     case '2':
     digitalWrite(1,0);
     break;
     case '3':
     digitalWrite(2,0);
     break;
     case '4':
     digitalWrite(3,0);
     break;
     case '5':
     digitalWrite(4,0);
     break;
     case '6':
     digitalWrite(5,0);
     break;
   }

    //clear_bufor(bufor); 
    }
  return_status(); //wysyla do zdalnego hosta status wyjsc po kazdej zmianie  return sttus od output when changed state
  
}
void return_status()//zwraca status wyjsc,return sttus od output when changed state
  
{
  char stat[7];
  stat[0]='s';

  for (int i =1;i<=6;i++)
  {
    if (digitalRead(i-1)==HIGH)
    {
      stat[i]='1';
    }
    else
    {
      stat[i]='0';
    }
  } 
 Udp.sendPacket(stat, zdalnyIp, cel_port); //wysyla status wyjsc do remote ip sending of outputs to remote host
}

boolean checkpass(char *bufor) //checking preamble in packetdata is valid
//analizuje odebrana paczke danych i sprawdza czy poprawne haslo checking preamble is valid
{
  
  for (int i=0;i<=strlen(haslo)-1;i++)
  {
 
   if (haslo[i]!=bufor[i]) 
   {
     Udp.sendPacket("e", zdalnyIp, cel_port);// zwraca kod bledu - e //preamble not valid - return 'e' error code
   return false;
   }  
  }
  
  return true;
}
void checktype(char *bufor) //sprawdza typ sygnalu i wywoluje odpowiednia funkcje check signal type and fire functions depends on
{

   switch(bufor[10]) //bajt 11 paczki danych to typ - r to rozkaz przelaczenia wyjscia byte 11 pof packet - typedata 'r' is command change output state
   {
     case 'r': //sygnal r rozkaz przelaczenia wyjsc change output state
  
    steruj_wyjsciem(bufor); //wywolanie funkcji przelaczjacej wejscia start change output state
  
     break;
     
     case 'h': //sygnal zmiana hasla //preamble change command
      set_pass(bufor);
     break;
     
     case 'i': //sugnal zmiana ip sterownika //device i pchange command
     set_ip(bufor,'i');
     break;
     
     case 'b': //sygnal zmiany bramy domyslnej //gateway change command
     set_ip(bufor,'b');
     break;
     
     case 's': //pobranie statusu wyjsc //get output state command
     return_status();
     break;
     
     case 'v': //get firware version command
     Udp.sendPacket("vSterownik v.1.1 flocki", zdalnyIp, cel_port);
     break;
     
     case 'x': //reset to defult settings command
     Udp.sendPacket("x", zdalnyIp, cel_port);
     set_default();
     setup();
    
     break;
      
     case 'p'://zmiana portu sterownika //change port of driver command
      Udp.sendPacket("p", zdalnyIp, cel_port); //zwraca p jako info o zmianie portu
     switch(bufor[11]) //zapamietuje dane wartosci portow zaleznie od odebranej danej //remember port value depending on received value from remote host
     {
       case '1': 
       localPort=1200;
      // cel_port=1200;
       port_write(1);
       break;
       case '2':
       localPort=2400;
      // cel_port=2400;
       port_write(2);
       break;
       case'3':
       localPort=4800;
      // cel_port=4800;
       port_write(3);
       break;
       case '4':
       localPort=9600;
      // cel_port=9600;
       port_write(4);
       break;
       case '5':
       localPort=19200;
      // cel_port=19200;
       port_write(5);
       break;
       case '6':
       localPort=38400;
     //  cel_port=38400;
       port_write(6);
       break;
       case '7':
       localPort=64000;
     //  cel_port=64000;
       port_write(7);
       break;
       case'8':
       localPort=80;
     //  cel_port=80;
       port_write(8);
       break;   
     }
      Ethernet.begin(mac,ip,brama);//inicjalizacja ethernet i udp z nowym portem initialize network with new settings
        Udp.begin(localPort); 
   }
}
void clear_bufor(char *bufor) //buffer data clear
{
  for (int i=0;i<strlen(bufor);i++)
  {
    bufor[i]=' ';
  }
}
void setup() 
{
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT); 
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode (9,INPUT); //pin resetu
  if (digitalRead(9)==HIGH) //wcisniecie reset przy wlaczeniu przywraca ust.domyslne //when resset button is pressed default settings will be loaded
  {
    set_default();
  }
  //read settings from eeprom
  data_read('h'); //pobranie hasla z pamieci eeprom
  data_read('i');//pobranie ip
  data_read('b');//pobranie bramy
  data_read('p'); //pobranie nr portu
  //
 Ethernet.begin(mac,ip,brama);
  Udp.begin(localPort);
  return_status();   
}

void loop() { //main loop
  int packetSize = Udp.available(); //when ud data received
  if(packetSize) //when packeddta >0
  {
    packetSize = packetSize - 8;     
   
   Udp.readPacket(buforDanych,64, zdalnyIp, remotePort); //read udp data  
  if( checkpass(buforDanych)==true )//gdy haslo poprawne when preamble is valid
  {    
    checktype(buforDanych); //sprawdza jakiego typu dana zostala odebrana //parse data of udp received packet
  }
  delay(10);
}
}

Pozdrawiam wszystkich!:)

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Hej, kolejny ciekawy artykuł. Sugeruję tagować również #pl-programowanie, to zwiększy zasięg artykułu. I ogólnie chyba lepiej celować w duże tagi z przedrostkiem pl-: https://steemweb.pl/categories. Tagi, które funkcjonują również jako angielskie słowa (#arduino, #diy) sprawiają, że tekst trafia również do angielskojęzycznych odbiorców, a oni raczej średnio będą zainteresowani tekstem po polsku. Optymalne tagowanie postawiłbym jako drugie pod względem ważności (po jakości tekstu), jeśli mówimy o sukcesie postu :)

Coś się też formatowanie rozjechało, sprawdziłem u siebie i udało mi się sformatować cały kod bez żadnych problemów, więc to chyba nie jest kwestia jakichś dziwnych znaków w środku (zrobiłem 3 backticki na początku i 3 na końcu).

Dziękuję! Będę tagował zgodnie z Twoimi sugestiami i postaram się poprawić w kwestii formatowania.:)

Hej, sam jestem elektronikiem i chętnie będę wspierał osoby, które coś tam grzebią :)

Musisz tylko popracować nad formatowaniem postów, bo ciężko się je czyta gdy wszystko się rozjeżdża :(

Dzięki za sugestie, cieszę się że ktoś mnie czyta:).Wiem że to formatowanie kuleje, muszę to rzeczywiście dopracować. Nie za bardzo wiem jak w czytelny sposób zamieścić tu kod.

Wystarczy cały kod objąć 3 znakami ` (u góry i u dołu).

@jacekw dzięki! Właśnie tego potrzebowałem :)