[043] Zdalne sterowanie przekaźnikami – wersja poszerzona

[043] Zdalne sterowanie przekaźnikami – wersja poszerzona

Przypomnę: dotąd za pomocą bardzo skromnego panelu mogliśmy włączać i wyłączać różne urządzenia podłączone do przekaźnika – jednego. Czas użyć pozostałych trzech i poszerzyć funkcjonalność projektu o kolejne elementy.


Przejdźmy sobie do zakładki Thing, wybierzmy nasz projekt, kliknijmy w ADD i stwórzmy trzy kolejne zmienne, podobne do już istniejącej. Będą mieć nazwę przelacznik z kolejnymi cyframi, a resztę parametrów należy po prostu skopiować ze zmiennej przelacznik1

Powinniśmy otrzymać zbiorek czterech zmiennych, przy czym trzy nie będą mieć ani określonego stanu, ani daty ostatniego zadeklarowania, ponieważ dopiero co powstały.

Przejdźmy teraz do zakładki z panelami (Dashboard) i rozmnóżmy naszą ikonkę czterokrotnie – za pomocą funkcji Duplicate. Oczywiście wszystkie cztery będą się zachowywać identycznie, więc należy teraz po kolei wejść w edycję każdej z nich i zmienić im nazwy oraz przyporządkować odpowiednią zmienną z listy, którą przed chwilą stworzyliśmy.

Powinniśmy dostać panel z czterema przełącznikami o różniących się nazwach i, co najważniejsze, jednoznacznie przyporządkowanych do indywidualnych zmiennych. Wracamy do szkiców (Sketch). Weryfikujemy plik z ustawieniami.

#include <ArduinoIoTCloud.h>
#include <Arduino_ConnectionHandler.h>

const char DEVICE_LOGIN_NAME[] = "";
const char SSID[] = SECRET_SSID;
const char PASS[] = SECRET_OPTIONAL_PASS;
const char DEVICE_KEY[] = SECRET_DEVICE_KEY;

bool przelacznik1;
bool przelacznik2;
bool przelacznik3;
bool przelacznik4;

void initProperties() {
  ArduinoCloud.setBoardId(DEVICE_LOGIN_NAME);
  ArduinoCloud.setSecretDeviceKey(DEVICE_KEY);
  ArduinoCloud.addProperty(przelacznik1, READWRITE, ON_CHANGE);
  ArduinoCloud.addProperty(przelacznik2, READWRITE, ON_CHANGE);
  ArduinoCloud.addProperty(przelacznik3, READWRITE, ON_CHANGE);
  ArduinoCloud.addProperty(przelacznik4, READWRITE, ON_CHANGE);
}

WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);

Powinny się tutaj znaleźć cztery deklaracje binarnych zmiennych przelacznik oraz bliźniacze wpisy, również ze zmienną przelacznik ze stosownym numerkiem.

#include "thingProperties.h" // Dołącz plik z definicjami.
const byte przekaznik1 = 4;  // Port przekaźników.
const byte przekaznik2 = 5;
const byte przekaznik3 = 6;
const byte przekaznik4 = 7;

void setup() {
  initProperties();                                  // Zaczytaj definicje z pliku thingProperties.h
  ArduinoCloud.begin(ArduinoIoTPreferredConnection); // Inicjuj połączenie z chmurą Arduino IoT Cloud
  pinMode(przekaznik1, OUTPUT);                      // Deklaruj porty przekaźników jako wyjścia.
  pinMode(przekaznik2, OUTPUT);
  pinMode(przekaznik3, OUTPUT);
  pinMode(przekaznik4, OUTPUT);
}

void loop() {
  ArduinoCloud.update();
  digitalWrite(przekaznik1, przelacznik1); // Ustaw przekaźnik zgodnie ze stanem przełącznika.
  digitalWrite(przekaznik2, przelacznik2);
  digitalWrite(przekaznik3, przelacznik3);
  digitalWrite(przekaznik4, przelacznik4);
}

Przechodzimy do zakładki ze szkicem i tutaj musimy wymnożyć byty, czyli wszystko co zawierało w sobie nazwę przekaznik bądź przelacznik zestawić czwórkami. Oczywiście trzeba pamiętać o kolejnych adresach przekaźników z płytki, które producent ustawił na stałe w zakresie od czwartego do siódmego. Gdy wszystko będzie gotowe, pozostanie program skompilować i wysłać do Arduino. Po chwili będziemy mogli sterować za pomocą telefonu już nie jednym, a czterema przekaźnikami i to jest już jak najbardziej użyteczne.

