Wprowadzenie do zaawansowanego programowania stron internetowych

Dokumentacja w języku angielskim jest dostępna tutaj 

W prostych przypadkach wystarczą pliki html,javascript generowane przez kreatory, o których więcej jest tutaj i tutaj. W scenariuszach bardziej zaawansowanych proponuje użyć bibliotek, który szybki start opisze w tym artykule. Kody źródłowe znajdują się  tutaj

W tych przykładach pokazuję komunikację na przykładzie stron internetowych (otwartych w dwóch różnych zakładkach przeglądarki, w identyczny sposób można przesyłać dane do urządzeń typu ESP i RPi).

Prze lekturą tego artykułu koniecznie proszę się zapoznać z artykułem o ogólnie o internetowych w RemoteMe

Jak to działa

Strony są hostowane w chmurze RemoteMe maksymalny rozmiar pliku to 200KB, co powinno wystarczyć do celów, w jakim RemoteMe został stworzony, jednak gdy potrzebujesz większej przestrzeni — proszę o kontakt.

W stronach internetowych do komunikacji z systemem RemoteMe należy użyć bibliotek. Biblioteki w łatwy sposób umożliwiają komunikacje z samym systemem. Ogólnie cała komunikacja z system odbywa się przez :

  • połączenie websocketowe — większość komunikacji opiera się na przesyłaniu binarnych wiadomości websocketowych w specjalnym formacie wiadomości. Format wiadomości do podglądnięcia w źródłach
  • REST Api funkcje restowe dostępne są tutaj 

W większości przypadków nawet do zaawansowanych scenariuszy wystarczą biblioteki javascriptowe których pełny kod źródłowy znajduję się tutaj. Znacząco ułatwiają one implementacje rozwiązań i z tych bibliotek będę korzystał w tym artykule.

( Do scenariuszy najbardziej zaawansowanych typu własny desktopowy interfejs zarządzający (czy aplikacja desktopowa, mobilna) konieczne jest implementacja wysyłania wiadomości i korzystania z REST api )

Strona internetowa od zera

zabezpieczenie

Żeby użytkownik mógł pobrać strony internetowe musi być zalogowany (jedną z trzech metod – nazwa użytkownika i hasło, token (używany np w QR kodach), pobrać specjalny token na podstawie nazwy i hasha hasła użytkownika). W innym przypadku podczas próby pobrania plików zostanie zwrócony kod błędu 401

import plików

Import bibliotek, styli — najprościej jest je po prostu skopiować z nowo utworzonej strony internetowej tworzonej z domyślnych ustawień i domyślnego szablonu. Oprócz bibliotek takich jak jquery, material design lite, dodatkowych komponentów znajdują się:

  • remoteMe.js – klasa RemoteMe – połączenie z RemoteMe i zapewnia wysyłanie i odbieranie wiadomości ( również wiadomości typu ping)
  • remoteMeMessages.js – funkcje, klasy które umożliwiają w łatwy sposób budowanie wiadomości do wysłania i parsowanie odebranych wiadomości
  • variables.js – funkcje ułatwiające komunikacje na podstawie zmiennych

Importuje pliki css i javascriptowe użytkownika te, które znajdują się po rozwinięciu belki strony internetowej — można dodawać nowe pliki javascript, css i importować je w podobny sposób.

Stałe w plikach

W plikach można korzystać ze stałych, które zostaną zamienione, gdy strona będzie serwowana do użytkwnika są to (wielkość liter nie ma znaczenia) :

  • ####deviceId# – zamieniony na liczbę — deviceId strony internetowej
  • ####username# – userName użytkownika
  • ####raspberrypideviceid# – deviceId pierwszego znalezionego RasbperryPi – gdy w systemie nie ma RasbperryPi – 0
  • ####arduinodeviceid# – deviceId pierwszego znalezionego urządzenia typu Arduino – gdy w systemie nie ma takiego urządzenia – 0

Najprostsza strona internetowa

Strona jedynie łączy się do systemu RemoteMe i utrzymuje połączenie. Dodatkowo będzie wypisywała na konsole zmiany stanu połączenia. ( w celu zachowania maksymalnej prostoty kod javascriptu jest również w kodzie html)

