piątek, 8 marca 2019

Obsługa pinów wirtualnych BLYNK w kodzie mikrokontrolera


Piny rzeczywiste znakomicie ułatwiają budowę prostych układów zdalnego sterowania w oparciu o Arduino i BLYNKa. Mają jednak sporo ograniczeń i w wielu miejscach utrudniają lub wręcz uniemożliwiają rozbudowę systemu o własne procedury czy zewnętrzne biblioteki. Programiści BLYNKa zaimplementowali więc ciekawy mechanizm przesyłania danych poprzez piny wirtualne. Na pierwszy rzut oka wyglądają jak klasyczne zmienne programowe. Ale tak naprawdę są indeksami (adresami) dużo bardziej złożonych procedur dostępnych w bibliotekach BLYNKa. Prawdziwa moc twórcza systemu zawarta jest właśnie w tych pinach i tych procedurach - trzeba im więc przyjrzeć się nieco dokładniej.


Piny wirtualne jak sama nazwa wskazuje to byty mocno nieokreślone służące do wymiany dowolnych danych dowolnego typu. Dane te mogą być przesyłane w relacjach aplikacja <> serwer lub/i serwer <> mikrokontroler. To lub/i jest ciekawe bo pokazuje, że pin wirtualny wysłany z aplikacji nie musi dotrzeć do urządzenia i vice versa. Ale zacznijmy od początku.
Pinów wirtualnych mamy w zasadzie 128. Są układy z mniejszymi zasobami pamięci, w których domyślne ustawienie liczby pinów wynosi 32. Jeśli potrzebujemy więcej należy dodać deklarację licząc się ze zmniejszeniem dostępnej  pamięci programu i danych.
#define BLYNK_USE_128_VPINS

Transmisja danych -  z programu do aplikacji

Podstawowym sposobem transmisji danych pinem wirtualnym jest wywołanie procedury
Blynk.virtualWrite(pin, dana);
Powoduje ona natychmiastowe wysłanie wirtualnego pinu z zawartością dana do serwera BLYNK.
Bardzo ważną cechą VP jest brak konieczności deklaracji typu przesyłanych nim zmniennych. Można więc do niego zapakować bit ,bajt, liczbę stało lub zmienno-przecinkową, string a nawet tablicę czy strukturę. Ze wszystkimi typami pin radzi sobie znakomicie gdyż ... zamienia je w string i w takiej postaci są one dalej przesyłane w systemie. Problem mógłby być po drugiej stronie kanału transmisji z interpretacją danych. Mógłby ale nie jest, bo po obu stronach czuwa TWÓRCA PROJEKTU znający doskonale co wpuszcza do kanału transmisji i wie co ma z niego wypaść.  Możemy bez obaw deklarować poniższe sposoby wysyłania danych:
Blynk.virtualWrite(V0, HIGH );
Blynk.virtualWrite(V0, "abc");
Blynk.virtualWrite(V0, 123);
Blynk.virtualWrite(V0, 12.34);
Blynk.virtualWrite(V0, "hello"12312.34);

Najciekawszy jest przypadek ostatni ale o tym później.
Ten sposób można nazwać "natychmiastowym" wysyłaniem danych dla odróżnienia innego - "cyklicznego". Jeśli w programie zdefiniujemy funkcję:
BLYNK_READ(V0) 
{
Blynk.virtualWrite(V0, dana); 
}
to dane będą wysyłane jedynie w przypadku żądania generowanego przez widget aplikacji. Częstotliwość generowania żądania jest ustawiana w parametrach widgetu.
Czym różnią się oba sposoby wysyłania danych z mikroprocesora. WSZYSTKIM. Pomimo identyczności użytej procedury Blynk.virualWrite() sposób i skutki wysyłki danych są diametralnie odmienne.
  • Wysyłka natychmiastowa rozpoczyna transmisję danych tak szybko jak tylko to może obsłużyć biblioteka BLYNK. Wysyłka cykliczna odbywa się jedynie w określonych przez widget interwałach
  • Sposób pierwszy jest inicjowany przez nasz program w mikrokontrolerze - dana jest wysyłana z programu. Drugi inicjuje wyłącznie widget - dana jest pobierana z programu
  • W pierwszym dana dociera do serwera gdzie jest zapamiętywana (i to z historią). Ewentualne wysłanie jej dalej do aplikacji zależy od innych czynników np. czy aplikacja jest aktywna, czy w aplikacji zadeklarowany jest widget z dowiązanym pinem wirtualnym przenoszącym tą wartość. Wysyłka cykliczna kieruje daną z układu bezpośrednio do aplikacji. Jeśli jeden z tych elementów systemu jest nieaktywny - nie ma transmisji cyklicznej. Ponadto serwer pełni w tym przypadku jedynie rolę biernego przekaźnika - przesyłana dana nie jest zapisywana na serwerze.