W praktyce tak słodko nie jest. O ile jesteśmy poza domem, kilka, a nawet kilkanaście sekund niezbędnych do startu apki i nawiązania współpracy będzie tolerowane, ale uczynienie z telefonu pilota na takich warunkach już nie za bardzo. Apka pracująca w tle budzi się znacznie szybciej, ale konsumuje energię w stopniu wyczuwalnym, jeśli nie ładujemy telefonu każdego dnia. Z tego powodu, ale też z czysto praktycznych, urządzenie powinno mieć alternatywną lokalną klawiaturę.

Skorzystamy tutaj z programu, który kiedyś już napisałem. Jak wtedy mówiłem, nieszczęśliwie adresy przycisków weszły w konflikt z adresami przekaźników, ale płytka TME EDU ARD 2 jest na tyle sprytna, że w razie potrzeby dobra na niej zawarte można przekrosować.

Cztery przewody rozwiązują problem i odtąd przyciski będą okupować adresy od 9 do 12. Toteż dodajemy je w deklaracjach i oczywiście definiujemy jako wejściowe.

#include "thingProperties.h" // Dołącz plik z definicjami.
const byte przekaznik1 = 4;  // Port przekaźników.
const byte przekaznik2 = 5;
const byte przekaznik3 = 6;
const byte przekaznik4 = 7;
const byte pstryczek1 = 9;   // Port klawiatury.
const byte pstryczek2 = 10;
const byte pstryczek3 = 11;
const byte pstryczek4 = 12;
const byte opoznienie = 100; // Opóźnienie w ms po wciśnięciu pstryczków.

void setup() {
  initProperties();                                  // Zaczytaj definicje z pliku thingProperties.h
  ArduinoCloud.begin(ArduinoIoTPreferredConnection); // Inicjuj połączenie z chmurą Arduino IoT Cloud
  pinMode(przekaznik1, OUTPUT);                      // Deklaruj porty przekaźników jako wyjścia.
  pinMode(przekaznik2, OUTPUT);
  pinMode(przekaznik3, OUTPUT);
  pinMode(przekaznik4, OUTPUT);
  pinMode(pstryczek1, INPUT_PULLUP);                 // Deklaruj porty pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
  pinMode(pstryczek2, INPUT_PULLUP);
  pinMode(pstryczek3, INPUT_PULLUP);
  pinMode(pstryczek4, INPUT_PULLUP);
}
void loop() {
  if (digitalRead(pstryczek1) == HIGH) {        // Jeśli wciśnięto pstryczek, to...
    przelacznik1 = !przelacznik1;               // Zaneguj stan przełącznika,
    digitalWrite(przekaznik1, przelacznik1);    // Wyślij na przekaźnik jego stan,
    delay(opoznienie);                          // Zaczekaj chwilę,
    while (digitalRead(pstryczek1) == HIGH) {}  // Czekaj na puszczenie pstryczka,
    delay(opoznienie);                          // Zaczekaj chwilę.
  }
  if (digitalRead(pstryczek2) == HIGH) {
    przelacznik2 = !przelacznik2;
    digitalWrite(przekaznik2, przelacznik2);
    delay(opoznienie);
    while (digitalRead(pstryczek2) == HIGH) {}
    delay(opoznienie);
  }
  if (digitalRead(pstryczek3) == HIGH) {
    przelacznik3 = !przelacznik3;
    digitalWrite(przekaznik3, przelacznik3);
    delay(opoznienie);
    while (digitalRead(pstryczek3) == HIGH) {}
    delay(opoznienie);
  }
  if (digitalRead(pstryczek4) == HIGH) {
    przelacznik4 = !przelacznik4;
    digitalWrite(przekaznik4, przelacznik4);
    delay(opoznienie);
    while (digitalRead(pstryczek4) == HIGH) {}
    delay(opoznienie);
  }  
  ArduinoCloud.update();
  digitalWrite(przekaznik1, przelacznik1); // Ustaw przekaźnik zgodnie ze stanem przełącznika.
  digitalWrite(przekaznik2, przelacznik2);
  digitalWrite(przekaznik3, przelacznik3);
  digitalWrite(przekaznik4, przelacznik4);
}

