Obsługa pinów wirtualnych BLYNK w kodzie mikrokontrolera

By: | Post date: Listopad 1, 2017

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

4 komentarze

  • Wiktol napisał(a):

    Dziękuję bardzo że zainteresowałeś się tematem. Na razie wszystko testuję na serwerze publicznym Chodź dzięki twoim artykułom udało mi się uruchomić serwer lokalny. W weekend kolejne próby. Jak coś wymyślę dam znać.
    Pozdrawiam. Tosiek

  • Wiktol napisał(a):

    Witam. Na początek gratulacje i wielkie DZIĘKI za świetne opracowania. A teraz prośba. Czy możesz przybliżyć mi działanie historii pinu przechowywanej na serwerze? A zwłaszcza chodzi mi o pobierany plik csv. Jakimi regułami kieruje się serwer? ile danych zapisze? czy mam wpływ na te dane np kasowanie itp? . Od jakiegoś czasu zapoznaje się z Blynkiem. Ucieszyłem się bardzo kiedy przeczytałem o historii pinu. To coś czego szukam. Niestety pierwsze próby nie są zachwycające. Odbieram to tak, że server zapisuje, owszem ale kiedy chce i kończy kiedy chce. Utworzyłem najprostszy przykład push: blink.virtualWrite(V4,dana) dana to oczywiście millis/1000. Po godzinie dołożyłem wszystko w timerze 1s. Następnie : http://blynk-cloud.com/e933e7ccdfdee4f7f8f6b0755295e8efa/data/V5.
    plik scv zawiera 82 rekordy. ostatni nawet w czasie zbliżony do czasu odczytu: 4775;28-05-18 16:23. Pin V4 zapisał 16 rekordów. Sprawdzę jeszcze dokładnie czasy w momencie odczytu, ale muszę użyć nowych pinów, bo odczyt V5, V4 pobiera stale te same dane z godz 16:40 mimo, że układ cały czas pracuje. I ważniejsza część pytania: układ pracuje a pobierane dane są stare? dlaczego? wcześniej robiłem próby z V10. Pobierany plik zawierał dane z ubiegłego tygodnia mimo, że układ z nadawaniem na V10 uruchamiany był wielokrotnie.
    Mam nadzieje, że nie przysnąłeś przy tym przynudzaniu 🙂 będę bardzo wdzięczny za każdą pomoc w temacie.
    Pozdrawiam baardzo serdecznie
    Tosiek
    p.s. gdzie mogę spodziewać się odpowiedzi? (sq9pcc@gmail.com)

    • Krzycho napisał(a):

      Sprawa jest złożona i nie do końca jasna. Pytanie pierwsze serwer prywatny czy publiczny? Teoretycznie serwer zapisuje historię dadnych wysyłanych komendą blink.virtualWrite . Ale na publicznym serwerze dane do zapamiętania można wysyałać nie częściej niż 1/min. Jeśli wysyłasz częściej dane są w jakiś sposób kompresowane /uśrednianie – nie pytaj jak bo nie wiem dokładnie jaki jest algorytm.
      aby odtworzyć dane po resecie mikrokontrolera trzeba wywołać Blynk.syncVirtual
      Co do kasowania danych to też nie jest jasne. teoretycznie dane są przechowywane zawsze (o ile wiem jest jakieś ograniczenie czasowe 3 miesiące albo cóś). ale z drugiej strony jest informacja ze dane są kasowane jeśli skasuje się widget (np superchart) powiązany z tymi danymi
      To jest wiedza teoretyczna :). Dopiero będę rozpracowywał ten temat przy okazji licznika energii i wskaźnika mocy pobieranej przez urządzenia w domu. Ale to pojawi na blogu 100-x-arduino.blogspot.com za jakiś czas.
      Jeśli dowiesz się wcześniej jak to działa -opisz to proszę bo to ciekawy temat. Ja działam na serwerze lokalnym BLYNK i mogę zdjąć wszystkie ograniczenia co do przechowywanych danych.
      Jeśli masz szczegółowe pytania pisz na krzychopp(malpa)gmail.com

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Translate »