logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia

p.kaczmarek2 18 Sie 2023 11:58 2646 12
REKLAMA
  • Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Przedstawię tutaj poprawki, uruchomienie i rozwinięcie biblioteki ArduinoHttpServer na platformie Arduino R4 WiFi. Celem tematu będzie ułatwienie nam operacji związanych z protokołem HTTP, a dokładniej parsowania żądań HTTP (przetworzenie nagłówka, ścieżki zasobu oraz argumentów GET) oraz wysyłania odpowiedzi na te żądania. Przy okazji też pokażę prosty system autoryzacji HTTP oferowanej przez tę bibliotekę.

    Temat zakłada, że użytkownik ma podstawową wiedzę i wie mniej więcej czym jest HTTP, czym jest żądanie GET, czym jest serwer, adres IP i tak dalej.

    Motywacja projektu
    Arduino R4 WiFi posiada nieco gotowych przykładów projektów sieciowych:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Ale po ich otwarciu, ku naszemu zaskoczeniu, okazuje się, że parsing HTTP realizowany jest w bardzo uproszczony sposób i nie jest upakowany w osobne klasy:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    W związku z tym rozpocząłem poszukiwania biblioteki, która mi to zaoferuje.
    Zainteresowała mnie praca QuickSander:
    https://github.com/QuickSander/ArduinoHttpServer
    Jednakże, jak szybko się okazało, jest z nią parę problemów:
    - nie kompiluje się pod Arduino R4 WiFi
    - przykłady są dość ubogie
    - nie wspiera parsingu argumentów GET, tj. linku w stylu:
    
    https://www.elektroda.pl/rtvforum/search.php?search_id=egosearch&user=1234
    

    W linku powyżej są zawarte dwa argumenty: jeden o kluczu "search_id" i wartości "egosearch", a drugi o kluczu "user" i wartości "1234". Warto by było móc je łatwo wyciągnąć z tego adresu....
    W tym temacie wszystkie te trzy kwestie postaram się naprawić.

    Instalacja i poprawki ArduinoHttpServer
    ArduinoHttpServer pobieramy z Libraries Manager, przy okazji też trzeba zainstalować jedną dependencję, a mianowicie Base64:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Potem można spróbować skompilować ich przykład z Githuba, ale się to nie powiedzie:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Otrzymamy taki błąd kompilacji:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Brakuje nagłówka pgmspace.h. Zaraz to naprawimy. Nie jest on w rzeczywistości potrzebny w tym przypadku.
    Otwieramy kod biblioteki, u mnie jest on pod:
    
    C:\Users\Admin\Documents\Arduino\libraries\Base64\src\Base64.cpp
    

    Wystarczy zakomentować ten blok (albo dodać define dla R4):
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Teraz przykład powinien działać, ale jeśli czasem otrzymujemy taki komunikat:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    To warto zajrzeć do:
    
    C:\Users\Admin\Documents\Arduino\libraries\ArduinoHttpServer\src\internals\StreamHttpRequest.hpp
    

    Problemem jest ten fragment kodu:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Po prostu wykrywa on błąd timeout. Początkowo chciałem zwiększyć wartość stałej MAX_RETRIES_WAIT_DATA_AVAILABLE:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    ale ostatecznie po prostu cały blok zakomentowałem. Od tego momentu całość zaczęła działać nieco stabilniej.
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Nie wiem, na ile jest to poprawne podejście ale po tej zmianie ta biblioteka działa u mnie nieco stabilniej.

    Przykład użycia od autorów ArduinoHttpServer
    Poświęćmy chwilkę na przetestowanie przykładowego kodu ArduinoHttpServer dostarczonego przez autora biblioteki. Ten kod oferuje prostą autoryzację HTTP opartą o mechanizm wbudowany w przeglądarkę:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Rzeczywiście, po wejściu na stronę mamy okienko z pytaniem o dane:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Po wpisaniu złych danych w logu otrzymujemy:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Natomiast po wpisaniu poprawnych:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Autoryzacja może i jest przydatna, ale o dziwo nie widzę nigdzie czegoś bardziej fundamentalnego, czyli możliwości wyłuskania argumentów z żądania GET. Zaraz się tym zajmiemy.

    Implementacja parsingu argumentów
    Krótka analiza kodu biblioteki pokazuje, że za parsing adresu zasobu odpowiada klasa HttpResource. Autor w tej klasie trzyma tylko jeden napis i na bieżąco w razie potrzeby go rozdziela:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Operator [] pozwala się dostać do konkretnych folderów w adresie zasobu:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Skoro już robimy w ten sposób, to dopisałem swoją metodę dla pobierania tekstowej wartości danego argumentu GET, który też wyszukujemy po nazwie:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Moja implementacja:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Implementacja najpierw szuka znaku zapytania, który oznacza początek argumentów, a potem wyszukuje nazwy klucza danego argumentu wraz z dodanym znakiem równości, by chociaż pomijać część potencjalnych błędów związanych z wyszukiwaniem nazwy która stanowi substring innej nazwy klucza dostępnego w stringu. Ten problem nie jest tu w pełni rozwiązany, ale założyłem, że i tak raczej będą używana jeden czy dwa klucze, więc zostawiłem tak jak jest.
    Czytelnik może ulepszyć ten kod we własnym zakresie.
    Napisałem dodatkowo przykładowy kod, który korzysta z nowej funkcjonalności:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Cała magia się dzieje we fragmencie ze zmienną textField . Kod sprawdza, czy jest argument GET o takiej nazwie i jeśli tak, to w HTMLu uwzględnia komunikat z wyświetleniem wartości textField . Jeśli nie, to wyświetlenie pomija. W obu przypadkach dalej tworzę formularz HTML z polem właśnie o takiej nazwie textField , by użytkownik mógł łatwo wysyłać zapytanie GET z nową wartością tej zmiennej.
    Po otwarciu strony widzimy:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Po wpisaniu tekstu "ArduinoFan" i wysłaniu:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Wygląda na to, że moja obsługa argumentów GET działa.

    Wskazówka - budowa ArduinoHttpServer i strumienie
    Teraz chciałbym jeszcze zwrócić na pewną przydatną cechę omawianej biblioteki wynikającej z obiektowości i dziedziczenia dostępnego w użytym środowisku.
    Rozważmy konstruktor klasy StreamHttpReply, która reprezentuje odpowiedź HTTP:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Pierwszym argumentem jest obiekt klasy Stream, czyli strumień. Po klasie Stream dziedzicą różne klasy, w tym strumień sieciowy. Użycie StreamHttpReply wygląda wtedy tak:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    To było w umieszczonym przykładzie. Ale strumieniem też jest obiekt Serial, czyli port szeregowy Arduino. Pozwala to nam na taką konstrukcję:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Może wydawać się to dziwne, ale nic nie stoi na przeszkodzie by używać protokołu HTTP na strumieniu UART, zamiast na strumieniu TCP. Rozważmy większy przykład:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Rezultat w konsoli:
    Arduino R4 WiFi i ArduinoHttpServer - poprawki, uruchomienie, przykłady użycia
    Tak jest, ten kod po prostu wydrukował wysłany HTML z doklejonym nagłówkiem HTTP. To bardzo ładnie pokazuję hermetyczną i modułową budowę tej biblioteki.

    Podsumowanie
    Całkiem fajna i przydatna ta biblioteka. Nie było dużych problemów z portowaniem jej na R4, co nie jest zaskoczeniem, gdyż jej mechanizmy opierają się o klasę Stream, która może stanowić wspólny interfejs dla wielu strumieni.
    Troszkę dziwne, że na ten moment pisania tego tematu ta biblioteka nie wspierała łatwego dostępu do argumentów GET, ale wystarczyło dopisać jedną funkcję by to zmienić.
    Jeszcze troszkę potestuję mój kod a potem otworzę pull request na Github by zaproponować moje zmiany i ulepszenia, być może autor biblioteki je przyjmie i doda do swojego kodu.
    To na razie tyle. Czy ktoś z czytelników tworzył już serwer HTTP na Arduino R4? Widzicie jakieś potencjalne zastosowania dla tej biblioteki?
    PS: Poniżej macie łatkę którą wysłałem do autora biblioteki: https://github.com/QuickSander/ArduinoHttpServer/pull/23

    Fajne? Ranking DIY
    Pomogłem? Kup mi kawę.
    O autorze
    p.kaczmarek2
    Moderator Smart Home
    Offline 
  • REKLAMA
  • #2 21098963
    ripnetru
    Poziom 4  
    Witam,

    Mam R4 Uno i przy kompilacji dostaję błąd

    Błąd kompilacji: 'const class ArduinoHttpServer::HttpResource' nie ma członka o nazwie 'getArgument'

    Dodano po 3 [minutach]:

    String ssid = httpRequest.getResource().getArgument("ssid");
    String pass = httpRequest.getResource().getArgument("pass");
    String ap = httpRequest.getResource().getArgument("ap");
  • REKLAMA
  • #3 21098991
    p.kaczmarek2
    Moderator Smart Home
    To dlatego, że musisz użyć mojej wersji biblioteki. Kod można zobaczyć tutaj:
    https://github.com/QuickSander/ArduinoHttpServer/pull/23/files
    Można go po prostu skopiować i wkleić. Daj mi znać, jeśli potrzebujesz dalszej pomocy.
    Pomogłem? Kup mi kawę.
  • REKLAMA
  • #4 21099044
    ripnetru
    Poziom 4  
    Dobra, teraz działa, też mam błąd 400 i naprawiam tak jak piszesz. Skonfigurowałem kredyty wifi i działa, po ponownym uruchomieniu
    beginSTA z
    SSID
    pass

    Jest ok, ale nie rozumiem, jak działające łącze powraca do trybu AP, jeśli już umieściłem wifi ssid i hasło, jak mogę przywrócić domyślne bez Arduino IDE. Próbują się połączyć i tryb AP nie działa, jak widzę. Dziękuję.

    Dodano po 7 [minutach]:

    Widzę nowe ip i jest ok, przepraszam.

    [WiFiManager]: starting AP

    Dodano po 6 [minutach]:

    Dziękuję bardzo. Bardzo dobrze.
  • REKLAMA
  • #5 21100974
    ripnetru
    Poziom 4  
    Proszę o pomoc, jeśli to w ogóle możliwe.

    W formularzu mamy ssid i pass, i chcę, aby jeśli ssid i pass zostały zapisane, aby wyświetlić je w formularzu internetowym w polu wprowadzania.

    " <label for=\"ssid\">Enter SSID:</label><br>\n"
    " <input type=\"text\" id=\"ssid\" name=\"ssid\"><br><br>\n"

    Jak dodać set->inner.sta_ssid w wartości wejściowej. Szukałem przykładów, ale nie znalazłem jak to zrobić.
  • #6 21101023
    p.kaczmarek2
    Moderator Smart Home
    Czy możesz podać pełny kod, nad którym obecnie pracujesz?

    Jeśli odnosisz się do generowania strony HTML, to możesz zrobić coś takiego:
    Kod: HTML, XML
    Zaloguj się, aby zobaczyć kod

    Zasadniczo wystarczy dodać pole "wartość" do znacznika wejściowego.
    Pomogłem? Kup mi kawę.
  • #7 21101041
    ripnetru
    Poziom 4  
    Nie mam kodu, pracuję z oryginalnym kodem https://www.elektroda.com/rtvforum/topic4000214.html

    chcę coś takiego:

    " <label for=\"ssid\">Enter SSID:</label><br>\n"
    " <input type=\"text\" id=\"ssid\" name=\"ssid\" value=\"123\"><br><br>\n"

    ale 123 zmienić na prawdziwą wartość z eepromu. Jak poprawnie połączyć kod С++ z html.
  • #8 21101095
    p.kaczmarek2
    Moderator Smart Home
    Rozumiem, więc to jest mój stary fragment kodu:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Możemy to zmodyfikować, najpierw musimy uzyskać stary identyfikator SSID, jako wskaźnik ciągu C:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
    .
    Następnie podłączamy go do HTML:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Należy pamiętać, że jest to tylko przykład i może wymagać dalszego dostrajania, nie skompilowałem go po mojej stronie. Zasadniczo wystarczy uzyskać dostęp do starego ciągu SSID i podłączyć go do nowego HTML, do wspomnianego już pola wartości.
    Pomogłem? Kup mi kawę.
  • #9 21101142
    ripnetru
    Poziom 4  
    Naprawdę to doceniam.

    Właśnie sprawdziłem, działa tak, jak chciałem. Dziękuję!
  • #10 21101373
    p.kaczmarek2
    Moderator Smart Home
    Jasne, daj mi znać, jeśli potrzebujesz dalszej pomocy
    Pomogłem? Kup mi kawę.
  • #11 21105141
    ripnetru
    Poziom 4  
    Witam,

    Tak, mam jeszcze jedno pytanie, podczas pracy w trybie STA, czasami traci połączenie, może to zakłócenia (2,4 GHz)

    (00:43:00.872 -> Przechodzę do odczytu ustawień....
    00:43:00.872 -> Ustawienia poprawnie załadowane!
    00:43:00.872 -> [WiFiManager]: uruchamianie STA
    00:43:00.872 -> beginSTA z
    00:43:00.872 -> SSID
    00:43:03.168 -> Adres IP: 192.168.222.229
    00:43:03.216 -> siła sygnału (RSSI):-48 dBm

    Jak rozumiem void beginSTA() używa tylko raz.
    I po utracie połączenia z routerem nie próbuje połączyć się ponownie. Jak dodać taką opcję?
  • #12 21106192
    ripnetru
    Poziom 4  
    Próbuję zrobić tak:

    void loop() {
    int status = WL_IDLE_STATUS;
    while (status != WL_CONNECTED) {
    Serial.print("Próba połączenia z SSID: ");
    Serial.println("Pixel");
    status = WiFi.begin("Pixel", "pass");
    }

    To działa, ale statycznie, jeśli użyję
    status = WiFi.begin(set->inner.sta_ssid, set->inner.sta_pass);
    podczas kompilacji kodu wyskakuje błąd. void loop nie wiem o inner.sta_pass i inner.sta_ssid

    Próbuję dodać

    const char *oldSsid = set->inner.ap_ssid;
    const char *oldPass = set->inner.ap_pass;

    ale wyskakuje błąd przy set....
  • #13 21115631
    ripnetru
    Poziom 4  
    Zrozumiałem to, nie wiedziałem, że zmienne mogą być wyższe niż pętla i konfiguracja i zrobiłem je podobnie do twojego przykładu

    #include <ArduinoHttpServer.h>

    const char *ssidglobal;
    const char *passglobal;

    ..
    in class
    ..
    ssidglobal = set->inner.sta_ssid;
    passglobal = set->inner.sta_pass;
    ...
    A teraz działa w pętli, jak chcę.
    ...
    status = WiFi.begin(ssidglobal, passglobal);
    ...

    Po prostu eksperymentuję, ponieważ naprawdę to lubię. Przepraszam, jeśli cię rozproszyłem.
REKLAMA