Skopiowałem sobie procedury operujące przyciskami tak, by wyeliminować ewentualne drgania zestyków i autopowielanie. Jeśli teraz kolejny z pstryczków zostanie wciśnięty, to zmienna przelacznik zostanie zaprzeczona, czyli zmieni stan na przeciwny. Tak, przelacznik – a ta zmienna jest równocześnie zmieniana zgodnie z położeniem przełącznika na ekranie smartfona. Byłoby niedobrze, gdyby lokalna klawiatura zmieniała stan przekaźnika bez informowania o tym chmury. Na szczęście transmisja ma miejsce w obie strony i priorytetem jest owa zmienna w fizycznym Arduino. Czyli jeśli zostanie zmieniona z poziomu smartfona, klawiaturka na starcie otrzyma jej stan faktyczny i na odwrót: po zmianie stanu za pomocą klawiaturki, do chmury zostanie wysłany jej stan z Arduino. Chmura zadba już o odświeżenie tego stanu na ekranie smartfona. Niestety opóźnienia są odczuwalne i sięgają sekundy, jednak w większości wypadków nie ma to znaczenia. Tam gdzie miałoby, trzeba pracować w szybszym środowisku bądź lokalnie. Najszybciej będzie pracować pilot nie korzystający z wi-fi, co nie wyklucza użycia obu technik jednocześnie.

Wróćmy do szkicu. Jak mówiłem, po zaprzeczeniu zmiennej przelacznik następuje natychmiastowe ustawienie przekaźnika zgodnie z jej stanem. Teoretycznie można tę linię pominąć, bo przecież za chwilę ten rozkaz nadejdzie z chmury – jako że naruszono stan zmiennej, co implikuje wysłanie rozkazu. Ale mamy tutaj problem opóźnienia: wspomniana sekunda czyni obsługę bardzo nieintuicyjną. Nie może być tak, by klawiatura nie dawała rezultatów natychmiast, tylko po bliżej nieokreślonej chwili.

Jest jeszcze drugi problem. Następną instrukcją jest opóźnienie, zapobiegające wejściu przełącznika w stany nieustalone i kolejno, oczekujemy aż przełącznik zostanie puszczony, a więc dodatkowo czekamy. Te wszystkie opóźnienia razem wzięte byłyby zupełnie nietolerowane, toteż linia przełączająca przekaźnik zaraz po wykryciu zdarzenia musi istnieć.

Na końcu znajduje się grupa odświeżająca stan przekaźników zgodnie ze stanem przełączników na panelu smartfona i program wraca na początek. Taki program jest już bardzo użyteczny, umożliwia sterowanie zdalne jak i lokalne, przy czym lokalne działa szybko, a stany są uaktualniane na ekranie telefonu. Wszystko działa pięknie, lecz jest obarczone ryzykiem, typowym dla układów nadzorowanych zdalnie. Co będzie, gdy braknie prądu albo układ się zawiesi? Zacznijmy od braku energii: tutaj dobra wiadomość. Gdy zasilanie wróci, płytka połączy się z siecią, a stan przekaźników zostanie zaktualizowany zgodnie z pulpitem w telefonie. Jednak gdyby płytka się zawiesiła, bez resetu nie obejdzie się.

I tu wrócimy na chwilę do artykułu o watchdogu, czyli układzie, który resetuje nadzorowany mikroprocesor, gdy ten w odpowiednim czasie nie da znaku życia. Przypomnę, że idea polega na tym, by po inicjacji watchdoga co jakiś czas, nie przekraczający czasu maksymalnego ustalonego wcześniej, wysyłać do niego sygnał zerujący. Jeśli to nastąpi, watchdog niczego nie zmieni. Jednak gdyby taki zerujący sygnał nie pojawi się w określonym terminie, mikroprocesor zostanie zresetowany, a w tym wypadku – jak mówiłem przed chwilą, nawiąże łączność na nowo i pozwoli się ponownie nadzorować zdalnie.

#include "thingProperties.h" // Dołącz plik z definicjami.
#include <WDT.h>             // Biblioteka obsługująca watchdoga.
const byte przekaznik1 = 4;  // Port przekaźników.
const byte przekaznik2 = 5;
const byte przekaznik3 = 6;
const byte przekaznik4 = 7;
const byte pstryczek1 = 9;   // Port klawiatury.
const byte pstryczek2 = 10;
const byte pstryczek3 = 11;
const byte pstryczek4 = 12;
const byte opoznienie = 100; // Opóźnienie w ms po wciśnięciu pstryczków.