Skrypt ma za zadanie łączyć się z RemoteMe i rozłączać po wciśnięciu przycisków.

var thisDeviceId=####deviceId#;   id naszego urządzenia – jest używany przez biblioteki i musi zostań przypisany do zmiennej globalnej thisDeviceId.

new RemoteMe({automaticlyConnectWS:false,webSocketConnectionChange:[onWebSocketChange]}); stworzenie obiektu klasy RemoteMe, w konfiguracji (dostępna w źródłach) informujemy, że nie chcemy łączyć się odrazu do systemu RemoteMe, oraz że za każdym razem, gdy zmieni się status połączenia chcemy wywołać funkcję onWebSocketChange:

Funkcja onWebSocketChange otrzyma jako parametr aktualny status połączenia webSocketowego. status jest typu number a ConectinStatusEnum to typ wyliczeniowy do łatwiejszego rozpoznawania stanu połączenia.

RemoteMe.getInstance() ponieważ nie można stworzyć drugiego obiektu klasy RemoteMe najłatwiej odwołać się do remoteMe własńie przy użyciu funkcji getInstance gdy obiekt nie był wcześniej utworzony zostanie on utworzony z domyślnymi ustawieniami.

Strona w menadżerze urządzeń

  1. pojedyńczy plik html
  2. status połączenia ze stroną

Po otwarciu strony internetowej (index.html-> open in new Tab) otrzymamy:

Po kliknięciu connect strona internetowa połączy się z systemem, Status połączenia (2) zmieni się na połączony, a w logach konsoli otrzymamy stosowną informację.

Modyfikacja i odczyt stanu zmiennych

przypomnienie: zmienne -mają typ i wartość, są obserwowane przez urządzenia, zmiana na jednym urządzeniu jest propagowana do innych nasłuchujących urządzeń. Więcej o zmiennych tutaj

Żeby przetestować działanie proponuje otworzyć stronę w kilku różnych zakładkach:

Zmiana wartości zmiennej na jednej zakładce powoduje zmianę wartości zmiennej an pozostałych zakładkach. Aktualny stan zmiennej pokazuje się też w zakładce “Variables” w systemie, gdzie jest też możliwość modyfikowania wartości zmiennej.

Omówienie kodu:
ta funkcja jest odpowiedzialna za wysłanie wartości zmiennej, po rostu zmiennej o nazwie “someName” o typie Integer ustawiamy wartkość podaną jako parametr w funkcji. Zmiana ta zostanie rozpropagowana do wszystkich urządzeń nasłuchujących na zmienna, a także do tej strony. Ponieważ:
W wywołaniu powyżej poinformowaliśmy remoteMe, że jesteśmy zainteresowani otrzymywaniem informacji, o zmianie wartości zmiennej. Po otrzymaniu nowej wartości zmiennej ustawiamy text diva na wartość zmiennej (jquery).

W pliku: variables.js  Znajdują się odpowiedniki funkcji observeInteger i setIntger dla innych typów. Oczywiście jak nasza zmienna jest zmieniana przez inne urządzenie niż strona internetowa, i inne urządzenie nasłuchuje na zmianę wartości zmiennej, postępujemy identycznie.

 

Wysyłanie wiadomości

O ile zmienne mają określony typ i są wysyłane do wszystkich urządzeń nasłuchujących, w niektórych przypadkach przydaje się wysłanie wiadomości o dowolnym typie do określonego urządzenia. W RemoteMe odbywa się to przez wysyłanie wiadomości. Wiadomości mają postać tablicy bajtów, w bibliotekach RemoteMe istnieją funkcje do łatwego operowania na takiej tablicy. System wspiera dwa rodzaje wiadomości:

  • asynchroniczne – wysyłamy wiadomość do konkretnego urządzenia i nie oczekujemy na odpowiedź – np wiadomość zaświeć światło
  • synchroniczne – wiadomości przy których czekamy na odpowiedź np – jaka jest temperatura w pokoju

( napisane przykłady jak zaświeć światło, czy odczytaj temperaturę w większości przypadków powinny być zaimplementowane przy użyciu zmiennych jednak chciałem, żeby przekaz był jasny)

Wysyłanie wiadomości asynchronicznej

