Sterowanie oświetleniem przez sieć LAN #2

in #pl-artykuly6 years ago (edited)

W poprzedniej części omówiłem rozwiązania sprzętowe projektu. Teraz napiszę trochę o sposobie komunikacji aplikacji użytkownika ze sterownikiem.
praca2.jpg

Komunikacja ze sterownikiem

Do transmisji danych wykorzystałem protokół UDP. UDP jest protokołem umiejscowionym w 4 warstwie modelu OSI i jest jednym z podstawowych protokołów sieciowych. Charakteryzuje go prosta budowa wynikająca z braku mechanizmów przepływu i retransmisji, oraz faktu, że jest to protokół bezpołączeniowy(brak narzutu na nawiązywanie połączenia i śledzenie sesji). Prostota protokołu w tym wypadku przynosi korzyści w postaci braku dodatkowych zadań obciążających mikrokontroler. Z uwagi na bezpołączeniowość protokołu, kontrolę poprawności transmisji zrealizowałem w warstwie aplikacji. Bez takiej kontroli, nie można mieć pewności, że datagram UDP dotarł do adresata.

Datagram UDP poza nagłówkami niższego poziomu zawiera nagłówek UDP w skład którego wchodzą:

  • docelowy adres IPv4
  • docelowy port(16 bitów)
  • port źródłowy(16 bitów,opcjonalny)
  • długość(16 bitów) - zawiera nagłówek oraz dane. Teoretyczna długość tego pola to 54,527 bajtów
  • suma kontrolna(16 bitów),pole opcjonalne.IP nie wylicza sumy kontrolnej, dlatego jest to jedyna gwarancja, że dane pakietu nie zostały uszkodzone.

UDP poprzez porty umożliwia identyfikację puntów docelowych(aplikacji lub usług).Daje także możliwość transmisji danych di wielu hostów jednocześnie(broadcast).

Jeżeli dwa procesy sieciowe - w tym wypadku kod mikro-kontrolera i aplikacja użytkownika -mają komunikować się przez sieć Ethernet, wymagane jest aby obydwa otworzyły gniazdo i przesyłały przez nie dane w blokach po kilka kilobajtów(pakietach IP). Gniazdo umożliwia programowi wykonanemu w języku wysokiego poziomu dostęp do stosu protokołów TCP/IP. Gniazda posiadają dwa istotne parametry: adres IP oraz numer portu. Pakiet, który trafi do docelowego hosta o zdefiniowanym w gnieździe IP musi jeszcze dotrzeć do odpowiedniego procesu z którym ma zostać nawiązana łączność. Numer portu ma za zadanie zidentyfikowanie tego procesu. W budowanym projekcie portem domyślnym sterownika jest port 80. Zmiana portu jest możliwa z poziomu aplikacji sterującej poprzez wysłanie sygnału zmiany portu Po obydwu stronach port serwera UDP może być taki sam. Komunikacja ze sterownikiem działa na zasadzie "pytanie - odpowiedź". Aplikacja użytkownika nasłuchuje na porcie 64000.

Aby umożliwić sterowanie urządzeniem przez sieć Ethernet, stworzyłem zestaw rozkazów, które po wysłaniu przez użytkownika z aplikacji sterującej analizowane są przez mikro-kontroler. W zależności od rodzaju rozkazu mikro-kontroler podejmuje odpowiednie działanie. Każdorazowo w paczce odebranych danych znajduje się 10-bajtowa preambuła, następnie jednoznakowy identyfikator rozkazu ID . Zależnie od jego zawartości mikro-kontroler odpowiednio interpretuje pozostałe odebrane dane i wykonuje rozkaz. Odbiór danych odbywa się przez pooling, co oznacza, że w głównej pętli kodu mikro-kontrolera cyklicznie wywoływana jest funkcja sprawdzająca, czy odebrane zostały dane. Gdy dane zostaną odebrane, mikro-kontroler analizuje pierwsze dziesięć bajtów odebranej paczki danych (preambuła). Jeśli jest poprawna, analizowany jest bajt ID rozkazu. Od jego zawartości zależy w jaki sposób mikro-kontroler zadziała.Jeśli rozkaz odebrany przez sterownik jest rozpoznany, sterownik wysyła odpowiedź na adres IP z jakiego odebrał dane. Adres ten sterownik odczytuje z odebranego datagramu UDP w którym jest on zawarty. Jeśli rozkaz jest nieznany, sterownik zwraca kod błędu (‘e’). Zwrotna odpowiedź sterownika jest interpretowana przez kliencką aplikację sterującą, w której – podobnie jak w sterowniku – działa zarówno serwer jak i klient UDP.

Aplikacja użytkownika

app.png

Zadaniem aplikacji jest umożliwienie wysyłania do sterownika rozkazów sterujących i odbieranie statusów od niego. W górnej części okna umieściłem przyciski odpowiadające kolejnym wyjściom przekaźnikowym sterownika, wraz z kolorową sygnalizacją statusu. Dodatkowo w polu "komunikaty" pojawiają się szczegółowe informacje na temat przesyłanych i odbieranych danych. Poza przełącznikami wyjść w aplikacji znajdują się pola umożliwiające konfigurację IP sterownika a także numeru portu na jakim sterownik ma nasłuchiwać. Aplikacja daje także możliwość zmiany ustawień IP sterownika na inne (przycisk Zmień) Jeśli sterownik ma być wyzwalany spoza sieci, w polu "IP docelowy" wystarczy podać IP nadane przez providera internetu, w polu "IP sterownika" adres lokalny sterownika a w polu "brama domyślna" bramę zgodną z konfiguracją naszej sieci. Należy także pamiętać o przekierowaniu ruchu na adres lokalny sterownika,po wybranym porcie w konfiguracji domowego routera.