Jak widać cykliczne przesyłanie danych z mikrokontrolera do aplikacji wiąże się z pewnymi ograniczeniami. Po co więc wprowadzono ten sposób? To ślady z przeszłości rozwoju systemu  BLYNK. Ten  sposób funkcjonalnie jest identyczny z przesyłem danych moduł > widget dla pinu rzeczywistego. Pobieranie cykliczne jest tym samym ale dla pinu wirtualnego.
Moim zdaniem w zdecydowanej większości warto korzystać z natychmiastowego sposobu wysyłania danych. Należy jedynie pamiętać o ograniczeniu związanym z  częstotliwością wysyłki - nie powinno się to odbywać częściej niż 10/sek by nie blokować dostępu do serwera.
Czy więc cykliczne pobieranie danych przez widget jest bezużyteczne? Niekoniecznie. W sytuacji gdy zależy nam na mocnym ograniczeniu ruchu w sieci (systemy GSM) lub gdy zależy nam na danych jedynie gdy aplikacja w telefonie jest aktywna warto stosować ten sposób transmisji.

Piny wirtualne z predefinicją

Istnieje jeszcze jeden sposób dostarczania danych z programu do serwera (i dalej do widgetu). Nazwę go "natychmiastowym z deklaracją". W grę wchodzą tu klasy, konstruktory i inne tego typu programowe wynalazki więc nie będę się nad nimi rozwodził.
Bazując na przykładzie widgetu LED z poprzedniego posta najpierw definiujemy nasz obiekt led1 w klasie WidgetLED
WidgetLED led1(V0);
a potem już wywołujemy różne funkcje zdefiniowane w klasie w odniesieniu do naszego obiektu
led1.off();
led1.on();
led1.setValue(127);
Skutkiem wywołania tych funkcji jest wysyłanie danych z programu do widgetu za pomocą pinu wirtualnego V0.  Oba sposoby (natychmiastowy i natychmiastowy z deklaracją) są sobie równoważne. Większość widgetów posiada albo jeden albo drugi sposób obsługi wysyłania danych. Widget LED jest tu jednym z wyjątków - możemy używać obu jeśli chcemy (opisane w poprzednim wpisie). Wysyłanie z deklaracją będzie omawiana każdorazowo przy widgetach specjalnych posiadających niestandardowe funkcje sterujące jego zachowaniem (działaniem ).

Odbiór danych - z aplikacji do programu

Do odbioru danych zawartych w pinach wirtualnych mamy jeden rodzaj funkcji
BLYNK_WRITE(V0) 
{
  int pinData = param.asInt(); 
}
Funkcja ta pobiera wartość danej pinu wirtualnego zawsze z serwera. Jest to o tyle ważne, że wywołanie funkcji może nastąpić dwojako
  • funkcja jest wywoływana przez bibliotekę BLYNKa a sygnałem do jej wywołania jest zmiana stanu widgeta dowiązanego do danego pinu wirtualnego
  • funkcja wywołana jest "ręcznie" przez nasz program
W pierwszym przypadku wywołanie funkcji BLYNK_WRITE() informuje nas o zmianie w widgecie a więc o działaniu w obrębie telefonicznej aplikacji. Odczytując zmienną pinData dostaniemy nową aktualną wartości danej wysłanej przez widget a po drodze zapamiętanej na serwerze.
W drugim odczytujemy historycznią wartość danej zapamiętanej na serwerze. W tym czasie aplikacja w telefonie może nie być już aktywna.
Te dwa sposoby pobierania danej służą różnym celom w naszym programie. Pierwszy jest oczywisty - ma na celu obsługę danych wysyłanych przez widget. Przy czym przydatną informacją jest nie tylko nowa wartość widgeta zawarta w zmiennej pinData ale i sam fakt wywołania procedury BLYNK_WRITE(). Np. dołączenie przycisku do pinu V0 i wstawienie poniższej procedury w programie
int licznik_zmian = 0;
BLYNK_WRITE(V0) 
{
licznik_zmian++;
}
będzie nam zliczało ilość zmian wartości widgetu V0 (ilość naciśnięć i zwolnień wirtualnego przycisku).
Sposób drugi służy najczęściej do odtworzenia zapamiętanej wartości danego pinu wirtualnego po resecie mikrokontrolera lub po utracie połączenia z serwerem, w czasie którego wartość pinu mogła ulec zmianie. Możemy ręcznie wywoływać odtworzenie wartości każdego użytego w naszym programie pinu a możemy to zrobić blokiem dla wszystkich pinów znaną już procedurą odtwarzającą wartości wszystkich pinów rzeczywistych i wirtualnych:
Blynk.syncAll();

Przetwarzanie- konwersja - danych