Zaczniemy od wysłania wiadomości asynchronicznej. Będziemy potrzebowali dwóch urządzeń WWW, jedno o nazwie sender o dowolnym Id- i drugi o Id 1 – nazwa receiver , Ważne jest w tym pyrzpadku deviceId, bo na ten deviceId będziemy wysyłali wiadomość se strony “sender”. Oczywiście strona “receiver” po wywołaniu funkcji wysyłającej też może wiadomości wysyłać, ale w tym przypadku nie będziemy tego robić, żeby nie zaciemniać obrazu.

index.html nadajnika “sender”:

Strona wysyła liczby do odbiornika (urządzenia receiver) (liczby Fibonacciego, pierwsze, i duże liczby gdzie ostatnia jest za duża do wysłania) . Gdy przesyłamy wiadomości jest ona dostarczana jako tablica bajtów, musimy zatem wiedzieć jak ją zdekodować / zakodować.

W pierwszej kolejności tworze buffor wiadomości :  let data= new RemoteMeData(2+toSend.length*4); klasa RemoteMeData posiada metody do łatwego zapisywania zmiennych jak putShort, putString (więcej w źródłach remoteMeMessages.js ). W czasie tworzenia buforu konieczne jest podanie jego rozmiaru. rozmiar zależy od samego formatu wiadomości :

W moim przypadku w tablicy bajtów jako pierwsze dwa bajty (jako bez znakowy 16bitowy int) przesyłam ilość liczb w wiadomści: data.putUint16(toSend.length); (2 bajty) następnie do buforu zapisuje po kolei każdą liczbę, każda liczba ma rozmiar 4 bajtów, stąd też rozmiar buforu. Następnie funkcją  RemoteMe.getInstance().sendUserMessage(1,data.getArray()); wysyłam dane do urządzenia o deviceId=1

index.html urządzenia receiver (deviceId=1):

w konstruktorze new RemoteMe({onUserMessage:onMessage}); podaję jaką funkcję biblioteka ma wywołać, gdy przyjdzie wiadomość. Funkcja odbierająca jako argument pierwszy będzie miała id urządzenia nadawczego, a jako drugi parametr tablice bajtów (jako obiekt typu DataView) w funkcji onMessageopakowuje obiekt DataView obiektem typu RemoteMeData żeby było łatwiej odczytywać liczby, następnie pobieram pierwszą liczbę (2 bajtowy int bezznakowy) a następnie pobieram liczby w ilości size. Na koniec wypisuje liczby.
Oba urządzenia w menadżerze urządzeń powinny wyglądać następująco:

po uruchomieniu obu urządzeń w osobnych zakładkach w konsoli receivera będziemy mieli wypisane liczby wysłane przez urządzenie “sender”, za każdym wciśnięciem przycisku sender’a. W identyczny sposób odbieramy i wysyłamy wiadomości do innych urządzeń (ESP, RPi, aplikacja na androida etc)

 

Wysyłanie wiadomości synchronicznej

Czyli wysyłamy wiadomość i oczekujemy na odpowiedź – przydatne gdy chcemy “zadać pytanie naszemu ESP”. W przykładzie stworzymy dwie strony internetowe, jedna będzie pytać o sumę z dodawanie dwóch liczb, druga zrobi obliczenia i odpowie.

sender (deviceId obojętne) index.html

Poprostu funkcją :

do urządzenia o id=1 wysyłamy zapytanie. W treści wiadomości znajdują się dwie zmienny typu Float64, po otrzymaniu odpowiedzi uruchamiana jest funkcja podana jako trzeci argument, której parametrem jest odpowiedź jako tablica bajtów, która dekodujemy i wyświetlamy.

Receiver (deviceId:1)

w konstruktorze remoteMe podajemy jaka funkcja ma zostać odpalona, gdy nadejdzie wiadomość asynchroniczna, następnie we wskazanej funkcji dekodujemy wiadomość i zwracamy tablice bajtów zawierającą jedną liczbę typu Float64. Po uruchomieniu stron w dwóch różnych zakładkach przeglądarki, po kliknięciu przycisków otrzymamy wynik z dodawania zwrócony przez stronę otwartą w drugiej zakładce.

W przyszłości umieszczę obsługę połączeń webRTC oraz streaming wideo;