void setup() {
  initProperties();                                  // Zaczytaj definicje z pliku thingProperties.h
  ArduinoCloud.begin(ArduinoIoTPreferredConnection); // Inicjuj połączenie z chmurą Arduino IoT Cloud
  WDT.begin(4000);                                   // Włączamy watchdoga, będzie musiał być zerowany co cztery sekundy.
  pinMode(przekaznik1, OUTPUT);                      // Deklaruj porty przekaźników jako wyjścia.
  pinMode(przekaznik2, OUTPUT);
  pinMode(przekaznik3, OUTPUT);
  pinMode(przekaznik4, OUTPUT);
  pinMode(pstryczek1, INPUT_PULLUP);                 // Deklaruj porty pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
  pinMode(pstryczek2, INPUT_PULLUP);
  pinMode(pstryczek3, INPUT_PULLUP);
  pinMode(pstryczek4, INPUT_PULLUP);
}
void loop() {
  WDT.refresh();                                // Zeruj watchdoga.
  if (digitalRead(pstryczek1) == HIGH) {        // Jeśli wciśnięto pstryczek, to...
    przelacznik1 = !przelacznik1;               // Zaneguj stan przełącznika,
    digitalWrite(przekaznik1, przelacznik1);    // Wyślij na przekaźnik jego stan,
    delay(opoznienie);                          // Zaczekaj chwilę,
    while (digitalRead(pstryczek1) == HIGH) {}  // Czekaj na puszczenie pstryczka,
    delay(opoznienie);                          // Zaczekaj chwilę.
  }
  if (digitalRead(pstryczek2) == HIGH) {
    przelacznik2 = !przelacznik2;
    digitalWrite(przekaznik2, przelacznik2);
    delay(opoznienie);
    while (digitalRead(pstryczek2) == HIGH) {}
    delay(opoznienie);
  }
  if (digitalRead(pstryczek3) == HIGH) {
    przelacznik3 = !przelacznik3;
    digitalWrite(przekaznik3, przelacznik3);
    delay(opoznienie);
    while (digitalRead(pstryczek3) == HIGH) {}
    delay(opoznienie);
  }
  if (digitalRead(pstryczek4) == HIGH) {
    przelacznik4 = !przelacznik4;
    digitalWrite(przekaznik4, przelacznik4);
    delay(opoznienie);
    while (digitalRead(pstryczek4) == HIGH) {}
    delay(opoznienie);
  }  
  ArduinoCloud.update();
  digitalWrite(przekaznik1, przelacznik1); // Ustaw przekaźnik zgodnie ze stanem przełącznika.
  digitalWrite(przekaznik2, przelacznik2);
  digitalWrite(przekaznik3, przelacznik3);
  digitalWrite(przekaznik4, przelacznik4);
}

Do szczegółów odsyłam do wymienionego artykułu, natomiast tutaj zastosowałem nieco zmienioną procedurę. Po pierwsze, musimy użyć biblioteki przeznaczonej dla tego, konkretniego mikrokontrolera, a nie jest to już ośmiobitowy Atmel. Zmieniają się też parametry zerowania, maksymalny czas to niespełna sześć sekund, ustaliłem cztery – dość dużo, ale tutaj sterowanie jest niekrytyczne, a z zapasem pozwala na opóźnienia powodowane na przykład kiepskim zasięgiem sieci.

Po trzecie, usunąłem zerowanie watchdoga z pętli czekania na puszczenie przycisku. Jeśli te będą wciśnięte dłużej niż 4 sekundy, nastąpi niezależny reset. Dzięki temu można będzie zresetować urządzenie bez wyciągania wtyczki i bez potrzeby wsadzania osobnego przełącznika resetującego. Do tego będziemy mieć gwarancję, że procedura pracuje jak należy – wystarczy chwilę potrzymać wciśnięty dowolny przycisk. Oczywiście na miejscu, a nie w telefonie. Zerowanie watchdoga znajduje się tym razem na początku pętli, a dopiero później pozostałe procedury. W zasadzie byłoby obojętne, w którym miejscu osadzimy procedurę zerującą, byle tylko była odwiedzana regularnie. Po skompilowaniu i wysłaniu szkicu otrzymamy urządzenie odporne na zawieszenia. Teoretycznie, bo nie zdarzyło mi się takowe, natomiast możemy je zasymulować dłuższym trzymaniem dowolnego przycisku. Stracimy wówczas kontrolę na maksymalnie kilkadziesiąt sekund, choć lokalnie urządzenie powinno działać już po chwili.

#include "thingProperties.h" // Dołącz plik z definicjami.
#include <WDT.h>             // Biblioteka obsługująca watchdoga.
const byte przekaznik1 = 4;  // Port przekaźników.
const byte przekaznik2 = 5;
const byte przekaznik3 = 6;
const byte przekaznik4 = 7;
const byte pstryczek1 = 9;   // Port klawiatury.
const byte pstryczek2 = 10;
const byte pstryczek3 = 11;
const byte pstryczek4 = 12;
const byte opoznienie = 100; // Opóźnienie w ms po wciśnięciu pstryczków.
const byte led = 13;         // Port diody świecącej.
bool przelacznikLed;         // Stan diody świecącej.