W procedurze odczytu danej z pinu pojawia się konstrukcja
param.asInt()
Ma ona na celu odtworzenie typu danej jaka została pierwotnie umieszczona w pinie wirtualnym. Tak naprawdę jest to konwersja z danej typu string na typ integer odpowiednik funkcji toInt() dostępnej w Arduino. Twórcy BLYNKa zaopatrzyli nas w następujące rodzaje konwersji danych
int pinData = param.asInt();
String pinData = param.asStr() //czyli bez konwersji
float pinData = param.asFloat();
double pinData = param.asDouble();

Oczywiście możemy odtworzyć sobie inny typ danych  niż ten jaki był na wejściu jeśli taka konwersja typu jest nam do czegoś w programie potrzebna.
Możemy również pobrać "surowe" dane w takiej postaci w jakiej są one zapisane w buforze za pomocą
param.getBuffer()
param.getLength()

Dane wielowymiarowe

Ciekawą opcją jest możliwość przenoszenia przez wirtualny pin danych wielowymiarowych. Jest to potrzebne niektórym widgetom operujących więcej niż jedną daną (joystick, zeRGBa, GPS itp.) I choć twórcy BLYNKa pozostawili w tym przypadku możliwość wysyłania tych danych za pomocą kilku jednowymiarowych wielkości (kilkoma osobnymi pinami wirtualnymi) to opcja wielowymiarowa jest nad wyraz atrakcyjnym rozwiązaniem programistycznym.



Do odczytania danych wielowymiarowych stosujemy indeksowanie
  BLYNK_WRITE(V0)
  {
    int r = param[0].asInt(); // get a RED channel value
    int g = param[1].asInt(); // get a GREEN channel value
    int b = param[2].asInt(); // get a BLUE channel value
  }
Ten sam efekt dla danych jednowymiarowych uzyskamy przyporządkowując każdej zmiennej osobny pin wcześniej ustawiając wartość MERGE w opcjach widgetu
  BLYNK_WRITE(V0)
  {
    int r = param.asInt(); // get a RED channel value
  }
  BLYNK_WRITE(V1)
  {
    int g = param.asInt(); // get a GREEN channel value
  }
  BLYNK_WRITE(V2)
  {
    int b = param.asInt(); // get a BLUE channel value
  }
Do danych wielowymiarowych będziemy wracać niejednokrotnie wykorzystując siłę i możliwości tego prostego rozwiązania.

Przechowywanie wartości pinów wirtualnych na serwerze BLYNK

Popatrzmy na schemat obiegu pinu wirtualnego w systemie BLYNK



Część widgetów może zapisać daną do pinu wirtualnego na serwerze (wszystkie widgety typu sterującego). Większość widgetów może odczytać daną z pinu wirtualnego (wszystkie widgety typu sterującego i typu wyświetlacz). Program zawsze może zapisać i odczytć każdą daną z dowolnego pinu wirtualnego. Obie części (aplikacja-serwer i serwer-mikrokontroler) działają niezależnie od siebie to znaczy wyłączenie aplikacji nie blokuje operacji na pinach wirtulanych przez procesor i odwrotnie. Widget zapisuje nową wartość pinu każdorazowo po zmianie swojego stanu a odczytuje gdy dana pinu została zmieniona przez program. Program może zapisać i odczytać nową daną w dowolnym momencie. Jednocześnie jest "zmuszany" do odczytu nowej wartości po zmianie jej przez widget. Dane wpisywane przez program do pinu wirtualnego są dodatkowo składowane w pamięci serwera jako historia zmian.
Co wynika z powyższego. Dla aplikacji w telefonie serwer oprócz roli przekaźnika danych do i z mikrokontrolera stanowi pamięć wartości pinu niezbędną do odtworzenia właściwego stanu aplikacji przy kolejnym jej uruchomieniu.
Dla programu  serwer jest ,prócz roli przekaźnika danych, miejscem przechowywania bieżących i historycznych wartości danych pinu wirtualnego. Jeśli więc w programie umieścimy rozkaz
Blynk.virtualWrite(V0, "hello"12312.34);
to po resecie procesora możemy odtworzyć zapamiętane dane za pomocą
  BLYNK_WRITE(V0)
  {
    String a = param[0].Str(); // odtwarzamy "hello"
    int b = param[1].asInt(); // odtwarzamy 123
    float c = param[2].asFloat(); // odtwarzamy 12.34
  }
Serwerowi BLYNK jako zewnętrznej nieulotnej pamięci danych poświęcę osobny wpis. Również o tym jak korzystać z zapisanych na serwerze danych historycznych.
Zrozumienie funkcjonowania pinów wirtualnych BLYNKa to klucz do zrozumienia działania całego systemu. Ten wpis to dopiero początek tego ciekawego tematu.

18

Brak komentarzy:

Prześlij komentarz