sieciii.png

Aplikacja powstała w C#(WinForms).

Wysyłanie danych do sterownika zapewnia klasa Klient.

class Klient
    {
        private int _port;
        private string _ip;
        private UdpClient _client;

        public Klient(string ip, int port = 80)
        {
            _port = port;
            _ip = ip;
            _client = new UdpClient(ip, port);
        }
        private byte[] Encode(string txt)
        {
            return Encoding.ASCII.GetBytes(txt);
        }
        public void wyslij(string dat)
        {
            byte[] dane = Encode(dat);
            _client.Send(dane, dane.Length);
        }
        public void set_ip(string ip)
        {

            _client = new UdpClient(ip,_port ); 
        }

      
    }

W konstruktorze klasy Klient tworzony jest obiekt klasy UDPClient. W momencie gdy tworzony jest obiekt klasy Klient, tworzone jest gniazdo o adresie IP oraz porcie podanym do konstruktora klasy jako argument, przy czym numerem portu przez domniemanie jest 80. Aby wysłać dane protokołem UDP na zadany adres IP wykorzystano metodę Send klasy UdpClient (metoda wyslij(string dat) klasy Klient). Łańcuch znaków podany jako argument do metody, jest następnie enkodowany do postaci bajtowej i wysyłany na adres zdefiniowany jako docelowy. W przypadku zmiany przez użytkownika, docelowego adresu IP wywołana zostaje metoda set_ip(string ip) klasy Klient. Aplikacja sterująca realizuje również funkcję serwera UDP nasłuchując na porcie 64000. Sterownik po otrzymaniu rozkazu przetwarza go i w zależności od jego typu wysyła do aplikacji sterującej odpowiedź – potwierdzenie. Serwer interpretuje odebrane dane i na ich podstawie wyświetla informacje w oknie komunikatów aplikacji. W ten sposób rozwiązana jest kontrola poprawności transmisji, której nie posiada zastosowany protokół UDP.

Metoda Server() uruchamiana w odrębnym wątku:

Task t2 = Task.Factory.StartNew(Server);
private void Server()
        {
            UdpClient udp = new UdpClient(64000);
            string dane = " ";
            while(true)
            {
            IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
            Byte[] receivedBytes = udp.Receive(ref RemoteIpEndPoint);
            string returnData = Encoding.ASCII.GetString(receivedBytes);
            switch (returnData[0])
            {
                case 's': 
                    for (int i = 1; i <= 6; i++)
                    {
                        switch (returnData[i])
                        {
                            case '1':
                                Invoke((Action)(()=> stan(i,true)));
                                break;
                            case '0':
                                Invoke((Action)(() => stan(i, false));
                                break;
                        }
                    }
                    for (int i = 1; i <= 6; i++) 
                    {
                        dane = dane + returnData[i];
                    }
                    listBox1.Invoke((Action)(() => listBox1.Items.Add(RemoteIpEndPoint.Address + ": Status wyjść: " + dane)));
                    dane = " ";
                    break;
                case 'h': 
                    listBox1.Invoke((Action)(() => listBox1.Items.Add(RemoteIpEndPoint.Address + ": Hasło zmienione")));
                    break;
                case 'i': 

                    listBox1.Invoke((Action)(() => listBox1.Items.Add(RemoteIpEndPoint.Address + ": Zmieniono adres IP sterownika")));
                    break;
                case 'b':
                    listBox1.Invoke((Action)(() => listBox1.Items.Add(RemoteIpEndPoint.Address + ": Zmieniono IP bramy sterownika")));
                    break;
                case 'v':
                    for (int i = 1; i < returnData.Length; i++)
                    {
                        dane = dane + returnData[i];
                    }
                    listBox1.Invoke((Action)(() => listBox1.Items.Add(RemoteIpEndPoint.Address + ": " + dane)));
                    break;
                case 'e':
                    listBox1.Invoke((Action)(() => listBox1.Items.Add(RemoteIpEndPoint.Address + " Błąd danych")));
                    break;
            }
            returnData = " ";
            dane = " ";
            }
        }

Dane odebrane w metodzie Server zapisaywane są do zmiennej typu string a następnie analizowane przez instrukcję switch. Pierwszy bajt odebranych danych identyfikuje rodzaj odebranej informacji w wyniku czego zostaje wywołana odpowiednia funkcja przy użyciu Invoke. Metoda Server działa w wiecznej pętli while,nasłuchując na porcie 64000. W instrukcji switch analizowany jest pierwszy bajt odebranych danych, zwróconych przez sterownik. W zależności od typu odebranych danych w oknie komunikatów aplikacji sterującej wyświetlane są stosowne informacje potwierdzające zrealizowanie danej instrukcji sterującej przez urządzenie.
Na dzisiaj tyle. W kolejnej części spróbuję omówić kod mikro-kontrolera. Pozdrawiam wszystkich ! :)

Coin Marketplace

STEEM 0.17
TRX 0.15
JST 0.028
BTC 57852.72
ETH 2355.59
USDT 1.00
SBD 2.44