void setup() {
  initProperties();                                  // Zaczytaj definicje z pliku thingProperties.h
  ArduinoCloud.begin(ArduinoIoTPreferredConnection); // Inicjuj połączenie z chmurą Arduino IoT Cloud
  WDT.begin(4000);                                   // Włączamy watchdoga, będzie musiał być zerowany co cztery sekundy.
  pinMode(przekaznik1, OUTPUT);                      // Deklaruj porty przekaźników jako wyjścia.
  pinMode(przekaznik2, OUTPUT);
  pinMode(przekaznik3, OUTPUT);
  pinMode(przekaznik4, OUTPUT);
  pinMode(pstryczek1, INPUT_PULLUP);                 // Deklaruj porty pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
  pinMode(pstryczek2, INPUT_PULLUP);
  pinMode(pstryczek3, INPUT_PULLUP);
  pinMode(pstryczek4, INPUT_PULLUP);
  pinMode(led, OUTPUT);                              // Deklaruj port diody świecącej jako wyjście.
}
void loop() {
  WDT.refresh();                                // Zeruj watchdoga.
  przelacznikLed = !przelacznikLed;             // Zaneguj przełącznik stanu diody świecącej.
  digitalWrite(led, przelacznikLed);            // Uaktualnij diodę świecącą.
  if (digitalRead(pstryczek1) == HIGH) {        // Jeśli wciśnięto pstryczek, to...
    przelacznik1 = !przelacznik1;               // Zaneguj stan przełącznika,
    digitalWrite(przekaznik1, przelacznik1);    // Wyślij na przekaźnik jego stan,
    delay(opoznienie);                          // Zaczekaj chwilę,
    while (digitalRead(pstryczek1) == HIGH) {}  // Czekaj na puszczenie pstryczka,
    delay(opoznienie);                          // Zaczekaj chwilę.
  }
  if (digitalRead(pstryczek2) == HIGH) {
    przelacznik2 = !przelacznik2;
    digitalWrite(przekaznik2, przelacznik2);
    delay(opoznienie);
    while (digitalRead(pstryczek2) == HIGH) {}
    delay(opoznienie);
  }
  if (digitalRead(pstryczek3) == HIGH) {
    przelacznik3 = !przelacznik3;
    digitalWrite(przekaznik3, przelacznik3);
    delay(opoznienie);
    while (digitalRead(pstryczek3) == HIGH) {}
    delay(opoznienie);
  }
  if (digitalRead(pstryczek4) == HIGH) {
    przelacznik4 = !przelacznik4;
    digitalWrite(przekaznik4, przelacznik4);
    delay(opoznienie);
    while (digitalRead(pstryczek4) == HIGH) {}
    delay(opoznienie);
  }  
  ArduinoCloud.update();
  digitalWrite(przekaznik1, przelacznik1); // Ustaw przekaźnik zgodnie ze stanem przełącznika.
  digitalWrite(przekaznik2, przelacznik2);
  digitalWrite(przekaznik3, przelacznik3);
  digitalWrite(przekaznik4, przelacznik4);
}

Na koniec już postanowiłem – raczej na potrzeby testów – użyć wbudowanej diody świecącej jako wskaźnika aktywności urządzenia. Po standardowym zadeklarowaniu samego portu diody oraz przełącznika, który będziemy negować, zaraz na początku głównej pętli osadziłem dwie nowe linie. W pierwszej za każdym razem przelacznikLed, czyli wskaźnik stanu diody będzie negowany. Zaraz potem jego wartość będzie wysyłana na port diody. Jaki to ma sens?

Podczas oczekiwania na wciśnięcie klawiatury bądź przyjście rozkazu przez WiFi, dioda wyraźnie mruga. Mruganie, a nie świecenie ciągłe świadczy o tym, że współpraca z chmurą jest dość powolna, na tyle, że częstotliwość przejścia przez całą pętlę wynosi kilkadziesiąt herców. Podczas wciskania klawiszy zmiana stanu diody przestaje mieć miejsce. Jeszcze ciekawiej dzieje się po włączeniu zasilania. Dioda nieregularnie zmienia stan, co świadczy o tym, że nawiązywanie współpracy z chmurą musi swoje trwać. Zatem po zadziałaniu watchdoga fakt ten będzie nam komunikowany nieregularnymi błyskami przez nawet kilkadziesiąt sekund. Tego typu, sprytne, choć nieoczywiste wskaźniki, pozwalają nieraz przekazać znacznie więcej informacji niż by się mogło wydawać, obserwując jedną, zwyczajną diodę.

Powiązane tematy

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2Sprawdź tutaj

Przeczytaj również

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2025 arduino.pl