Przeszukaj forum
Pokazywanie wyników dla tagów 'esp32'.
Znaleziono 88 wyników
-
Ten artykuł jest częścią serii "Tworzenie interfejsu sieciowego z wykorzystaniem ESP" #1 - część 1 (właśnie to czytasz) #2 - część 2 ESP32 czy też ESP8266 na dobre już zagościło w wielu warsztatach domowych majsterkowiczów. Większość obecnych projektów z wykorzystaniem ESP skupia się wokół dorzucenia do niego garści czujników, podłączenia do baterii i wybudzania go od czasu do czasu, aby wysłać dane o wykonanych pomiarach do naszego serwera. Czasem zdarza się, że nasze urządzenie pobiera pewne dane z zewnątrz i je wykorzystuje, np. budzik czas z serwera NTP, czy stacja pogodowa, informacje o pogodzie z wybranego serwisu. Co w sytuacji kiedy chcemy kontrolować nasze urządzenie lub obserwować jego stan z poziomu przeglądarki, a nie posiadamy Raspberry Pi, czy innej opcji, na której moglibyśmy mieć własny serwer? Co jeżeli zastosowanie dodatkowego serwera jest po prostu nieadekwatne do naszego celu? W tym artykule postaram się: omówić najpopularniejsze rozwiązania pokazać jak uruchomić serwer www ESP32 stworzyć prostą stronę www do naszych zadań wykonać interakcje strona-ESP w postaci: kontroli portu GPIO wyświetlanie wyniku pomiaru z ADC pobieranie pliku z pamięci ESP/karty SD Ten artykuł bierze udział w naszym konkursie! Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » Wszystkie powyższe rzeczy postaram się zobrazować w jak najprostszy i przejrzysty sposób. Poruszany temat jest niewątpliwie bardzo złożony i niestety nie jest możliwe aby wszystkie informacje zawrzeć w jednym artykule. Temat wymaga zarówno znajomość obsługi samego ESP, HTML, JavaScriptu czy też CSS, zaś znajomość protokołów sieciowych również byłaby mile widziana. Tutaj będą jedynie ukazane podstawy jak to wszystko ze sobą połączyć. Pokazane metody z pewnością nie będą należeć do najbardziej optymalnych rozwiązań, mają jedynie na celu ukazanie koncepcji i zachęcenia do dalszej analizy tego zagadnienia. Wszystkie kody będą skomentowane. W treści będę również odsyłał do dodatkowych materiałów, które dokładniej opisują poszczególne zagadnienia oraz tam gdzie można zdobyć więcej wartościowych informacji. Ale w jakim celu? Część z osób może zadać pytanie po co uruchamiać serwer na ESP, wiąże się to z dużym poborem energii, pomiary najlepiej z wielu czujników wysyłać w jedno miejsce, to dużo pracy itd. Inni zaś, od razu stwierdzą, że to jest to czego oni potrzebują. Jako że nie widzę większego sensu pisania długich wywodów na temat dlaczego warto, dlaczego nie, kiedy tak, kiedy nie. Przedstawię poniżej dwa praktyczne przykłady i możliwości takich realizacji które pozwolą samemu ocenić te aspekty. Pierwszym przykładem jest zdalny interfejs drukarki 3D. Dzięki niemu możemy zdalnie uruchomić drukarkę, wysyłać do niej pliki, uruchamiać druk, obserwować parametry druku, dostosowywać je, konfigurować drukarkę i wiele innych. Zostało to zrealizowane na ESP8266 i projekt jest dostępny pod tymi linkami Duet WiFi Server oraz Duet Web Control Drugi przykład jest to interfejs do sterowania lampką/oświetleniem LED. Z poziomu przeglądarki możemy ustawiać różne efekty świetlne, barwę, jasność, konfigurować urządzenie. Więcej o tym projekcie można dowiedzieć się tutaj Aircookie WLED Co będzie nam potrzebne? Podstawowa znajomość platformy ESP oraz programowania w Arduino w tym obsługa SPIFFS lub kart SD Płytka z ESP32 (wszystko powinno być kompatybilne z ESP8266) Zainstalowana biblioteka Async Web Server Dodatkowo: Znajomość języka angielskiego – dodatkowe odnośniki Płytka stykowa, potencjometr, fotorezystor czy cokolwiek sobie wymyślicie aby urozmaicić sobie temat Zrozumienie tematu również ułatwi znajomość podstaw HTML oraz JavaScriptu. Jako że wymagane są już podstawowe umiejętności odnośnie obsługi ESP oraz Arduino, pominę kwestie instalacji biblioteki, omówienia zagadnień struktury programu czy też obsługi peryferiów. Z czym to się je? Podstawowa koncepcja naszego projektu opiera się na tym, iż na ESP uruchamiamy serwer, który na zapytanie klienta (klient czyli nas - naszej przeglądarki) zwraca odpowiednie pliki lub wykonuje pewne operacje. W ten sposób możemy poprosić ESP aby zwrócił nam plik HTML zawierający naszą stronę, przeglądarka ją odbierze, a my będziemy się mogli cieszyć widokiem naszej witryny. W ten sposób możemy wyróżnić pierwszy ze sposobów interakcji z naszym ESP, czyli z wykorzystaniem metod HTTP. W uproszczeniu, metody są to pewnego rodzaju „komunikaty” czego oczekujemy od naszego serwera. Przykładowo, wysyłamy zapytanie „GET” – oznacza że chcemy coś od serwera i ma on nam to dać, zapytanie „POST” – oznacza że chcemy coś dać od siebie. Każde nasze zapytanie będzie skutkować odpowiedzią (lub jej brakiem ). Odpowiedzi posiadają swoje kody, które mają różne znaczenie – to daje nam dodatkowe możliwości interakcji. Wiedząc co oznacza dany kod możemy przykładowo stwierdzać czy dostaliśmy odpowiedź, czy wyświetlić jakiś błąd, lub stwierdzić że coś nie istnieje (każdemu znane 404). Najprostszym użyciem tych zapytań jest po prostu wykorzystanie odpowiednich struktur w HTML z stosownymi atrybutami. Metodę „POST” możemy wykorzystać przy tworzeniu formularza. Wadą tego rozwiązania jest fakt tego iż będzie to skutkować przeładowaniem strony przy każdej tego typu akcji. Inną opcją jest wykorzystanie pomocy Java Scriptu który będzie służył jako nasza „trzecia ręka” wykonująca te operacje w tle. Rozwiązanie to nazywa się AJAX (z angielskiego Asynchronus JavaScript and XML) i na nim się głównie skupimy w tym artykule. Drugą powszechną opcją jest skorzystanie z WebSocket. Jest to sposób ciągłej komunikacji między klientem a serwerem. Polega ona na nawiązaniu „kontaktu” z serwerem i zapytaniem go czy jest chętny na „pogawędkę”. Metoda ta idealnie się nadaje do wymiany ciągów informacji na żywo. Przykładowo potrzebujemy ciągłego odczytu z przetwornika ADC – można stwierdzić „wirtualny port szeregowy”. Oczywiście moglibyśmy zrealizować to samo zadanie z wykorzystaniem wcześniej wspomnianych metod, ale wykorzystanie metody HTTP wiąże się z całym procesem, wysłania zapytania, otrzymania odpowiedzi, co w skali procesora trwa wieki (np. jedno zapytanie kilkadziesiąt – set ms). Tutaj nie mamy tego problemu, gdyż nasze połączenie ciągle trwa i sobie rozmawiamy. W przypadku gdy nie zależy nam na ciągłym podglądzie (np. odświeżanie informacji raz na pół minuty) możemy spokojnie zadowolić się wykorzystaniem AJAX i metod HTTP. Ponadto warto nadmienić iż korzystanie z WebSocketów jest zarówno korzystne dla serwera jak i klienta ze względu na minimalną ilość przesyłanych danych (ograniczenie tego co jest nam w rzeczywistości zbędne). No to zaczynamy! Na wstępie warto nadmienić że pracujemy wewnątrz sieci lokalnej. Jeżeli połączymy się z naszym WiFi, inne urządzenia z tej samej sieci będą miały dostęp do naszego serwera. Bez stosownej konfiguracji sieci (jak i czasem ograniczeń narzuconych przez naszego dostawcę internetowego) nie będziemy mieć dostępu do naszego urządzenia z dowolnego miejsca na świecie. Na początek zacznijmy od tego czym jest nasza biblioteka i dlaczego ona. Otóż umożliwia ona komunikację asynchroniczną, co pozwala nam na posiadanie więcej niż jednego połączenia w danej chwili i działa poza pętlą loop(). Aby się nie rozpisywać na temat innych zalet i możliwości zainteresowanych dogłębną analizą odeślę tutaj. Uwaga dla użytkowników ESP8266! Biblioteka od obsługi WiFi definiuje się jako: #include <ESP8266WiFi.h> Zaś obsługa SPIFFS: #include <FS.h> Ponadto w poniższej pętli while() potrzebne jest opóźnienie, aby zapobiec uruchamianiu się watchdoga while (WiFi.status() != WL_CONNECTED){ delay(1000); } Powyższe uwagi będą zawarte w komentarzach kodów. Uruchamiamy serwer! #include <Arduino.h> #include <WiFi.h> //ESP8266 //#include <ESP8266WiFi.h> #include <SPIFFS.h> //ESP8266 //#include <FS.h> #include <ESPAsyncWebServer.h> #define SSID "nazwa sieci" #define PASS "hasło sieci" AsyncWebServer serwer(80); //utwórzmy obiekt serwera na porcie 80 void setup() { Serial.begin(115200); //zainicjujmy port szeregowy WiFi.begin(SSID, PASS); //połącz z naszą siecią wifi while (WiFi.status() != WL_CONNECTED){ //poczekajmy aż ESP połączy się z naszą seicią //delay(1000); //dla ESP8266 } Serial.printf("\nAdres IP:"); Serial.println(WiFi.localIP()); //wypisz adres IP naszego ESP przez port szeregowy //tutaj odbywa sie obsługa zapytań serwer.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/" typu GET, request->send_P(200, "text/plain", "Witaj! :)"); //odpowiedz mu kodem 200, danymi tekstowymi, o treści "Witaj! :)" }); serwer.begin(); //zainicjujmy nasz serwer } void loop() { } W powyższym kodzie widzimy następujące etapy, łączymy się z naszą siecią WiFi, ESP zwraca nam przez port szeregowy swój adres IP w naszej sieci. Będzie on nam potrzebny do wpisania w pasku przeglądarki w celu połączenia się z serwerem. Następnie tworzymy funkcję która obsługuje konkretne zapytania, w naszym przypadku po otrzymaniu zapytania GET pod adresem „/” – można to określić jako „folder główny” serwera, tak samo jak w komputerze mamy dysk np. „D:\” – odeśle klientowi odpowiedź o kodzie 200 (oznacza to „ok” – więcej o kodach tutaj) i zawartości typu tekstowej (są to typy MIME, mówią one przeglądarce co jej chcemy przekazać – więcej o typach MIME tutaj). Rezultatem, po wpisaniu w pasek przeglądarki adresu IP naszego ESP, jest strona. Tworzymy prostą stronę Jako że celem tutaj nie jest nauka HTML czy też CSS, ograniczyłem stronę do absolutnego minimum, potrzebnego do naszych zabaw. Tutaj też, odeślę do wartościowego źródła gdzie można znaleźć wiele wartościowych informacji odnośnie HTML, JavaScript, CSS oraz innych. Nasza strona będzie się składać z pola tekstowego gdzie wyświetlimy wartość odczytaną z ADC, dwóch przycisków do włączania i wyłączania diody oraz przycisku pobierania pliku z naszego ESP. <!DOCTYPE html> <html> <head> <title>Strona</title> <meta charset="UTF-8"/> </head> <body> <p id="pomiar">Wartość:</p> <button id="on">Włącz</button> <button id="off">Wyłącz</button><br> <button id="download">Pobierz obrazek</button> <script> </script> </body> </html> Kluczowe podczas tworzenia takiej strony jest nadawanie unikalnego ID każdemu elementowi, ułatwi to współpracę z JavaScriptem. Gdy już mamy przygotowaną stronę musimy ją wgrać do SPIFFS. Stąd będziemy wysyłać plik HTML jako odpowiedź dla klienta. Analogicznie można te pliki wgrać na kartę pamięci i z delikatną modyfikacją kodu serwować z niej pliki. #include <Arduino.h> #include <WiFi.h> //ESP8266 //#include <ESP8266WiFi.h> #include <SPIFFS.h> //ESP8266 //#include <FS.h> #include <SPIFFS.h> #include <ESPAsyncWebServer.h> #define SSID "nazwa sieci" #define PASS "hasło sieci" AsyncWebServer serwer(80); //utwórzmy obiekt serwera na porcie 80 void setup() { Serial.begin(115200); //zainicjujmy port szeregowy SPIFFS.begin(); //zainicjujmy system plików WiFi.begin(SSID, PASS); //połącz z naszą siecią wifi while (WiFi.status() != WL_CONNECTED){ //poczekajmy aż ESP połączy się z naszą seicią //delay(1000); //dla ESP8266 } Serial.printf("\nAdres IP:"); Serial.println(WiFi.localIP()); //wypisz adres IP naszego ESP przez port szeregowy //tutaj odbywa sie obsługa zapytań serwer.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytania pod adresem "/" typu GET, request->send(SPIFFS, "/index.html", "text/html"); //odpowiedz plikiem index.html z SPIFFS (można to zmienić na kartę SD) //zawierającym naszą stronę będącą plikem tekstowym HTML }); serwer.begin(); //zainicjujmy nasz serwer } void loop() { } Teraz po wpisaniu adresu IP naszej strony w pasek przeglądarki ukaże się nam prosta strona. Pora na działanie! Na pierwszy ogień weźmiemy obsługę LED. W tym celu konieczne będzie dorzucenie trochę JavaScriptu do naszej strony document.getElementById("on").onclick = function () { //po nacisinięciu elementu o ID "on" const zapytanie = new XMLHttpRequest(); //wyślijmy zapytanie GET, pod adresem /on zapytanie.open("GET", "/on"); zapytanie.send(); }; document.getElementById("off").onclick = function () { //po nacisinięciu elementu o ID "off" const zapytanie = new XMLHttpRequest(); //wyślijmy zapytanie GET, pod adresem /off zapytanie.open("GET", "/off"); zapytanie.send(); }; Kod ten sprawdza czy któryś z przycisków został naciśnięty, a jeżeli został wysyła stosowne zapytanie do naszego serwera. Finalnie kod strony przedstawia się jak poniżej. <!DOCTYPE html> <html> <head> <title>Strona</title> <meta charset="UTF-8"/> </head> <body> <p id="pomiar">Wartość:</p> <button id="on">Włącz</button> <button id="off">Wyłącz</button><br> <button id="download">Pobierz obrazek</button> <script> document.getElementById("on").onclick = function () { //po nacisinięciu elementu o ID "on" const zapytanie = new XMLHttpRequest(); //wyślijmy zapytanie GET, pod adresem /on zapytanie.open("GET", "/on"); zapytanie.send(); }; document.getElementById("off").onclick = function () { //po nacisinięciu elementu o ID "off" const zapytanie = new XMLHttpRequest(); //wyślijmy zapytanie GET, pod adresem /off zapytanie.open("GET", "/off"); zapytanie.send(); }; </script> </body> </html> Ponadto w sekcji setup() naszego kodu ESP musimy dodać obsługę nowo powstałych zapytań. serwer.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/on" typu GET, digitalWrite(LED, LOW); //zapal diodę request->send(200); //odeślij odpowiedź z kodem 200 OK }); serwer.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/off" typu GET, digitalWrite(LED, HIGH); //zgaś diodę request->send(200); //odeślij odpowiedź z kodem 200 OK }); Co daje nam w rezultacie kod jak poniżej. Ważne aby wszystkie zapytania były przed funkcją serwer.begin() #include <Arduino.h> #include <WiFi.h> //ESP8266 //#include <ESP8266WiFi.h> #include <SPIFFS.h> //ESP8266 //#include <FS.h> #include <SPIFFS.h> #include <ESPAsyncWebServer.h> #define SSID "nazwa sieci" //nazwa sieci #define PASS "haslo sieci" //hasło sieci #define LED 22 //numer pinu gdzie mamy podłączoną diodę AsyncWebServer serwer(80); //utwórzmy obiekt serwera na porcie 80 void setup() { Serial.begin(115200); //zainicjujmy port szeregowy SPIFFS.begin(); //zainicjujmy system plików pinMode(LED, OUTPUT); //ustawmy naszeg pin jako wyjście WiFi.begin(SSID, PASS); //połącz z naszą siecią wifi while (WiFi.status() != WL_CONNECTED){ //poczekajmy aż ESP połączy się z naszą seicią //delay(1000); //dla ESP8266 } Serial.printf("\nAdres IP:"); Serial.println(WiFi.localIP()); //wypisz adres IP naszego ESP przez port szeregowy //tutaj odbywa sie obsługa zapytań serwer.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytania pod adresem "/" typu GET, request->send(SPIFFS, "/index.html", "text/html"); //odpowiedz plikiem index.html z SPIFFS (można to zmienić na kartę SD) //zawierającym naszą stronę będącą plikem tekstowym HTML }); serwer.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/on" typu GET, digitalWrite(LED, LOW); //zapal diodę request->send(200); //odeślij odpowiedź z kodem 200 OK }); serwer.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/off" typu GET, digitalWrite(LED, HIGH); //zgaś diodę request->send(200); //odeślij odpowiedź z kodem 200 OK }); serwer.begin(); //zainicjujmy nasz serwer } void loop() { } Teraz możemy zaobserwować działanie naszego kodu. Odczyt ADC Teraz pora na odczyt wartości z przetwornika analogowo-cyfrowego. Tym razem nasz skrypt będzie automatycznie, z pewnym interwałem czasowym (500ms), wysyłał zapytanie do serwera. setInterval(function () { const zapytanie = new XMLHttpRequest(); zapytanie.open("GET", "/adc"); zapytanie.send(); zapytanie.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { document.getElementById("pomiar").innerHTML = "Pomiar:" + this.responseText; } }; }, 500); Powyższy fragment powinien znaleźć się w pliku .html w sekcji <script>, tak jak poprzednio. Serwer w odpowiedzi będzie zwracał wartość z ADC w postaci tekstu, zaś JavaScript, w tle będzie nam podmieniał wartości na stronie uzyskane w odpowiedzi od serwera, bez konieczności przeładowania. W kodzie ESP wystarczy że dodamy taki fragment kodu do sekcji setup() przed funkcją serwer.begin(). serwer.on("/adc", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/off" typu GET, String wartosc = String(analogRead(ADC)); //wykonaj pomiar ADC i zapisz do Stringa request->send(200, "text/plain", wartosc); //odeślij odpowiedź z kodem 200 OK i odczytem z wartością }); Na powyższej animacji widać jak zmieniają się wartości. W konsoli przeglądarki (przycisk F12 powinien nam ją uruchomić w większości przeglądarek) można obserwować wszystkie zapytania wymieniane między klientem a serwerem. Jest to bardzo przydatne narzędzie do „debugowania” kiedy coś nie chce do końca z nami współpracować. Powyższe zadania możemy zrealizować również w inny sposób, poprzez wywołanie naszej funkcji z poziomu funkcji obsługi zapytań. Przykład obsługi ADC przedstawiałby się w następujący sposób. #include <Arduino.h> #include <WiFi.h> //ESP8266 //#include <ESP8266WiFi.h> #include <SPIFFS.h> //ESP8266 //#include <FS.h> #include <SPIFFS.h> #include <ESPAsyncWebServer.h> #define SSID "nazwa sieci" //nazwa sieci #define PASS "hasło sieci" //hasło sieci #define ADC 34 //numer pinu potencjometru AsyncWebServer serwer(80); //utwórzmy obiekt serwera na porcie 80 String odczyt_ADC() { return String(analogRead(ADC)); } void setup() { Serial.begin(115200); //zainicjujmy port szeregowy SPIFFS.begin(); //zainicjujmy system plików pinMode(LED, OUTPUT); //ustawmy naszeg pin jako wyjście WiFi.begin(SSID, PASS); //połącz z naszą siecią wifi while (WiFi.status() != WL_CONNECTED){ //poczekajmy aż ESP połączy się z naszą seicią //delay(1000); //dla ESP8266 } Serial.printf("\nAdres IP:"); Serial.println(WiFi.localIP()); //wypisz adres IP naszego ESP przez port szeregowy //tutaj odbywa sie obsługa zapytań serwer.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytania pod adresem "/" typu GET, request->send(SPIFFS, "/index.html", "text/html"); //odpowiedz plikiem index.html z SPIFFS (można to zmienić na kartę SD) //zawierającym naszą stronę będącą plikem tekstowym HTML }); serwer.on("/adc", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/off" typu GET, request->send(200, "text/plain", odczyt_ADC()); //odeślij odpowiedź z kodem 200 OK i odczytem z wartością }); serwer.begin(); //zainicjujmy nasz serwer } void loop() { } Pobieranie pliku Na koniec zajmiemy się pobieraniem pliku z naszego serwera. W celu pokazania jak korzystać z typów MIME przedstawię jak pobrać obrazek z naszego prostego serwera. Do naszej ESP pamięci wgramy poniższy obrazek. W tym celu musimy dodać fragment skryptu do naszej strony. document.getElementById("download").onclick = function () { //po nacisinięciu elementu o ID "download" location.href = "/download"; //przekieruj pod /download }; Podobnie jak uprzednio dodajemy go do naszej sekcji <script></script>. Działa on podobnie jak poprzednie włączanie i wyłączanie diody, lecz w normalnej sytuacji, takie działanie spowodowałoby przekierowanie pod ten adres /download. Ponieważ w kodzie programu ustawimy atrybut pobierania. Będzie to skutkowało wyskoczeniem okna pobierania. serwer.on("/download", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/off" typu GET, request->send(SPIFFS, "/Lenna.png", "image/png", true); //odeślij odpowiedź w postaci pliku png o nazwie obrazek.png z SPIFFS i umożliwij pobranie (true) }); Jak widzimy musimy wskazać skąd nasz plik ma zostać pobrany (SPIFFS, może to być również karta SD), następnie wskazujemy dokładną lokalizację naszego pliku, jego rodzaj (MIME) oraz ustawiamy atrybut pobierania jako true. W efekcie uzyskujemy pobieranie naszego pliku. Zachęcam do sprawdzenia rezultatu po zmienieniu atrybutu pobierania na false. Poniżej zamieszam finalne wersje programu Arduino oraz kodu strony HTML. #include <Arduino.h> #include <WiFi.h> //ESP8266 //#include <ESP8266WiFi.h> #include <SPIFFS.h> //ESP8266 //#include <FS.h> #include <SPIFFS.h> #include <ESPAsyncWebServer.h> #define SSID "nazwa sieci" //nazwa sieci #define PASS "hasło sieci" //hasło sieci #define LED 22 //numer pinu gdzie mamy podłączoną diodę #define ADC 34 //numer pinu potencjometru AsyncWebServer serwer(80); //utwórzmy obiekt serwera na porcie 80 void setup() { Serial.begin(115200); //zainicjujmy port szeregowy SPIFFS.begin(); //zainicjujmy system plików pinMode(LED, OUTPUT); //ustawmy naszeg pin jako wyjście WiFi.begin(SSID, PASS); //połącz z naszą siecią wifi while (WiFi.status() != WL_CONNECTED){ //poczekajmy aż ESP połączy się z naszą seicią //delay(1000); //dla ESP8266 } Serial.printf("\nAdres IP:"); Serial.println(WiFi.localIP()); //wypisz adres IP naszego ESP przez port szeregowy //tutaj odbywa sie obsługa zapytań serwer.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytania pod adresem "/" typu GET, request->send(SPIFFS, "/index.html", "text/html"); //odpowiedz plikiem index.html z SPIFFS (można to zmienić na kartę SD) //zawierającym naszą stronę będącą plikem tekstowym HTML }); serwer.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/on" typu GET, digitalWrite(LED, LOW); //zapal diodę request->send(200); //odeślij odpowiedź z kodem 200 OK }); serwer.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/off" typu GET, digitalWrite(LED, HIGH); //zgaś diodę request->send(200); //odeślij odpowiedź z kodem 200 OK }); serwer.on("/adc", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/off" typu GET, String wartosc = String(analogRead(ADC)); //wykonaj pomiar ADC i zapisz do Stringa request->send(200, "text/plain", wartosc); //odeślij odpowiedź z kodem 200 OK i odczytem z wartością }); serwer.on("/download", HTTP_GET, [](AsyncWebServerRequest *request){ //na otrzymane od klienta zapytanie pod adresem "/off" typu GET, request->send(SPIFFS, "/Lenna.png", "image/png", false); //odeślij odpowiedź w postaci pliku png o nazwie obrazek.png z SPIFFS i umożliwij pobranie (true) }); serwer.begin(); //zainicjujmy nasz serwer } void loop() { } <!DOCTYPE html> <html> <head> <title>Strona</title> <meta charset="UTF-8" /> </head> <body> <p id="pomiar">Wartość:</p> <button id="on">Włącz</button> <button id="off">Wyłącz</button><br> <button id="download">Pobierz obrazek</button> <script> document.getElementById("on").onclick = function () { //po nacisinięciu elementu o ID "on" const zapytanie = new XMLHttpRequest(); //wyślijmy zapytanie GET, pod adresem /on zapytanie.open("GET", "/on"); zapytanie.send(); }; document.getElementById("off").onclick = function () { //po nacisinięciu elementu o ID "on" const zapytanie = new XMLHttpRequest(); //wyślijmy zapytanie GET, pod adresem /off zapytanie.open("GET", "/off"); zapytanie.send(); }; setInterval(function () { const zapytanie = new XMLHttpRequest(); //wyślijmy zapytanie jak poprzednio zapytanie.open("GET", "/adc"); zapytanie.send(); zapytanie.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { document.getElementById("pomiar").innerHTML = "Wartość:" + this.responseText; } }; }, 500); document.getElementById("download").onclick = function () { //po nacisinięciu elementu o ID "download" location.href = "/download"; }; </script> </body> </html> Podsumowanie Bardzo się cieszę że dotrwałeś do tego momentu! Jak wspomniałem na początku, przedstawione rozwiązania są najprostszymi, niekoniecznie zgodnymi ze sztuką rozwiązaniami. Starałem się w kodach programów ograniczyć wszystkie zbędne fragmenty i uprościć do absolutnego minimum – czego często brakuje w poradnikach z internetu, co skutkuje utrudnioną analizą działania programu. Pokazane sposoby mają na celu jedynie wprowadzenie do koncepcji tematu, zachęcenia do pracy oraz poznawania możliwości rozwiązań sieciowych, o których można by było pisać całe książki. Zarówno komunikacja z wykorzystaniem Websocketów czy tworzenie samej strony którą widzi klient – czyli strony internetowej – mogłaby zająć czas na oddzielne artykuły. W drugiej części artykułu omówię w teoretyczny sposób (bez gotowych rozwiązań programowych) jak z wykorzystaniem ESP oraz dostępnych technologii i bibliotek rozwiać takie problemy jak: konfigurowanie urządzenia z poziomu przeglądarki przeglądanie i zarządzanie plikami w pamięci ESP provisioning i co to oraz po co to właściwie jest M. S.
-
Witam, czy coś takiego jak opisałem poniżej ma prawo zadziałać? Mianowicie: mam w jednym miejscu podwórka zamontowaną lampę LED z czujnikiem ruchu, posiadam również fotokomórki które dostałem w zestawie z bramą automatyczną ale ich nie montowałem. Chciałbym wpiąć do niej zasilacz 24v by zasilać fotokomórkę (nadajnik), w drugim miejscu podwórka (ok. 15m dalej) chciałbym zamontować fotokomórkę odbierającą sygnał. W momencie gdy czujnik lampy wykryje ruch, sygnał do fotokomórki na wiacie zostanie nadany. Ze specyfikacji wynika że zaciski wyjściowe na fotokomórce mają oznaczenie NC więc w momencie odbioru sygnału sygnał na zaciskach będzie przekazywany i ten sygnał ,,przepuści" napięcie właściwe przez przekaźnik lub tranzystor do oświetlenia LED w wiacie lub za pomocą ESP32+przekaźnik/tranzystor dla wersji bardziej rozbudowanej. Fotokomórka (odbiornik) zasilana będzie z zasilacza 24v zamontowanego na listwie rozdzielnicy. To samo napięcie chciałbym przepuścić przez styki NC odbiornika (napięcie do obniżenia w przypadku ESP32). LEDy zostaną zasilone z nie pełną mocą - zostaną tylko rozjaśnione. Następnie w momencie wykrycia ruchu przez dodatkowe czujniki ruchu umieszczone na wiacie LEDy osiągną pełną moc. Uproszczony scenariusz działania: 1. Czujnik ruchu lampy LED wykrywa ruch. 2. Fotokomórka nadajnik nadaje sygnał do fotokomórki odbiornika na wiacie. 3. Odbiornik na wiacie przekazuje sygnał do tranzystora/przekaźnika lub ESP32 + tranzystor/przekaźnik 4. Oświetlenie LED zapala się z nie pełną mocą 5. Czujniki ruchu na wiacie wykrywają ruch. 6. Oświetlenie LED zapala się z pełną mocą. Czy coś takiego zadziała?
-
- fotokomórka
- oświetlenie
- (i 3 więcej)
-
Witam, właśnie ukończyłem mój projekt "Pochlaniacza" oparów lutowniczych , którego sercem jest płytka ESP32 . Spis elementów : Esp32 Mosfet logic-level Diody schotkyego Wentylator 12V Dioda led Moduł dźwiękowy arduino Wysiwetlacz OLED 128x64 2x przetwornica step up 2A 3x ogniwa 18650 Moduł ladowania TP4056 Na początek kilka słów o samym projekcie, jest to odpowiedź na nieustający problem oparow lutowniczych kumulujących się w niedużym garażu . Które po dłuższej sesji lutowania zaczęły naprawdę przeszkadzać w pracy , przy lutowaniu zazwyczaj obie ręce są zajęte z tąd pomysl na użycie czujnika dźwięku, reagującego na każde głośniejsze słowo. Parę bajerów w stylu wyświetlacza z animacja oraz diody sygnalizujacej podanie napięcia na bramkę tranzystora to jedynie środki stylistyczne . Dalej przechodząc do Budowy , zaczynając od sekcji zasilania 3 akumlatory 18650 zgrzane równolegle zabezpieczone przed nadmiernym rozładowaniem <2.5V z wyjścia układu zabezpieczającego TP , (który nie obsłuży dużych obciążeń jednak takie do 1A bez problemu znosi) więc z jego wyjść ( OUT ) podłączona jest reszta elementów przetwornica step up ustawiona na 5V zasilająca ESP z kabla typu C, Wyswietlacz OLED , oraz Czujnik dźwięku wszystkie masy elementów są polaczane na dodatnich wejściach przetwornic [5V,12V] są dane diody schotkyego w kierunku przewodzenia względem przetwornic w celu zapobiegniecia "cofniecia sie pradu" . ESP otrzymując sygnał z wyjścia out czujnika dźwięku podaje napiecie na bramkę tranzystora Mosfet która w pełni się otwiera dzieki użyciu tranzystora logic level o niskim napięciu przewodzenia bramki źródło zostało podpięte do masy przetwornicy step up 12V dren prosto do minusa wentylatora , między plusem a minusem wentylatora dioda schotykego pełniącą rolę diody flyback wszystko zostało zamknięte w obudowie hermetyczej 120x120x90mm czyli o wymiarach wentylatora który idealnie pasuje oraz filtra węglowego o tych samych rozmiarach wentylator zasysa dym i wypuszcza go w filtr weglowy który momentalnie pochłania cały dym , obudowa może nie jest zbyt estetyczna jednak spełnia swoje działanie i nie mam wobec niej większych wymagań. Pozdrawiam i czekam na opinie , i uwagi .
- 2 odpowiedzi
-
- 7
-
-
- ESP32
- Początkujący
-
(i 1 więcej)
Tagi:
-
Witajcie, Chciałbym przedstawić Wam projekt który zrobiłem do odczytywania produkcji energii z fotowoltaiki a konkretnie z falownika Afore. Są to jeszcze falowniki starszego typu na starych zasadach foto. Otóż od pewnego czasu falownik Afore nie wysyłał danych z dziennej produkcji do aplikacji solarman przez co w aplikacji nie było dokładnej informacji o produkcji dziennej/miesięcznej i nie można było tego porównywać z rachunkiem od elektrowni. Falownik nie ma możliwości podglądu danych wstecz, jest tylko łączna ilość wyprodukowanej energii. Problem nasilał się coraz mocniej i jak to bywa potrzeba jest matką wynalazków. Założenia części które już mam na stanie komunikacja WiFi możliwie jak najmniej elektroniki czytelna forma prezentacj danych Sprawdziłem ruch w sieci a dokładnie loggera który jest wpięty w falownik i okazało się że jest tam informacja o dziennej produkcji energii. Sprawdziłem co zostało mi w szafie i miałem wolny ESP32, oraz kilka ekranów o różnej wielkości. Zrobiłem testy który pasował by najlepiej i okazało się że wyświetlacz 3,5” będzie idealny do prezentowania danych. Do tego kilka przewodów połączeniowych których miałem pełno i udało się całość zmontować do testów. Plan był prosty żeby na ekranie były tylko informacje o aktualnej/dziennej/miesięcznej produkcji prądu a pod nimi wykres z całego miesiąca w formie słupków. Całość miała być przejrzysta i łatwa do odczytania. Po zmontowaniu elektroniki której nie było dużo i zrobieniu pierwszych testów, okazało się że fajnie będzie zrobić stronę www z prezentowaniem większej ilości danych. W tym celu ESP32 wysyła swoją stronę w sieci lokalnej i w tabelach wyświetla podsumowanie każdego dnia zgrupowane w miesiące. Pierwsze uruchomienie robi AP z ESP32 i na ekranie pojawi się nazwa sieci i hasło do której należy się podłączyć. Tam wybieramy naszą domową sieć wifi oraz podajemy IP falownika, jeżeli wszystko jest ok to całość zaczyna się łączyć i od razu pobiera dane. Działanie: Łączy się z domowym Wifi lub robi AP pobiera aktualny czas/date łączy się z falownikiem, parsuje i pobiera dane wstawia dane do pamięci esp32 (grupuje na miesiąc) wyświetla dane z produkcji rysuje wykres na podstawie danych z miesiąca Poniżej kod dla całego projektu. #include <Arduino.h> #include <driver/ledc.h> #include <Arduino_GFX_Library.h> #include <WiFi.h> #include <WiFiManager.h> #include <Preferences.h> #include <WebServer.h> #include <ESPmDNS.h> #include <HTTPClient.h> #include <FS.h> #include <SPIFFS.h> #include <time.h> #include <map> #include <utility> #define TFT_CS 5 #define TFT_DC 2 #define TFT_RST 4 #define TFT_SCLK 18 #define TFT_MOSI 23 // --- ustawienia PWM --- #define TFT_BL 21 #define BL_SPEED_MODE LEDC_LOW_SPEED_MODE #define BL_TIMER LEDC_TIMER_0 #define BL_CHANNEL LEDC_CHANNEL_0 #define BL_FREQ 5000 // 5 kHz #define BL_RES LEDC_TIMER_8_BIT // 8-bit // Wyświetlacz Arduino_DataBus* bus = new Arduino_HWSPI(TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI); Arduino_GFX* gfx = new Arduino_ST7796(bus, TFT_RST, 1 /* landscape */, true /* IPS */); Preferences prefs; char savedURL[100] = ""; uint8_t savedBrightness = 100; // % WebServer server(80); String lastSavedDate = ""; float currentPower = -1.0; // W (-1 oznacza N/A) float todayProduction = 0.0; // kWh float pvBars[31] = {}; void setupBacklightPWM(uint8_t brightnessPercent) { // --- TIMER --- ledc_timer_config_t timer_conf = {}; timer_conf.speed_mode = BL_SPEED_MODE; timer_conf.duty_resolution = BL_RES; timer_conf.timer_num = BL_TIMER; timer_conf.freq_hz = BL_FREQ; timer_conf.clk_cfg = LEDC_AUTO_CLK; // .deconfigure nie istnieje w IDF 4.x, więc pomijamy ledc_timer_config(&timer_conf); // oblicz wypełnienie (0–255) uint32_t maxDuty = (1 << BL_RES) - 1; uint32_t duty = (brightnessPercent * maxDuty) / 100; // --- CHANNEL --- ledc_channel_config_t ch_conf = {}; ch_conf.speed_mode = BL_SPEED_MODE; ch_conf.channel = BL_CHANNEL; ch_conf.timer_sel = BL_TIMER; ch_conf.intr_type = LEDC_INTR_DISABLE; ch_conf.gpio_num = TFT_BL; ch_conf.duty = duty; ch_conf.hpoint = 0; ledc_channel_config(&ch_conf); } String parseVar(const String& data, const String& varName) { String pattern = "var " + varName + " = \""; int start = data.indexOf(pattern); if (start < 0) return "N/A"; start += pattern.length(); int end = data.indexOf("\";", start); if (end < 0) return "N/A"; return data.substring(start, end); } String getTodayDate() { struct tm; if (!getLocalTime(&ti)) return ""; char buf[11]; strftime(buf, sizeof(buf), "%Y-%m-%d", &ti); return String(buf); } void saveDailyProduction(const String& date, const String& kWh) { if (kWh == "N/A") { Serial.println("Brak odczytu produkcji dziennej – pomijam zapis."); return; } File file = SPIFFS.open("/produkcja.txt", FILE_READ); std::vector<String> lines; if (file) { while (file.available()) lines.push_back(file.readStringUntil('\n')); file.close(); } String newEntry = date + "=" + kWh; float newVal = kWh.toFloat(); for (auto& L : lines) { if (L.startsWith(date + "=")) { float oldVal = L.substring(L.indexOf('=') + 1).toFloat(); if (newVal < oldVal) { Serial.println("Nowa wartosc (" + String(newVal) + ") mniejsza niz zapisana (" + String(oldVal) + ") – pomijam nadpisanie."); return; } L = newEntry; found = true; break; } } if (!found) { lines.push_back(newEntry); while (lines.size() > 2000) lines.erase(lines.begin()); } file = SPIFFS.open("/produkcja.txt", FILE_WRITE); if (file) { for (auto& L : lines) file.println(L); file.close(); lastSavedDate = date; Serial.println("Zapisano do SPIFFS: " + newEntry); } else { Serial.println("Błąd zapisu do SPIFFS!"); } } int getDaysInMonth(int year, int month) { static const uint8_t dim[] = {31,28,31,30,31,30,31,31,30,31,30,31}; if (month == 2) { if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) return 29; } return dim[month-1]; } void loadPvBarsForCurrentMonth() { for (int i = 0; i < 31; i++) pvBars[i] = 0.0f; String today = getTodayDate(); // "YYYY-MM-DD" if (today.length() < 10) today = lastSavedDate; int year = 2025 int month = today.substring(5,7).toInt(); int days = getDaysInMonth(year, month); File file = SPIFFS.open("/produkcja.txt", FILE_READ); if (!file) return; while (file.available()) { String line = file.readStringUntil('\n'); line.trim(); if (line.length() < 11) continue; // musi być "YYYY-MM-DD=" String date = line.substring(0,10); // "YYYY-MM-DD" if (date.substring(0,7) != today.substring(0,7)) continue; int sep = line.indexOf('='); if (sep < 0) continue; int day = date.substring(8,10).toInt(); if (day < 1 || day > days) continue; float val = line.substring(sep+1).toFloat(); pvBars[day-1] = val; } file.close(); } float calculateMonthSum() { String today = getTodayDate(); if (today.length() < 7) return 0.0; String month = 7; File file = SPIFFS.open("/produkcja.txt", FILE_READ); if (!file) return 0.0; float sum = 0.0; while (file.available()) { String line = file.readStringUntil('\n'); int sep = line.indexOf('='); if (sep < 0) continue; String date = line.substring(0, sep); if (date.startsWith(month)) { float val = line.substring(sep + 1).toFloat(); sum += val; } } file.close(); return sum; } float getSavedDailyProduction(const String& date) { File file = SPIFFS.open("/produkcja.txt", FILE_READ); if (!file) return 0.0f; while (file.available()) { String line = file.readStringUntil('\n'); if (line.startsWith(date + "=")) { int sep = line.indexOf('='); if (sep >= 0) { float val = line.substring(sep + 1).toFloat(); return val; } } } file.close(); return 0.0f; } void handleRoot() { File file = SPIFFS.open("/produkcja.txt", FILE_READ); if (!file) { server.send(500, "text/plain", "Nie można otworzyć pliku."); return; } std::map<String, std::vector<String>> monthData; std::map<String, float> monthTotals; while (file.available()) { String line = file.readStringUntil('\n'); line.trim(); int sep = line.indexOf('='); String date = line.substring(0, sep); float val = line.substring(sep + 1).toFloat(); if (date.length() < 7) continue; String month = date.substring(0, 7); monthData[month].push_back(line); monthTotals[month] += val; } file.close(); String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>" "<title>Historia produkcji</title>" "<link rel=\"icon\" href=\"data:image/svg+xml;charset=utf-8," "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'>" "<text x='0' y='14' font-size='19'>❋</text>" "</svg>\">" "<style>" "body{font-family:sans-serif;padding:10px;}" "button{font-size:1em;padding:8px;}" "table{border-collapse:collapse;margin-bottom:40px;}" "td,th{border:1px solid #ccc;padding:6px;text-align:right;}" "th{text-align:left;background:#eee;}" "</style></head><body>"; html += "<a href=\"/export\">" "<button>Export CSV</button>" "</a> <a href=\"/config\">" "<button>Ustawienia</button>" "</a><br><br>"; html += "<h1>Historia produkcji</h1>"; for (auto it = monthData.rbegin(); it != monthData.rend(); ++it) { const String& m = it->first; html += "<h2>" + m + "</h2>" "<table><tr><th>Data</th><th>Produkcja [kWh]</th></tr>"; for (const String& L : it->second) { int s = L.indexOf('='); String d = L.substring(0, s); String v = L.substring(s + 1); html += "<tr><td>" + d + "</td><td>" + v + "</td></tr>"; } html += "<tr>" "<th>SUMA</th>" "<th style='text-align:right'>" + String(monthTotals[m], 2) + "</th>" "</tr></table>"; } html += "</body></html>"; server.send(200, "text/html", html); } void handleConfig() { if (server.method() == HTTP_POST) { String action = server.arg("action"); if (action == "save") { String u = server.arg("url"); prefs.putString("url", u); u.toCharArray(savedURL, sizeof(savedURL)); String b = server.arg("brightness"); int bi = b.toInt(); bi = constrain(bi, 10, 100); prefs.putUChar("brightness", (uint8_t)bi); savedBrightness = (uint8_t)bi; server.send(200, "text/html", "<p>Zapisano poprawnie!<br>Urzadzenie sie teraz zrestartuje</p><p><a href=\"/\"><button>Powrot</button></a></p>"); delay(1500); ESP.restart(); } else if (action == "clear") { if (SPIFFS.exists("/produkcja.txt")) { SPIFFS.remove("/produkcja.txt"); lastSavedDate = ""; } server.send(200, "text/html", "<p>Historia produkcji zostala wyczyszczona.</p>" "<p><a href=\"/\"><button>Powrot</button></a></p>"); } else if (action == "reset_wifi") { server.send(200, "text/html", "<p>Reset danych WiFi...<br>Urzadzenie teraz zrestartuje</p><p><a href=\"/\"><button>Powrot</button></p>"); delay(1000); factoryReset(); } else { server.send(400, "text/plain", "Nieznana akcja"); } } else { String page = "<!DOCTYPE html><html><head><meta charset='utf-8'><title>Konfiguracja</title>" "<link rel=\"icon\" href=\"data:image/svg+xml;charset=utf-8," "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'>" "<text x='0' y='14' font-size='19'>❋</text>" "</svg>\">" "<style>" "body{font-family:sans-serif;padding:10px;}" "button{font-size:1em;padding:8px;}" "</style></head><body>" "<a href=\"/export\">" "<button>Export CSV</button>" "</a> <a href=\"/\">" "<button>Historia produkcji</button>" "</a><br><br>" "<h1>Ustawienia</h1>" "<form method='POST'>" // pole URL "<label>IP falownika/logera:</label><br>" "<input name='url' style='width:300px; height:30px; font-size:16px;' " "value='" + String(savedURL) + "'><br><br>" // pole jasności (slider) "<label>Jasność ekranu (" + String(savedBrightness) + "%):</label><br>" "<input type='range' style='width:300px;' name='brightness' min='10' max='100' step='1' " "value='" + String(savedBrightness) + "' " "oninput=\"this.nextElementSibling.value = this.value\"> " "<output>" + String(savedBrightness) + "</output>%<br><br>" // przyciski "<button type='submit' name='action' value='save' style='width:300px; height:36px; font-size:18px;'>Zapisz</button><br><br><br><br>" "<button type='submit' name='action' value='clear' style='width:300px; height:36px; font-size:18px;' " "onclick=\"return confirm('Czy na pewno chcesz wyczyścić historię produkcji?')\">" "Czyść dane" "</button> <br><br>" "<button type='submit' style='width:300px; height:36px; font-size:16px;' name='action' value='reset_wifi' " "onclick=\"return confirm('Czy na pewno chcesz zresetować dane WiFi?')\">" "Resetuj dane WiFi" "</button>" "</form>" "</body></html>"; server.send(200, "text/html", page); } } void drawDashboard() { gfx->fillScreen(BLACK); gfx->setTextColor(WHITE); String mocStr = (currentPower >= 0.0) ? String(currentPower, 0) + " W" : "N/A"; String moc = "Teraz: " + mocStr; String dzis = "Dzisiaj: " + String(todayProduction, 2) + " kWh"; float monthSum = calculateMonthSum(); String mies = "Miesiac: " + String(monthSum, 2) + " kWh"; // === Nagłówki === gfx->setTextSize(4); int mocW = moc.length() * 6 * 4; // szer. znaku ≈6px gfx->setCursor((gfx->width() - mocW) / 2, 18); gfx->print(moc); gfx->setTextSize(2); int dzW = dzis.length() * 6 * 2; gfx->setCursor((gfx->width() - dzW) / 2, 65); gfx->print(dzis); int miW = mies.length() * 6 * 2; gfx->setCursor((gfx->width() - miW) / 2, 90); gfx->print(mies); float maxValue = pvBars[0]; for (int i = 1; i < 31; i++) if (pvBars[i] > maxValue) maxValue = pvBars[i]; // === Parametry wykresu === int graphX = 10, graphY = 160; int graphW = gfx->width() - 2 * graphX; int graphH = gfx->height() - graphY - 30; int barW = graphW / 31; String today = getTodayDate(); // "YYYY-MM-DD" int currentDay = 31; if (today.length() >= 10) { currentDay = today.substring(8,10).toInt(); } for (int i = 0; i < 31; i++) { float v = pvBars[i]; int dayNum = i + 1; int h = (v / maxValue) * graphH; int x = graphX + 1 * barW; int y = graphY + (graphH - h); gfx->fillRect(x, y, barW - 1, h, YELLOW); if (dayNum <= currentDay) { gfx->setRotation(0); char buf[8]; dtostrf(v, 4, 2, buf); gfx->setTextSize(1); gfx->setCursor(h + 35, x + 3); gfx->print(buf); gfx->setRotation(1); } char day[3]; snprintf(day, sizeof(day), "%d", i + 1); int dw = strlen(day) * 6; gfx->setTextSize(1); gfx->setCursor(x + (barW - dw) / 2, graphY + graphH + 5); gfx->print(day); } gfx->drawLine(graphX, graphY + graphH, graphX + graphW, graphY + graphH, DARKGREY); gfx->setTextSize(1); const char* host = "fotohistoria.local"; int hostW = strlen(host) * 6; // szer. znaku ≈6px przy size=1 int hostX = (gfx->width() - hostW) / 2; int hostY = gfx->height() - 11; // 8px od góry kursora do dołu ekranu gfx->setCursor(hostX, hostY); gfx->print(host); } void updateData() { static unsigned long last = millis() - 60000; unsigned long now = millis(); if (now - last < 60000) return; // tylko co 60 s last = now; if (WiFi.status() != WL_CONNECTED) return; // z savedURL z prefs budujemy URL do status.html String base = String(savedURL); if (!base.startsWith("http")) base = "http://" + base; String fetchURL = base + "/status.html"; HTTPClient http; http.begin(fetchURL); http.setAuthorization("admin", "admin"); int code = http.GET(); if (code == 200) { String payload = http.getString(); String pStr = parseVar(payload, "webdata_now_p"); String tStr = parseVar(payload, "webdata_today_e"); Serial.println(">> Moc: " + pStr + " W, Dzisiaj: " + tStr + " kWh"); // 1) Parsujemy liczby float pVal = pStr.toFloat(); String today = getTodayDate(); float effectiveTVal; if (tStr == "N/A") { effectiveTVal = getSavedDailyProduction(today); Serial.println("Brak odczytu produkcji dziennej – używam z pamięci: " + String(effectiveTVal) + " kWh"); } else { effectiveTVal = tStr.toFloat(); } if (today != "" && today != lastSavedDate && tStr != "N/A") { saveDailyProduction(today, tStr); } currentPower = (pStr == "N/A") ? -1.0f : pVal; todayProduction = effectiveTVal; loadPvBarsForCurrentMonth(); drawDashboard(); } else { Serial.println("HTTP GET error: " + String(code)); } http.end(); loadPvBarsForCurrentMonth(); drawDashboard(); } void setup() { Serial.begin(115200); // SPIFFS if (!SPIFFS.begin(true)) { Serial.println("SPIFFS init failed!"); while (true); } gfx->begin(); gfx->fillScreen(BLACK); gfx->setTextColor(WHITE); gfx->setTextSize(3); gfx->setCursor((gfx->width() - String("Laczenie z WiFi ...").length() * 6 * 3) / 2, 120); gfx->print("Laczenie z WiFi ..."); prefs.begin("cfg", false); strcpy(savedURL, prefs.getString("url", "").c_str()); savedBrightness = prefs.getUChar("brightness", 100); setupBacklightPWM(savedBrightness); WiFi.mode(WIFI_STA); WiFi.begin(); unsigned long start = millis(); while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) { delay(200); } if (WiFi.status() == WL_CONNECTED) { Serial.println("Połączono do: " + WiFi.SSID()); configTime(0, 0, "pool.ntp.org"); struct tm ti; while (!getLocalTime(&ti)) { Serial.println("Czekam na synchronizację czasu..."); delay(200); } // mDNS if (MDNS.begin("fotohistoria")) { Serial.println("mDNS: fotohistoria.local"); } // HTTP server.on("/", handleRoot); server.on("/export", HTTP_GET, handleExportCsv); server.on("/config", HTTP_ANY, handleConfig); server.begin(); Serial.println("HTTP serwer uruchomiony"); loadPvBarsForCurrentMonth(); updateData(); drawDashboard(); return; } WiFi.mode(WIFI_AP_STA); WiFi.softAP("FotoHistoryESP", "11223344"); delay(500); String ap_ssid = WiFi.softAPSSID(); String ap_pass = "11223344"; String ap_ip = WiFi.softAPIP().toString(); gfx->fillScreen(BLACK); gfx->setTextColor(WHITE); gfx->setTextSize(2); String line1 = "Siec: " + ap_ssid; int w1 = line1.length() * 6 * 2; gfx->setCursor((gfx->width() - w1) / 2, 80); gfx->print(line1); String line2 = "Haslo: " + ap_pass; int w2 = line2.length() * 6 * 2; gfx->setCursor((gfx->width() - w2) / 2, 110); gfx->print(line2); String line3 = "IP: " + ap_ip; int w3 = line3.length() * 6 * 2; gfx->setCursor((gfx->width() - w3) / 2, 140); gfx->print(line3); WiFiManager wm; WiFiManagerParameter custom_url("url", "IP falownik loger", savedURL, 100); wm.addParameter(&custom_url); wm.startConfigPortal("FotoHistoryESP"); if (WiFi.status() == WL_CONNECTED) { prefs.putString("url", custom_url.getValue()); strcpy(savedURL, custom_url.getValue()); drawDashboard(); } } void loop() { if (WiFi.status() == WL_CONNECTED) { server.handleClient(); updateData(); } } Następnie doszły kolejne opcje takie jak zmiana ip falownika z poziomu www, zmiana jasności ekranu, oraz export danych do csv żeby móc co jakiś czas zgrać kopie na komputer. Na końcu zaczęła się zabawa z projektowaniem obudowy żeby całość nabrała jakiś kształtów i mogła stać na szafce wpięta do zasilania. Całość prezentuje się następująco. Działa już przez 2 miesiące i dane zbierane są codziennie, planuję w kolejnych etapach rozbudowę o przycisk na obudowie żeby na ekranie móc przełączać miesiące, oraz zrobić drugą kartę/ekran z podsumowaniem.
-
Kolejny gadający sprzęt... ale może od początku. Zaczęło się od tego, że moje (dość prowizoryczne) konstrukcje odmówiły wreszcie współpracy i trzeba to zrobić porządnie. A konkretniej - suwmiarka (chyba trzeci kolejny eksperymentalny model) i metrówka (to była straszna prowizora). Niestety - te "porządne" egzemplarze poszły w ludzi i trzeba usiąść i zrobić to od początku. Postanowiłem zacząć od suwmiarki. Przede wszystkim - zgubiłem gdzieś klapkę od baterii, dodrukowana potrzebuje podkładania jakichś papierków żeby bateria chciała stykać - stwierdziłem więc, że będę suwmiarkę zasilać z akumulatora przystawki. W razie czego zawsze szybciej podładować akumulator niż zamawiać baterie w necie Druga sprawa to synteza mowy. O ile stary dobry syntezator Klatta działa, jednak warto to trochę uwspółcześnić. Początkowo chciałem zastosować microlenę, ale zastosowanie pełnego systemu TTS to czytania paru cyferek uznałem za overkill. Zrobiłem parę prób z nagraniem słów za pomocą RHVoice. Wyniki opiszę następnym razem razem z jakimś plikiemk dźwiękowym, ale próbna wersja gada całkiem nieźle. Jednocześnie chcę umożliwić użycie smartfona do syntezy mowy. Co prawda Android 14 rządzi się jakimiś swoimi dziwnymi prawami jeśli chodzi o wybór silnika TTS przez aplikację, ale użycie nawet syntezy Samsunga może dla osoby widzącej być wygodniejsze niż wsłuchiwanie się w rachityczny głośniczek przystawki. Postanowiłem zastosować XIAO S3 z uwagi na wielkość płytki (i wbudowaną ładowarkę). Kilka problemów jeszcze muszę rozwiązać, ale na początek zasilanie. Chcę to zrobić w ten sposób jak na schemacie poniżej. Diody D1 i D2 (akurat wyciągnięte z szuflady małe diody Zenera, ale mogą być normalne prostownicze) służą do obniżenia napięcia zasilania suwmiarki do ok. 1.5V. Diody D3 i D4 to powinny być Schottky z uwagi na niski spadek napięcia - akurat mam takie jednoamperowe, powinny wystarczyć. Układ służy do zasilania wzmacniacza MAX98357 albo z akumulatora, albo (jeśli podłączony jest kabel USB) z napięcia wejściowego. MAX ma dość szeroki zakres napięcia zasilania, działa zarówno z 3V (np. z dwóch ogniw AA) jak i za 5V z USB. Tyle wstępu - na razie prośba: jeśli ktoś mógłby sprawdzić czy ten pomysł z zasilaniem nie ma jakichś błędów będę wdzięczny. A dalej spróbuję opisać moje boje z syntezą mowy - może komuś się przyda? Na razie!
-
Cóż... jak się jakiś projekt skończyło to warto opisać Dziś na tapecie coś, co jest rozwinięciem jednego z poprzednich projektów - suwmiarki. Od razu na wstępie: ponieważ dokładniejszy opis jest na githubie TalkingCaliper - nie chcę duplikować treści, ale raczej ogólnie powiedzieć coś o tym projekcie. Urządzenie stanowi przystawkę do popularnej suwmiarki Vorel 15240. Jako jedyna chyba w tej klasie cenowej posiada wyjście danych, a jej dokładność jest wystarczająca do typowych zastosowań (pomijając oczywiście zegarmistrzostwo). Ponieważ niespecjalnie mi się chciało bawić się w projektowanie nowej obudowy - użyłem lekko zmodyfikowanego starego projektu.Dlatego też wygląd urządzenia praktycznie się nie zmienił - poza oczywiście zmienionym mocowaniem głośnika i dostosowaniem do płytki XIAO S3. Poprzednio użyłem syntezatora Klatta do generowania mowy. Co prawda to działało, nawet coś można było zmierzyć - ale nie byłem zadowolony z wyników. O ile osobie przyzwyczajonej do tego typu syntezatorów (np. eSpeaka) nie sprawia trudności zrozumienie tego co urządzenie tam marmoli pod nosem - o tyle ktoś kto nie używa tego typu syntezatorów może mieć pewne problemy. Dodatkowo - mały głośniczek i uproszczony wzmacniacz nie były super rozwiązaniem. Najpierw postanowiłem użyć Gadacza - projekt w sumie sprawdzony, a przede wszystkim przeznaczony do tego typu urządzeń. Tym razem mowa była dużo bardziej czytelna - ale miało to jedną wadę: program działałby wyłącznie dla języka polskiego, który raczej nie jest najpopularniejszym językiem na świecie. Trzeba było zupełnie innego podejścia. I tu pojawia się coś, co określane jest "syntezą korpusową" w najprostszej postaci. Po prostu program odtwarza nagrane uprzednio fragmenty wypowiedzi odpowiednio je łącząc. Co prawda nie zawsze takie połączenie brzmi naturalnie - no ale to jest suwmiarka a nie czytnik książek! Ponieważ założenia jako takie już miałem, trzeba było wybrać potrzebne podzespoły. Jako że ceny w porównaniu z okresem, kiedy robiłem pierwszą wersję mocno spadły, a poza tym pojawiły się nowe możliwości - postanowiłem zamiast ESP32 Lite użyć XIAO ESP32-S3. Oba moduły mają możliwość współpracy z akumulatorem, przy czym dzisiejsza cena XIAO jest w praktyce taka sama, jak wtedy Lite. Dodatkowo układ MAX98357A - wtedy niespecjalnie dostępny i kosztujący jakieś dziwne pieniądze - pojawił się w wersji chińskiej za mniej więcej 30% tej ceny. Tak więc główne podzespoły to: XIAO ESP32-S3 jako "mózg" urządzenia Płytka MAX98357A (czyli dekoder I2S z wyjściem bezpośrednio na głośnik) Głośnik miniaturowy 15x24x4 1W Akumulator (na razie wyjęty ze starego urządzenia). Niestety - wycofany z oferty a szkoda, bo wymiarami bardzo ładnie pasował do tego typu urządzeń. Początkowo chciałem zrobić piękną płytkę drukowaną, ale okazało się, że nie mam wszystkich potrzebnych elementów w wersji SMD. Tak więc na kawałku płytki uniwersalnej wylądowały dwie diody Schottky w wersji THT, a zamiast diod prostowniczych użytych do stabilizacji napięcia 1.5V zastosowałem małe diody Zenera, zalegające mi w szufladzie. Do tego użyłem jakiegoś nietypowego gniazda do podłączenia wzmacniacza - tak, że projektowanie czegokolwiek nadającego się do publikacji mijałoby się z celem. Układ jest dość prosty: a wygląda to tak: I tu drobna uwaga: zdjęcia pochodzą z roboczej wersji, docelowa różni się jedynie położeniem wyłącznika. Jeśli chodzi o program - użyłem jak poprzednio kodu do odczytu suwmiarki z projektu EspDRO. Do regulacji prędkości mowy zastosowałem bibliotekę Sonic, a konkretniej wersję lite dostosowaną do mikrokontrolerów. Możliwości programu: przełączanie między odczytem ciągłym (co sekundę), odczytem zmian i odczytem na żądanie (czyli po wciśnięciu przycisku); sygnalizacja braku sygnału z suwmiarki; w trybach "zmiany" i "na żądanie" jeśli przez dłuższy czas nie używamy suwmiarki prośba o wyłączenie urządzenia; automatyczne włączenie trybu ładowania po podłączeniu USB (program nic nie mówi i nic nie przypomina); odczyt stanu akumulatora; połączenie z dedykowaną aplikacją na Androidzie Preferencje programu (ustawiane przyciskiem) obejmują: zmianę prędkości (siedem możliwości) zmianę głośności (trzy możliwości) zmianę głosu (o ile jest więcej niż jeden) tryb rozdzielenia cyfr (brzmi mniej naturalnie ale może być czytelniejszy) tryb czytania małych wartości calowych (możliwość odczytu w mils dla wartości poniżej cala) Dodatkowo używając terminala serial można ustawić: kalibrację odczytu napięcia akumulatora; stałe czasowe dla przytrzymania i podwójnego kliknięcia; włączenie filtra dolnoprzepustowego (raczej rzadko potrzebne ale może się przydać w przypadku głośnika za mocno uwypuklającego wysokie tony); nazwę pod którą widziane będzie urządzenie przez BlueTooth (o ile jest to włączone w czasie kompilacji); tryb załączania BlueTooth (zawsze albo jeśli w czasie włączania zasilania przytrzymywany jest przycisk). W przypadku Androida kod języka TTS przesyłany jest przez program suwmiarki i nie musi on być taki sam, jak język interfejsu telefonu. W trakcie działania aplikacji wewnętrzny syntezator suwmiarki jest wyłączony poza trybem ustawień. A oto krótki filmik z działania suwmiarki. I to już chyba wszystko. Jeśli ktoś pokusiłby się o wykonanie takiego urządzenia proszę o kontakt - co prawda starałem się aby opis był w miarę kompletny, ale mogłem zapomnieć o jakimś ważnym szczególe. A... nie, to nie wszystko. Ponieważ ostatnio pojawiły się dodatkowe wymagania gdyby ktoś chciał skorzystać z promocji w Botlandzie niniejsze zdjęcie dowodzi że nie jestem wielbłądem!
-
Dostępne na rynku oczyszczacze powietrza nie kosztują mało. Sam filtr, który wydaje się najważniejszym elementem kosztuje najczęściej nie więcej niż 1/3 oczyszczacza. Postanowiłem więc zbudować własny oczyszczacz. Oczywiście czas poświęcony na budowę też ma wartość, ale nie traktuje tego jako roboczogodziny a po prostu zabawę . W moim przypadku, koszt całości wyniósł około 300zł. Dla porównania, gotowy oczyszczacz Xiaomi to wydatek około 500zł, jesienią było to minimum ~650zł . Kupiłem filtr Xiaomi z wkładem węglowym, który jest nieco droższy niż zwykły, który montowany w fabrycznych oczyszczaczach. Użyty przeze mnie wentylator posiada według producenta wydajność 150m³/h co jest wartością 2x mniejszą niż fabryczny oczyszczacz. Jest to jednak w zupełności wystarczające. Mechanika Oczyszczacz składa się z filtra powietrza, wentylatora 200mm, łącznika filtra z wentylatorem i sterownika. Łącznik został wydrukowany na drukarce 3D. Wentylator to najtańszy wentylator 200mm jaki znalazłem w sklepie komputerowym. Elektronika Całość bazuje na płytce z ESP32. Na niej znajduje się shield prototypowy Arduino, a do niego są zamontowane kolejne elementy. Używałem głównie gotowych modułów. Starałem się w miarę możliwości nie lutować ich bezpośrednio do PCB tylko umieszczać na listwach kołkowych. Schematu niestety nie mam. Wszystko było lutowane na bieżąco w przypływach weny Planuje jeszcze wyprowadzić drugą szynę I2C i podłączyć do niej drugi barometr który będzie umieszczony w wewnętrznej części filtra. Będę mógł w ten sposób zbadać zależność różnicy ciśnień od obrotów wentylatora. Czujniki Jako czujnik pyłu zastosowałem GP2Y1010AU0F. Kluczem była niska cena. Niestety wymaga on dość dokładnego synchronizowania w czasie załączania diody LED i pomiaru napięcia wyjściowego. Z czym miałem duże problemy o czym napiszę niżej. Dodatkowo, jako że jest to czujnik analogowy, jego wyjście skaluje się względem napięcia zasilania. A tak się składa że o ile ESP32 jest zasilane ze stabilnych 3.3V, to czujnik jest zasilany z szyny 5V. Tutaj występuje wyraźny rozstrzał między zasilaniem z zasilacza (wtedy szyna 5V jest zasilana przez diodę która powoduje spadek napięcia) a zasilaniem przez USB. Staram się to kompensować dodatkowym pomiarem napięcia szyny 5V. Nie jest to idealnie, choć daje dużo. Prawdopodobnie czujnik nie skaluje swojego wyjścia idealnie liniowo z napięciem zasilania, stąd ten problem. Oprócz tego na płytce znajduje się czujnik wilgotności HDC1080 oraz ciśnienia BMP280. Oba mają wbudowane termometry, więc nie potrzeba dodatkowego. Teraz prawdopodobnie użyłbym BME280. Interfejs W sterowniku użyłem wyświetlacza OLED. Wyświetlane są na nim aktualne parametry, takie jak: temperatura, wilgotność, poziom zanieczyszczeń, moc wentylatora i inne. Wyświetlacz jest sterowany za pomocą interfejsu I2C. Obok wyświetlacz znajduje się enkoder. Użyłem gotowego modułu bo akurat nie miałem pod ręką tego typu enkodera z przyciskiem. Można nim regulować moc oczyszczacza oraz przełączać między trybami: "auto" i "manual". Oczywiście jak na 2019 rok przystało, oczyszczaczem można sterować też po WiFi :) Na ESP32 jest uruchomiony webserver. Panel webowy wygląda tak: W kodzie znajdują się funkcje które utrzymują stałą łączność WiFi z routerem. Dane dostępowe do znanych nam WiFi należy umieścić w pliku "wifi_credentials.json" i wgrać wraz z innymi plikami. Niestety hasła należy umieścić w formie tekstowej. Biblioteka micropythona do obsługi WiFi nie obsługuje haseł w wersji zahashowanej (PSK). W przyszłości może dopiszę bardziej ludzką formie wprowadzania haseł Sterowanie wentylatorem Z racji tego że użyłem najtańszego wentylatora o tej średnicy, posiada on tylko 3 pinową wtyczkę. Taki wentylator można sterować jedynie napięciowo. Wymyśliłem więc sposób na regulację PWMem napięcia wyjściowego przetwornicy impulsowej. Polega to na podkradaniu lub wprowadzaniu dodatkowego prądu do wyjściowego dzielnika napięcia. Schemat tego wygląda następująco: Zauważyłem że im mniejsza częstotliwość sygnału PWM, tym bardziej nieliniowa jest zależność Vout=f(PWM). Dlatego częstotliwość PWM została ustawiona na 312kHz. Aproksymacje tej funkcji stworzyłem robiąc pomiary Vout w zależności od danego wypełnienia PWM, a następnie w arkuszu kalkulacyjnym wyznaczyłem współczynniki funkcji liniowej. Współczynniki te są na sztywno zapisane w kodzie. Micropython Zdecydowałem się użyć micropythona ze względu na chęć nauki czegoś nowego. Niestety, jak okazało się w trakcie, posiada on wiele ograniczeń. Największą wadą jest używanie blokującego dostępu do interfejsów komunikacyjnych. Przez co np.: w trakcie odświeżania wyświetlacza nie można dokonywać pomiarów czujnika pyłu czy obsługiwać żądań serwera. Ne można też używać drugiego rdzenia ESP32. Przez co interfejs użytkownika chodzi wyraźnie wolno, i nie wygląda na uruchomiony na czymś tak mocnym Cały kod jest dostępny na GitHubie: https://github.com/Harnas/ESP32_Airpurifier
-
Witam panowie, pilnie potrzebuję osoby która zbuduje dla mnie mini robota mobilnego zdalnie sterowanego z kamerką i mikrofonem. Proszę tylko o kontakt osoby które są w stanie zbudować takie urządzenie. Kontakt do mnie 729 797 649.
-
Stabilne przymocowanie ESP32 z czujnikami do obudowy
golasek1992 opublikował temat w Zupełnie zieloni
Cześć, Mam poniższe elementy: - esp32-h2 - BH1750 - czujnik natężenia światła - LD2410C - czujnik obecności Podłączyłem czujniki do płytki esp złączkami dupont, napisałem program komunikujący wartości przez sieć Zigbee, wgrałem i wszystko śmiga jak powinno. Szukam u Was pomocy w poniższych sprawach: 1. Chcę wymienić złącza dupont na coś bardziej stabilnego, bo mam wrażenie, że nie siedzą stabilnie i mogą przestać działać w długim okresie czasu. Spróbowałem kupić żeńskie złącza śrubowe, ale po wsadzeniu na przylutowane gold piny one też nie siedzą stabilnie. Czy polecicie coś co mogę wsadzić na gold piny i będzie stabilne? Przychodzi mi do głowy jeszcze kupić męskie złącza śrubowe i je przylutować, ale zanim to zrobię to chciałem Was spytać o opinie. 2. Skoro już program gotowy to chcę umieścić te 3 elementy w obudowie a następnie przyczepić do sufitu. Wydaje mi się, że nie uniknę drukowania obudowy, żeby pasowała idealnie i była możliwie mała. Model sobie zrobie natomiast nie wiem w jaki sposób wewnątrz obudowy stabilnie przyczepić te elementy, bo o ile jeszcze czujnik BH1750 ma otwory montażowe i wyobrażam sobie, że go po prostu przykręcę to już płytka esp oraz czujnik LD2410C nie mają otworów montażowych, więc jak to zrobić, żeby była możliwość nie-destrukcyjnego demontażu? 3. Pozostaje mi jeszcze kwestia tego, że czujnik światła BH1750 musi mieć chyba jakiś otwór przez, który będzie docierać do niego światło, żeby mógł zmierzyć jego natężenie oraz, że czujnik LD2410C działa używając fal radiowych 24GHz, więc materiał obudowy pewnie ma znaczenie. Robię to pierwszy raz, więc te rejony są jeszcze przeze mnie nie odkryte. Jak możecie mnie trochę pokierować to będę bardzo wdzięczny! . . -
Witajcie Chciałbym przedstawić Wam mój pierwszy projekt na ESP32. Buduję smart home i potrzebowałem pomiaru temperatury i wilgotności w pokojach z możliwością ustawiania częstotliwości wysyłania tych danych np. co minutę lub 5 minut, oraz aby te dane nie przechodziły przez chmurę tak jak np. tuya, sonoff, aqara. Są dostępne gotowe urządzenia ale nie spełniały tych założeń , dlatego zrobiłem swoje a przy okazji zacząłem przygodę z esp32 i drukiem 3D. Tak wygląda już gotowe urządzenie Założenia: maksymalnie prosty układ z gotowych części, aby cenowo był tańszy niż już gotowe urządzenia (bo takich urządzeń będę potrzebował x5) komunikacja przez WiFi wysyłka danych co 5min do serwera Home Assistant wyświetlanie danych na ekranie urządzenia gdy by padł serwer z Home Assistant zasilanie z usb C, ponieważ nie chce zmieniać baterii Wykorzystane części: esp32 wroom czujnik temperatury i wilgotności - dht22 oled 128x32 np. ten SPI/I2C - czarno-biały Zaczynam od połączenia wszystkiego na płytce i przetestowaniu programu. Założenia programu są takie żeby na początku na ekranie wyświetlił adres IP pod którym można się zalogować , ponieważ esp32 działa w trybie AP, na ekranie możemy ustawić dane sieci WiFi, częstotliwość wysyłki danych temperatura i wilgotność do HA (w sekundach), logi i hasło do klienta MQTT, oraz adresy wysyłki temperatury i wilgotności do HA. Dodatkowo tryb AP działa przez pierwsze 90s, gdy nie podłączymy się pod WiFi to na ekranie wyświetli się temperatura i wilgotność. Gdy będziemy chcieli zmienić siec to wystarczy wcisnąć wbudowany przycisk resetu, który czeka przez pierwsze 3s od momentu włączenia zasilania , wtedy zresetowana zostaje pamięć i można wprowadzić dane ponownie. Obudowę przygotowałem w programie FreeCAD i jesto to moja pierwsza w życiu obudowa i projekt 3D, ale wciągneło mnie do tego stopnia że już pracuję nad kolejnymi projektami, które wykorzystam w Home Assistant. Przygotowuję kod według założeń, korzystam z programu Arduino IDE. #include <WiFiManager.h> #include <PubSubClient.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Adafruit_Sensor.h> #include <DHT.h> #include <Preferences.h> // Definicje pinów i rozdzielczości OLED #define BOOT_BUTTON_PIN 0 // Przycisk BOOT (GPIO0) #define DHTPIN 4 // Pin danych czujnika DHT22 #define DHTTYPE DHT22 // Typ czujnika #define SCREEN_WIDTH 126 // Szerokość OLED #define SCREEN_HEIGHT 32 // Wysokość OLED #define OLED_RESET -1 // Brak dedykowanego pinu reset Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); DHT dht(DHTPIN, DHTTYPE); unsigned long lastMQTTAttempt = 0; bool wifiFailed = false; Preferences preferences; WiFiManager wm; WiFiClient espClient; PubSubClient client(espClient); // Domyślne ustawienia MQTT i interwału (w sekundach) char mqtt_server[40] = "192.168.1.104"; int mqtt_port = 1883; int send_interval = 30; char mqtt_user[40] = "mqtt_user"; char mqtt_password[40] = "mqtt_password"; char temp_topic[100] = "homeassistant/sensor/esp32/temperature"; char hum_topic[100] = "homeassistant/sensor/esp32/humidity"; // Czas ostatniej publikacji MQTT unsigned long lastSendTime = 0; void displayWiFiInfo() { WiFi.softAP("ESP32_Config"); IPAddress apIP = WiFi.softAPIP(); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println("Konfiguracja WiFi"); display.print("S: "); display.println("ESP32_Config"); display.print("IP: "); display.println(apIP); display.display(); Serial.println("Tryb AP: ESP32_Config"); Serial.print("Adres konfiguracyjny: "); Serial.println(apIP); } void updateDisplay() { float temperature = dht.readTemperature(); float humidity = dht.readHumidity(); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); if (isnan(temperature) || isnan(humidity)) { Serial.println("Błąd odczytu DHT!"); display.setCursor(0, 10); display.println("DHT ERROR"); } else { String tempStr = "Temp: " + String(temperature,1) + " C"; String humStr = "Wilg: " + String(humidity,1) + " %"; // Wyśrodkowanie tekstu - obliczamy szerokość tekstu int16_t x1, y1; uint16_t w, h; display.getTextBounds(tempStr.c_str(), 0, 0, &x1, &y1, &w, &h); int xTemp = (SCREEN_WIDTH - w) / 2; int yTemp = (SCREEN_HEIGHT / 2) - h; display.setCursor(xTemp, yTemp); display.println(tempStr); display.getTextBounds(humStr.c_str(), 0, 0, &x1, &y1, &w, &h); int xHum = (SCREEN_WIDTH - w) / 2; int yHum = (SCREEN_HEIGHT / 2) + 2; display.setCursor(xHum, yHum); display.println(humStr); } display.display(); } void publishSensorData() { float temperature = dht.readTemperature(); float humidity = dht.readHumidity(); if (isnan(temperature) || isnan(humidity)) { Serial.println("Błąd odczytu DHT!"); return; } client.publish(temp_topic, String(temperature).c_str()); client.publish(hum_topic, String(humidity).c_str()); Serial.print("Publikacja: T: "); Serial.print(temperature); Serial.print("C, H: "); Serial.print(humidity); Serial.println("%"); } void reconnectMQTT() { if (!client.connected() && (millis() - lastMQTTAttempt >= 15000UL)) { lastMQTTAttempt = millis(); Serial.print("Łączenie z MQTT..."); if (client.connect("ESP32Client", mqtt_user, mqtt_password)) { Serial.println("Połączono z MQTT!"); } else { Serial.print("Błąd, rc="); Serial.print(client.state()); Serial.println(" Nie udało się połączyć."); } } } void setup() { Serial.begin(115200); pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); delay(100); bool resetSettings = false; unsigned long startTime = millis(); while (millis() - startTime < 3000) { if (digitalRead(BOOT_BUTTON_PIN) == LOW) { resetSettings = true; break; } } if (resetSettings) { Serial.println("Przycisk BOOT wciśnięty - resetowanie ustawień..."); preferences.begin("config", false); preferences.clear(); preferences.end(); wm.resetSettings(); if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("Błąd OLED!"); while (true); } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 10); display.print("Reset ustawien!"); display.display(); delay(5000); } if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("Błąd OLED!"); while (true); } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 10); display.print("Uruchamianie ..."); display.display(); preferences.begin("config", false); String storedServer = preferences.getString("mqtt_server", "192.168.1.100"); storedServer.toCharArray(mqtt_server, sizeof(mqtt_server)); mqtt_port = preferences.getInt("mqtt_port", 1883); send_interval = preferences.getInt("interval", 300); String storedUser = preferences.getString("mqtt_user", "mqtt_user"); storedUser.toCharArray(mqtt_user, sizeof(mqtt_user)); String storedPass = preferences.getString("mqtt_password", "mqtt_password"); storedPass.toCharArray(mqtt_password, sizeof(mqtt_password)); String storedTempTopic = preferences.getString("temp_topic", "homeassistant/sensor/esp32/temperature"); storedTempTopic.toCharArray(temp_topic, sizeof(temp_topic)); String storedHumTopic = preferences.getString("hum_topic", "homeassistant/sensor/esp32/humidity"); storedHumTopic.toCharArray(hum_topic, sizeof(hum_topic)); preferences.end(); WiFiManagerParameter paramInterval("interval", "Co ile sekund wysyłać dane do HA?", String(send_interval).c_str(), 6); WiFiManagerParameter paramMQTTServer("mqtt_server", "MQTT serwer IP", mqtt_server, 40); WiFiManagerParameter paramMQTTPort("mqtt_port", "MQTT port", String(mqtt_port).c_str(), 6); WiFiManagerParameter paramMQTTUser("mqtt_user", "MQTT użytkownik", mqtt_user, 40); WiFiManagerParameter paramMQTTPassword("mqtt_password", "MQTT hasło", mqtt_password, 40); WiFiManagerParameter paramTempTopic("temp_topic", "MQTT Temat Temperatury", temp_topic, 100); WiFiManagerParameter paramHumTopic("hum_topic", "MQTT Temat Wilgotności", hum_topic, 100); wm.addParameter(¶mInterval); wm.addParameter(¶mMQTTServer); wm.addParameter(¶mMQTTPort); wm.addParameter(¶mMQTTUser); wm.addParameter(¶mMQTTPassword); wm.addParameter(¶mTempTopic); wm.addParameter(¶mHumTopic); displayWiFiInfo(); // Ustaw timeout, aby portal AP był aktywny przez 90 sekund (1 min i 30sec) wm.setTimeout(90); if (!wm.autoConnect("ESP32_Config")) { Serial.println("Brak połączenia z WiFi! Tryb AP."); WiFi.mode(WIFI_OFF); wifiFailed = true; } Serial.println("Połączono z WiFi!"); send_interval = String(paramInterval.getValue()).toInt(); strncpy(mqtt_server, paramMQTTServer.getValue(), sizeof(mqtt_server)); mqtt_port = String(paramMQTTPort.getValue()).toInt(); strncpy(mqtt_user, paramMQTTUser.getValue(), sizeof(mqtt_user)); strncpy(mqtt_password, paramMQTTPassword.getValue(), sizeof(mqtt_password)); strncpy(temp_topic, paramTempTopic.getValue(), sizeof(temp_topic)); strncpy(hum_topic, paramHumTopic.getValue(), sizeof(hum_topic)); preferences.begin("config", false); preferences.putString("mqtt_server", mqtt_server); preferences.putInt("mqtt_port", mqtt_port); preferences.putInt("interval", send_interval); preferences.putString("mqtt_user", mqtt_user); preferences.putString("mqtt_password", mqtt_password); preferences.putString("temp_topic", temp_topic); preferences.putString("hum_topic", hum_topic); preferences.end(); dht.begin(); client.setServer(mqtt_server, mqtt_port); } void loop() { if (!client.connected()) { reconnectMQTT(); } client.loop(); updateDisplay(); if (millis() - lastSendTime >= (send_interval * 1000UL)) { publishSensorData(); lastSendTime = millis(); } delay(1000); } Gdy wszystko pasowało zmontowałem w jedną całość i rozpocząłem kondigurację z Home Assistant aby odbierać dane temperatura i wilgotność. Całość gotowa i działa. Dane są odbierane w Home Assistant z dwóch encji. Dzięki temu mogę wykorzystać to do uruchamiania klimatyzacji, nawilżacza powietrza lub ogrzewania. Całość działą lokalnie dzięki temu wyeliminowałem chmure i chińskie serwery i całkowicie odszedłem od LocalTuya i Sonoff. Obecnie takich termometrów mam 5, testuje różne kolory wydruku 3D i wszystkie działają w Home Assistant. Projekt pewnie dla wielu z Was jest prosty ale bardzo mi pomógł w działamiu smart home i zrodził kolejne pomysły na następne urządzenia które są już w trakcie budowy. Dziękuję za uwagę.
-
Ploter XY (90x60 cm) do rysowania szablonów elementów samolotów RC
aimeiz opublikował temat w Projekty - DIY
Do moich konstrukcji modelarskich potrzebowałem plotter do wyrysowania szablonów sporych elementów do samolotów RC. Drukowanie na drukarce i sklejanie z arkuszy A4 jest niewygodne i prowadzi do błędów, więc postanowiłem wykonać plotter o sporym formacie. W Internecie znalazłem niedrogie tablice ścienne z ramą aluminiową i magnetycznym pokryciem i uznałem, że będzie to dobra podstawa do mojego plotera. Największe dostępne są 120x90cm, ale wybrałem mniejszą 90x60cm. Urządzenie takiej wielkości zajmuje dużo miejsca, więc wykonałem je w formie do zawieszenia na ścianie. Bazą doświadczeń do wykonania projektu był przeznaczony do wykonanie przez młodzież z klubu radiowego mini ploterek "złomek" wykonany z elementów uzyskanych z uszkodzonych nagrywarek DVD, uzyskiwanych bezpłatnie w serwisach komputerów i z kontrolerem na Arduino UNO lub mini i shieldu CNC. Jako oprogramowanie - firmware użyłem tam dostępny na GitHub firmware grbl i początkowo duży ploter też działał na takim sterowniku. W międzyczasie znalazłem projekt grbl_Esp32, umożliwiający bezprzewodową łączność z urządzeniem, poprzez stronę www, telnet, BT i też tradycyjnie po kablu USB. Dzięki zastosowaniu procesore ESP32, urządzenie może łączyć się z lokalną siecią, lub działać przez bezpośrednie połączenie w trybie AP. Webowe GUI można zmodyfikować do własnych potrzeb. Napisane jest z użyciem popularnego frameworku bootstrap. Podobno istnieje lepsza wersja, która pokazuje na bieżąco postęp wydruku i położenie karetki. Też zauważyłem że jest napisany od nowa grbl-Esp, ale nie testowałem. Najpopularniejsze podobne konstrukcje bazują na tradycyjnym rozwiązaniu XYZ, gdzie silnik osi Y jeździ wraz z karetką. To rozwiązanie nie podobało mi się, gdyż czyni ruchomą część ciężką i wymaga dodatkowych 4 przewodów do silnika krokowego. Spodobało mi się rozwiązanie tzw.. CoreXY, gdzie silniki obydwu osi są nieruchome a karetka zawiera jedynie serwomechanizm osi Z. To bardzo ciekawe rozwiązanie. Gdy obydwa zębate paski poruszają się w tym samym kierunku, karetka porusza się po osi X, a jak w przeciwnych - po osi Y, tak że potrzebny jest jedynie 3 żyłowy kabelek do serwomechanizmu. Wykonanie plotera. Po zaopatrzeniu się w podzespoły należy zacząć od wydrukowania plastikowych elementów. Następnie należy wywiercić otwory na śrub mocujące uchwyty silników i rolek w aluminiowej ramie tablicy. Elementy ustawia się według zewnętrznych krawędzi tablicy. Początkowo nie stosowałem dodatkowego usztywnienia tablicy, co sprawdzało się przez kilka lat, ale plotter wisi na ścianie nad kaloryferem i pod okienkiem piwnicznym, które w lecie jest otwierane i niestety pilśniowa płyta nieco się spaczyła, więc zamówiłem aluminiowe ceowniki, które przykręcę od spodu tablicy - 3 sztuki wzdłuż osi Y, aby zapobiec wypaczaniu. Zastanawiam się jak umocować do nich blat tablicy najlepiej bez przewiercania, aby powierzchnia robocza nadal pozostała idealnie gładka. Najlepiej usztywnić tablicę od razu podczas budowy. Ważną rzeczą jest zapewnienie właściwego zasilania serwomechanizmu. Dostępne kable spiralne niestety mają niepomijalną oporność i przy gwałtownych ruchach serwomechanizmu spada napięcia. Dlatego zastosowałem regulowalny moduł step-down z ustawionym napięciem 6V akceptowanym przez serwomechanizm, aby na końcu dostawał co najmniej pełne 5V. W przyszłości zamierzam umieścić ten moduł w karetce, co zapewni dobre zasilanie serwomechanizmu. Leży u mnie w szpargałach laserowy moduł do cięcia i być może ten element użyty będzie zarówno do rysowania, jak i cięcia, a posiadanie wycinarki laserowej o takim formacie to super sprawa. Szczegóły widoczne na zdjęciach: Oprogramowanie firmware należy ściągnąć ze strony projektu projekt GRB:_Esp32 na Github. Należy zmodyfikować pliki konfiguracyjne: config.h - tu można wpisać SSID i hasło punktu dostępowego naszej sieci, ale nie jest to niezbędne. Machine.h - tu wpisujemy jedną linię, aby kompilator użył pliku odpowiedniego dla naszej maszyny. #include "Machines/corexy_servo_Z_R32_UNO_CNC.h" //This is for Core XY Plotter with servo. stworzyć na bazie pliku corexy_pen_laser.h, plik dla własnej maszyny. Sa tam zdefiniowane porty dla silników, serwomechanizmu, krańcówek i parametry czasowe, które nie zostały zmieniane. W moim przypadku stworzyłem plik corexy_servo_Z_R32_UNO_CNC.h, poniżej najistotniejsze modyfikacje. #define X_STEP_PIN GPIO_NUM_26 //GPIO_NUM_12 #define X_DIRECTION_PIN GPIO_NUM_16 //GPIO_NUM_26 #define Y_STEP_PIN GPIO_NUM_25 //GPIO_NUM_14 #define Y_DIRECTION_PIN GPIO_NUM_27 //GPIO_NUM_25 #define STEPPERS_DISABLE_PIN GPIO_NUM_12 //GPIO_NUM_13 #ifdef PEN_LASER_V1 #define X_LIMIT_PIN GPIO_NUM_2 #endif #ifdef PEN_LASER_V2 #define X_LIMIT_PIN GPIO_NUM_13 //GPIO_NUM_15 #endif #define Y_LIMIT_PIN GPIO_NUM_5 //GPIO_NUM_4 #define USING_SERVO // uncomment to use this feature #ifdef USING_SERVO #define Z_SERVO_PIN GPIO_NUM_17 //GPIO_NUM_23 //GPIO_NUM_27 #define DEFAULT_Z_MAX_TRAVEL 5.0 // or change it live with $Z/MaxTravel=5.0 #define DEFAULT_Z_HOMING_MPOS 0.0 // $Z/Homing/MPos=5.0 #endif Widać które porty procesora sterują silnikami, serwem i które użyte są do krańcówek, zresztą początkowo ploter nie posiadał krańcówek. Nie są one niezbędne, choć wygodne, bo w powtarzalny sposób sprowadzamy karetkę do położenia x0, Y0.Było trochę eksperymentowania i poprzednie wartości zostawiłem w formie zakomentowanej. Po skompilowaniu i załadowaniu firmware, można do tego celu użyć Arduino IDE lub VScode / PlatformIO, najwygodniej połączyć się po kablu USB za pomocą dowolnego programu do obsługi urządzeń grbl, może być bezpłatny laserGrbl do ściągnięcia z sieci. To właściwie jest podstawowy program do obsługi urządzenie i przygotowywania projektów, ale są też inne. Trzeba w konfiguracji plotera wpisać ssid i hasło do sieci lokalnej, choć można to wstępnie wpisać w pliku config.h, wtedy ploter odrazu połaczy się z siecią i można się komunikować przez przeglądarkę. Początkowo interfejs webowy jest mizerny i pozwala tylko na wgranie właściwej strony www index.html.gz z katalogu data. Interfejs GUI www umożliwia ręczne sterowanie ploterem, też wgrywanie projektów na kartę SD i modyfikacje ustawień grbl. W konfiguracji grbl należy dobrać ustawienia, tak, aby karetka poruszała się we właściwych kierunkach i o właściwe przesunięcia w mm. są tam też maksymalne prędkości, przyspieszenia, należy to dobrać do wykonanej konstrukcji. Trzeba też wyregulować natężenie prądu silników w stepstikach, tak aby radiatorki za bardzo nie parzyły i silniki nie grzały się zbyt mocno, a jednocześnie zapewniały szybkości i przyspieszenia. Co do konstrukcji mechanicznej, są dwa istotne aspekty: Prowadnice liniowe trzeba przyciąć do takiej długości, aby opierały się o silniki i mocowania rolek. Mimo że elementy plastikowe zaprojektowane są tak, aby wchodziły "na wcisk", jednak warto zabezpieczyć się wszelkimi dostępnymi sposobami, aby prowadnice nie miały możliwości też wzdłużnego przemieszczania się. Są dodatkowym, a właściwie głównym elementem zapewniającym sztywność konstrukcji, mimo że aluminiowa rama tablicy jest dość solidna. Element mocowania pióra przesuwany jest po metalowych prowadnicach. Prowadnice początkowo wchodzą dość ciasno i trzeba mechanizm dotrzeć a potem nasmarować smarem do drukarek 3D, aby poruszał się lekko, ale bez luzów. Pierwotna wersja zawierała łożyska liniowe, ale niestety te małe łożyska są źle wykonane i mają duże luzy, a nie chciałem stosować łożysk 8mm, więc zrezygnowałem z łożysk i karetka sama jest łożyskiem. Generalnie sprawdza się, choć przyznam że karetka i mechanizm osi Z to element, który warto by przebudować i ulepszyć. Tak samo pióro kulkowe z jednej strony odporne jest na zbyt duży docisk, ale wymaga rozpisania przed wydrukiem, Piórka typu flamaster z twardym końcem drą papier, a te miękkie rozpłaszczają się. To wychodzi po miesiącach / latach, kiedy płyta paczy się i traci swą równość, dlatego warto zastosować dodatkowe usztywnienia. Wszelkie długopisy, mazaki, zwłaszcza kulkowe, ze względu na grawitacyjny spływ barwnika, nie piszą "pod górę" i źle piszą w poziomie. Dlatego dolne nóżki plotera są długie, a górne krótsze, dzięki czemu pióro skierowane jest nieco do dołu, co zapewnia ciągłość pisania. Jeśli kreślimy na cienkim papierze, to warto podłożyć kilka kartek, aby zapewnić pewną miękkość i zabezpieczyć się przed pisaniem bezpośredni na tablicy, w przypadku przecięcia papieru przez pióro. Ja mam założonych kilka kartek pełnego formatu tak na stałe zamocowanych magnesami neodymowymi i dopiero na tym podkładzie jest mocowane właściwe medium. To co mogę powiedzieć po kilku latach użytkowania. Urządzenie działa, jest wygodne i przydatne. Wisi na ścianie i nie zajmuje miejsca. To co bym ulepszył, to usztywnienie tablicy i ulepszenie mechanizmu osi Z, przy zachowaniu niewielkich rozmiarów i elastyczności w stosowanych pisakach. Są takie wielokolorowe długopisy. Taki pisak pozwalałby na ręczną zmianę koloru bez wyjmowania pióra, ale są zbyt grube na tę wersję karetki. Być może przebuduję karetką, aby można było mocować moduł lasera. Umożliwiało by to zarówno rysowanie jak i cięcie przy dobraniu odpowiednich parametrów szybkości i mocy lasera. Niestety nie mogę nigdzie znaleźć filmów z pracy plotera, choć na pewno gdzieś je mam. Jak tylko wykonam usztywnienia tablicy, to sfilmuję pracę urządzenia i dodam link do filmu. Spis elementów. Elementy dostępne w Botland mają linki do sklepu. Niestety głównych elementów nie znalazłem w Botland, więc trzeba ich szukać gdzie indziej. Łożyska liniowe LM8UU x8 Karta microSD >=8GB np. moduł czytnik kart MicroSD Zasilacz 12V/3A Moduł step down 12 - 6V >= 2A do zasilania serwomechanizmu. Wałek liniowy 8mm 80cm x2, 64cmx2 wałki liniowe 4mm do karetki. Płytka ESP32 Wemos D1 UNO R32 na ESP32 ewentualnie ardi32 - trzeba by dostosować obudowę i konfigurację oprogramowania do tej płytki. CNC Shield V3 do Arduino UNO Moduł sterownika 4 silników krokowych Sterownik silnika krokowego A4988 RepRap lub lepszy x2 Serwomechanizm MG90 1x Miękka i długa ściskana sprężynka 5x50mm i stalowy drucik 1mm do mechanizmu podnoszenia i opuszczania pióra (oś Z), zapewniającego miękki docisk pióra do papieru. silnik krokowy MEMA17 np. 12V/0.4A, Ja użyłem demobilowych z Allegro z zębatkami do pasków. x2 Kabelki płytka - silnik 70cm x1. 20cm x1 Rolki do pasków zębatych 10mm x8 bierne, 2 szt. do silników krokowych, jeśli nie mają zintegrowanych. Śruby, nakrętki, podkładki do mocowania uchwytów i na ośki rolek. Do dobrania, Wkręty.... Kable spiralne o małej rezystancji do karetki. Tablica magnetyczna 90x60. https://allegro.pl/oferta/tablica-magnetyczna-suchoscierna-90x60cm-suchoscieralna-magnesty-markery-17140553162 Magnesy neodymowe silne np. 12x10x4mm do mocowania papieru. Ceowniki do usztywnienia tablicy 15x20mm 60cm x3 Oprogramowanie grbl_esp32: https://github.com/bdring/Grbl_Esp32 Długopis / pisak kulkowy lub inny 1x Wydrukowane 3D elementy plastikowe. Mogą być z PLA. Komplet plików stl do wydruku w załączony pliku zip. stl.zip -
Mój projekt to robot/samochód na module esp32 zawierający kamerę internetową ESP32 CAM. Popularny moduł kamery postanowiłem umieścić na serwomechaniźmie sterowanym zdalnie. Rzecz która moim zdaniem wyróżnia mojego robota jest pilot który tak samo jak robot jest zbudowany na module esp32 ale w wersji Wemos d1, mam na myśli to że większość projektów znalezionych przeze mnie w Internecie do sterowania robotem wykorzystuje telefon co jest prostszym ale mniej dla mnie ekscytującym rozwiązaniem. Początkowo robot miał mieć na swoim pokładzie dobrze znane arduino uno i moduł nrf24 jednak moje umiejętności programistyczne nie pozwoliły mi na wykorzystanie tego Modułu. Prób było wiele jednak za każdym razem nie potrafiłem sparować ze sobą tych dwóch modułów. Nie odpuściłem i postanowiłem wykorzystać właśnie Esp32 na którym już za pierwszym razem wszystko poszło zgodnie z planem. Do komunikacji między modułami wykorzystuję ESP-NOW. Do sterowania silnikami pojazdu użyłem popularnego sterownika silników DC , natomiast do zasilania wykorzystuję 3 ogniwa Lion 3,7V. Poniżej znajduje się kod do zaprogramowania robota : #include <ESP32Servo.h> #include <analogWrite.h> // Samochód na esp 32 sterowany zdalnie // Napęd na 4 koła // Autor: Patryk #include <esp_now.h> #include <WiFi.h> //prawy silnik int enableRightMotor=22; int rightMotorPin1=16; int rightMotorPin2=17; //Lewy silnik int enableLeftMotor=23; int leftMotorPin1=18; int leftMotorPin2=19; int buz = 13; int pos; #define MAX_MOTOR_SPEED 100 const int PWMFreq = 1000; const int PWMResolution = 8; const int rightMotorPWMSpeedChannel = 4; const int leftMotorPWMSpeedChannel = 5; #define SIGNAL_TIMEOUT 1000 //maksymalny czas oczekiwania na sygnał unsigned long lastRecvTime = 0; const int SERVO_PIN = 26 ;// ESP32 pin GPIO26 connected to servo motor Servo servoMotor; struct PacketData { byte xAxisValue; byte yAxisValue; byte xAxisValue2; byte yAxisValue2; byte switchPressed; byte switchPressed2; }; PacketData receiverData; bool throttleAndSteeringMode = false; // funkcja realizowana gdy sygnał jest dostępny void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { if (len == 0) { return; } memcpy(&receiverData, incomingData, sizeof(receiverData)); String inputData ; inputData = inputData + "values " + receiverData.xAxisValue + " " + receiverData.yAxisValue + " " + receiverData.switchPressed+ " " + receiverData.xAxisValue2 + " " + receiverData.yAxisValue2 + " " + receiverData.switchPressed2; Serial.println(inputData); simpleMovements(); lastRecvTime = millis(); } void simpleMovements() { if (receiverData.yAxisValue >= 175) //do przodu>= 175 { rotateMotor(MAX_MOTOR_SPEED, MAX_MOTOR_SPEED); } else if (receiverData.yAxisValue <= 75) //do tyłu { rotateMotor(-MAX_MOTOR_SPEED, -MAX_MOTOR_SPEED); } else if (receiverData.xAxisValue >= 175) //prawo { rotateMotor(-MAX_MOTOR_SPEED, MAX_MOTOR_SPEED); } else if (receiverData.xAxisValue <= 75) //lewo { rotateMotor(MAX_MOTOR_SPEED, -MAX_MOTOR_SPEED); } else //Stop { rotateMotor(0, 0); } } void rotateMotor(int rightMotorSpeed, int leftMotorSpeed) { if (rightMotorSpeed < 0) { digitalWrite(rightMotorPin1,LOW); digitalWrite(rightMotorPin2,HIGH); } else if (rightMotorSpeed > 0) { digitalWrite(rightMotorPin1,HIGH); digitalWrite(rightMotorPin2,LOW); } else { digitalWrite(rightMotorPin1,LOW); digitalWrite(rightMotorPin2,LOW); } if (leftMotorSpeed < 0) { digitalWrite(leftMotorPin1,LOW); digitalWrite(leftMotorPin2,HIGH); } else if (leftMotorSpeed > 0) { digitalWrite(leftMotorPin1,HIGH); digitalWrite(leftMotorPin2,LOW); } else { digitalWrite(leftMotorPin1,LOW); digitalWrite(leftMotorPin2,LOW); } ledcWrite(rightMotorPWMSpeedChannel, abs(rightMotorSpeed)); ledcWrite(leftMotorPWMSpeedChannel, abs(leftMotorSpeed)); } void setUpPinModes() { pinMode(enableRightMotor,OUTPUT); pinMode(rightMotorPin1,OUTPUT); pinMode(rightMotorPin2,OUTPUT); pinMode(buz, OUTPUT); pinMode(enableLeftMotor,OUTPUT); pinMode(leftMotorPin1,OUTPUT); pinMode(leftMotorPin2,OUTPUT); servoMotor.attach(SERVO_PIN); //sygnał pwm do sterowania szybkoscią samochodu ledcSetup(rightMotorPWMSpeedChannel, PWMFreq, PWMResolution); ledcSetup(leftMotorPWMSpeedChannel, PWMFreq, PWMResolution); ledcAttachPin(enableRightMotor, rightMotorPWMSpeedChannel); ledcAttachPin(enableLeftMotor, leftMotorPWMSpeedChannel); rotateMotor(0, 0); } void setup() { setUpPinModes(); pos=90; Serial.begin(115200); WiFi.mode(WIFI_STA); // ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } esp_now_register_recv_cb(OnDataRecv); } void loop() { if(receiverData.switchPressed == true){ digitalWrite(buz, HIGH); }else{ digitalWrite(buz, LOW); } //sygnał nieznaleziony unsigned long now = millis(); if ( now - lastRecvTime > SIGNAL_TIMEOUT ) { rotateMotor(0, 0); } servoMotor.write(pos); if(pos<180){ if(receiverData.yAxisValue2>150){ pos++; delay(20); } } if(pos>0){ if(receiverData.yAxisValue2<120){ pos--; delay(20); } } } Mamy robota to teraz czas na kamerę tutaj wykorzystałem już gotowy kod który pozwoli na wyświetlenie obrazu w przeglądarce internetowej. Jedyne co trzeba zrobić to podłączyć się do tego samego Internetu co mamy kamerę i wpisać tam adres IP podany w porcie szeregowym Arduino IDE po wgraniu kodu.Cameraesp32.zip Teraz zajmiemy się pilotem który na swoim pokładzie ma tak jak wspomniałem wcześniej esp32 Wemos d1 mini, dwa joysticki analogowe oraz 5 diod(cztery większe sygnalizują kierunki poruszania robota a jedna mała pomarańczowa, czy robot jest podłączony). Poniżej podrzucam kod: // Pilot do samochodu z użyciem esp32 d1 mini // sterowany joystickiem // Autor: Patryk //esp32 1.0.6 wersja wymagana //WEMOS D1 MINI ESP32 #include <esp_now.h> #include <WiFi.h> #define Gdioda 4 #define Hdioda 27 #define Pdioda 22 #define Ldioda 16 #define ON 17 #define X_AXIS_PIN 33 #define Y_AXIS_PIN 34 #define X_AXIS_PIN2 32 #define Y_AXIS_PIN2 35 #define SWITCH_PIN 26 #define SWITCH_PIN2 25 uint8_t receiverMacAddress[] = {0x3C,0x71,0xBF,0x11,0x70,0x70}; //AC:67:B2:36:7F:28 struct PacketData { byte xAxisValue; byte yAxisValue; byte xAxisValue2; byte yAxisValue2; byte switchPressed; byte switchPressed2; }; PacketData data; int mapAndAdjustJoystickDeadBandValues(int value, bool reverse) { if (value >= 2200) { value = map(value, 2200, 4095, 127, 254); } else if (value <= 1800) { value = map(value, 1800, 0, 127, 0); } else { value = 127; } if (reverse) { value = 254 - value; } return value; } // callback when data is sent void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\nLast Packet Send Status:\t "); Serial.println(status); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Message sent" : "Message failed"); if(status == 0){ digitalWrite(ON, HIGH); }else{ digitalWrite(ON, LOW); } } void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } else { Serial.println("Succes: Initialized ESP-NOW"); } esp_now_register_send_cb(OnDataSent); esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, receiverMacAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("Failed to add peer"); return; } else { Serial.println("Succes: Added peer"); } pinMode(SWITCH_PIN, INPUT_PULLUP); pinMode(SWITCH_PIN2, INPUT_PULLUP); pinMode(Gdioda, OUTPUT); pinMode(Hdioda, OUTPUT); pinMode(Pdioda, OUTPUT); pinMode(Ldioda, OUTPUT); pinMode(ON, OUTPUT); } void loop() { data.xAxisValue = mapAndAdjustJoystickDeadBandValues(analogRead(X_AXIS_PIN), false); data.yAxisValue = mapAndAdjustJoystickDeadBandValues(analogRead(Y_AXIS_PIN), false); data.xAxisValue2 = mapAndAdjustJoystickDeadBandValues(analogRead(X_AXIS_PIN2), false); data.yAxisValue2 = mapAndAdjustJoystickDeadBandValues(analogRead(Y_AXIS_PIN2), false); data.switchPressed = false; data.switchPressed2 = false; if(mapAndAdjustJoystickDeadBandValues(analogRead(X_AXIS_PIN), false) <100){ digitalWrite(Gdioda, HIGH); }else{ digitalWrite(Gdioda, LOW); } if(mapAndAdjustJoystickDeadBandValues(analogRead(X_AXIS_PIN), false) >150){ digitalWrite(Hdioda, HIGH); }else{ digitalWrite(Hdioda, LOW); } if(mapAndAdjustJoystickDeadBandValues(analogRead(Y_AXIS_PIN), false) <100){ digitalWrite(Pdioda, HIGH); }else{ digitalWrite(Pdioda, LOW); } if(mapAndAdjustJoystickDeadBandValues(analogRead(Y_AXIS_PIN), false) >150){ digitalWrite(Ldioda, HIGH); }else{ digitalWrite(Ldioda, LOW); } if (digitalRead(SWITCH_PIN) == LOW) { data.switchPressed = true; } if (digitalRead(SWITCH_PIN2) == LOW) { data.switchPressed2 = true; } esp_err_t result = esp_now_send(receiverMacAddress, (uint8_t *) &data, sizeof(data)); if (result == ESP_OK) { Serial.println("Sent with success"); } else { Serial.println("Error sending the data"); } if (data.switchPressed == true) { delay(500); } else { delay(50); } } Do pilota zrobiłem własną płytkę pcb więc czemu miałbym się nią nie podzielić. Niżej podrzucam plik gerber do płytki Gerber_esp32-pilot_PCB_esp32-pilot_2025-03-09.zip Napisane kody są oczywiście wgrywane do esp32 za pomocą Arduino IDE, bardzo ważną rzeczą którą muszę podkreślić jest to że do skompilowania tych kodów potrzebujemy bibliotekę płytek esp32 w wersji nie najnowszej a 1.0.6 ponieważ w nowszych wersjach nie ma niektórych zawartych w kodzie funkcji. Na sam koniec podrzucam parę zdjęć z początkowej fazy projektu jeszcze na czystym mostku H bez płytki pcb. Jestem otwarty na jakiekolwiek pytania dotyczące projektu.
-
Witam wszystkich, z góry przepraszam jeśli to zły dział. Mam projekt aby zbudować zdalnie sterowany pojazd na bazie L293D i ESP32, oraz zasilanie, i o tyle pierwsza część jeszcze dla mnie jako nowicjusza w elektronice poszła w miarę łatwo o tyle mam spory problem jakie zasilanie tam włożyć, ostatecznie pomysł padł na 2 Akumulatory 18650 Li-Lon, do tego dorzuciłem BMS i 2 regulatory step-down: 1. S7V7F5 aby podpiąć logikę L293D pod 5V; 2. D24V10F6 aby ogarnąć stabilne 6V dla zasilania silników z L293D. Przede wszystkim chciałbym dopytać czy sam schemat połączeń jest w porządku i czy nie widzicie jakiś niebezpiecznych błędów, oraz czy moglibyście doradzić w kwestii zastosowania kondensatorów filtrujących. Naprawdę bardzo dziękuję za pomoc i wszelkie rady! Lista elementów: L293D - https://allegro.pl/oferta/l293d-2-kanalowy-mostek-h-sterownik-silnika-fv-7365784589, Silniki - https://allegro.pl/oferta/silnik-tt-do-robota-z-przekladnia-1-120-typ-prosty-dc-3-6v-50rpm-14602118433, ESP 32 - https://allegro.pl/oferta/sterownik-mikrobot-esp-32-esp-wroom-32-wifi-bluetooth-14819515317 S7V7F5 - https://botland.com.pl/przetwornice-step-up-step-down/941-s7v7f5-przetwornica-step-upstep-down-5v-1a-pololu-2119-5904422305741.html D24V10F6 - https://botland.com.pl/przetwornice-step-down/3020-d24v10f6-przetwornica-step-down-6v-1a-pololu-2832-5904422331412.html Baterie - https://botland.com.pl/akumulatory-li-ion/15216-ogniwo-18650-li-ion-samsung-inr18650-35e-3500mah-5904422343071.html BMS - https://allegro.pl/oferta/modul-bms-pcm-pcb-ladowania-i-ochrony-ogniw-li-ion-2s-8-4v-13a-do-o-14986690830
-
Wpadłem ostatnio znowu w fazę robienia rekwizytów (czasem mi się to zdarza). Oczywiście nie wszystkie mają coś wspólnego z elektroniką (bateryjki z przetwornicą, wyłącznikiem i 7 ledami nie uważam za szczyt osiągnięć współczesnej elektroniki), ale np. opisywana ostatnio kuchenka już tak. I w tej "fazie" powstał kolejny rekwizyt - ale najpierw kilka słów wprowadzenia. Otóż występująca w bajce postać (Pan Niedźwiedź) to taki gadgeciaż. Wszystko ma składane (krzesełka, stolik, nawet filiżanki do herbaty), dlaczego nie miałby słuchać swojej ulubionej muzyki z jakiegoś składanego sprzętu? Na początku jakoś nie miałem pomysłu co to by miał być za sprzęt aż do chwili, gdy w szufladzie w kuchni zobaczyłem silikonowy lejek. I wtedy mnie olśniło: przecież z tego wyjdzie super patefon! Jako że wszystkie dekoracje i rekwizyty w spektaklu to jakaś forma różnych tekturowych pudełek, wziąłem jakieś pudełko po chińskim filamencie, przyłożyłem do tego lejek... powstała tak absurdalna konstrukcja że stwierdziłem, że muszę to zrobić! Przeszukanie moich rupieci zaowocowało kilkoma elementami: ESP32 Lite (chciałem mieć zasilanie z baterii, ale o tym potem), mały wzmacniaczyk z potencjometrem na PAM8403 (kiedyś kupiony w Botlandzie ale ślad po nim w sklepie zaginął) no i oczywiście mały głośnik. Jako że klika urządzeń na takim zestawie już kiedyś zrobiłem, zabrałem się do pisania prostego programu... No i się zaciąłem. Okazało się, że najnowsze wersje boardu Arduino IDE oraz mojej ulubionej biblioteki ESP8266Audio nie przewidują działania takiego urządzenia. Nie bo nie, i koniec. Próby jakiegokolwiek zmuszenia tego do pracy owocowały wywaleniem jakiegoś mało zrozumiałego błędu i resetem ESP. Na szczęście miałem zarówno starą wersję biblioteki (taką sprzed trzech lat) jak i możliwość przełączenia boardu na wersję 2.x mój ESP wydał z siebie jakiś sensowny dźwięk, postanowiłem więc że nie będę wnikał w powody owego niedziałania i zastosuję po prostu te wersje. Patefon jak to patefon - musi być nakręcany, zdarta płyta ma szumieć i trzeszczeć no i oczywiście przeskakiwać. Czyli sekwencja działań ma być taka: Po włączeniu zasilania patefon czeka Po pokręceniu korbką (czemu ma towarzyszyć odgłos nakręcania) uruchamia się "płyta". Po kilku sekundach płyta się zacina Stuknięcie w obudowę ma spowodować, że igła przeskakuje dalej i patefon gra już bez przeszkód (zwrotka w pętli aż do wyłączenia). Jednocześnie dioda powinna pokazywać stan baterii (pali się - bateria nowa, miga - bateria zużyta). Tak więc schemat połączeń patefonu wygląda mniej więcej tak: I od razu pierwsza uwaga: wzmacniacz ma oczywiście potencjometr z wyłącznikiem, ale służy on do załączenia zasilania wyłącznie do tego wzmacniacza. Trezeba więc zasilanie do reszty układu pobrać zza wyłącznika, jak na poniższym zdjęciu: Główną płytkę wykonałem na bazie płytki uniwersalnej. Z lenistwa po prostu wlutowałem goldpiny od ESP32 do płytki - raczej nie przewiduję demontażu, a w razie czego zawsze można dmuchnąć hotairem. Wlutowane są tylko potrzebne piny. Jak widać na płytce oprócz ESP jest niewiele. Potencjometr służy do wstępnego ustalenia maksymalnej głośności, a wszystko jest spięte moimi ulubionymi złączami. Jeśli chodzi o zasilanie wykorzystałem fakt, że ta wersja modułu ESP32 pozwala na zasilanie z akumulatora. Tak więc połączyłem to tak jak na schemacie powyżej: dioda Schottky'ego zapobiega próbom ładowania baterii przy podłączonym USB (raczej by tego nie przeżyły) a jednocześnie pozwala na prawidłowe filtrowanie napięcia przez kondensator (próby uruchamiania urządzenia bez niego powodowały dość silne zakłócenia w głośniczku, a przy tym ESP okazjonalnie się resetował). Napięcie zasilające podłączone jest do wyprowadzenia BAT+ (pin nie jest wyprowadzony, w pierwszych próbach użyłem po prostu odpowiedniej wtyczki, w docelowym układnie nie miało to większego sensu więc przewód zasilający został po prostu przylutowany do wyprowadzenia z tyłu gniazda). No i kwestia korbki. Długo kombinowałem nad mechanicznym układem (jakieś zębatki z zapadkami które by ładnie trzeszczały, ale jak zrobić żeby patefon zaczął grać?) - potem zdałem sobie sprawę że przecież równie dobrze trzeszczeć może głośniczek, korbka ma napędzać najtańszy enkoder a program ma po prostu wykryć kiedy korbką się kręci (trzeszczy) a kiedy przestaje (gra). Proste, tanie i raczej mało awaryjne. Ostatnim elementem (oprócz diody sygnalizującej włączenie i stan baterii) jest czujnik stuknięcia. Początkowo chciałem użyć MPU6050 ale szkoda mi było użycia go wyłącznie jako akcelerometru, lepiej sprawdził się tu ADXL345. Wszystkie elementy połączone w (działającą) całość przedstawiają się tak: Teraz przyszła kolej na włożenie wszystkiego do pudełka, na razie bez montowania tuby. Ponieważ pudełko było w jednym miejscu lekko uszkodzone - zamaskowałem to tabliczką producenta na tylnej ściance. Skąd nazwa "His Slave's Voice" - to już pozostawiam domyślności czytelnika I jeszcze jedno: jeśli ktoś się uważnie przyjrzy zdjęciu może mieć wrażenie, że część terytorium naszego pięknego kraju (konkretniej Kobierzyce) znajduje się w Chinach... Zamontowanie "tuby" i przyklejenie koszyka na baterie zakończyło pracę nad konstrukcją elektroniczno - mechaniczną. Po złożeniu wysięgnika tuby wygląda to tak (przednia pokrywka jest otwarta - widać, że można w łatwy sposób wymienić baterie): Kilka słów o programie: Jak wspomniałem - użyłem starszej wersji boardu i biblioteki. Prawdopodobnie dałoby się to zrobić na najnowszych wersjach ale po prostu nie chciało mi się kombinować. Problem wystąpił wyłącznie wtedy, gdy chciałem w bibliotece ESP8266Audio użyć wbudowanego DAC jako wyjścia dźwięku. Dla tak prostej aplikacji nie cjhciało mi się kombinować. Dźwięk działa w oddzielnym tasku na rdzeniu 0 - ponieważ nie ma tu WiFi.BT, ma cały rdzeń do dyspozycji. Teoretycznie można by było rozbić na taski wszystkie elementy (akcelerometr, korbka, bateria) ale równie dobrze pracują w pojedynczej pętli. Komunikacja między poszczególnymi sekcjami programu odbywa się poprzez zmienne globalne - w tym przypadku nie trzeba było używać jakichś konstrukcji typu kolejka, semafor czy mutex, krótkie spojrzenie na kod powinno wyjaśnić wszystko. No i na zakończenie ciekawostka: przewidziałem możliwość uszkodzenia akcelerometru (patrz kod). Po zmontowaniu okazało się że akcelerometr nie działa, a zabezpieczenie doskonale zdało egzamin (płyta zacięła się 5 razy i pojechała dalej). Przyczyna była prosta: udało mi się przewiercić taśmę łączącą akcelerometr z główną płytką Tak wygląda patefon gotowy do pracy. Poniżej filmik (bałem się czy automaty YT nie uznają fragmentu "starego niedźwiedzia" za naruszenie praw autorskich, ale jakoś to przepuściły: Dźwięk powstał przy użyciu Audacity (montaż) i sox (niestety, Audacity nie chciał mi wygenerować pliku raw 16 khz 8-bit unsigned co nie powinno dziwić, bo nie do tego służy). Wynikowy plik raw został przekształcony na dane w pliku .h prostym programem w Pythonie: #!/usr/bin/env python3 muza=open('px1.raw','rb').read() print("static const uint8_t muza1[]={") pams=[] lem=len(muza) while len(muza) > 0: partia=muza[:16] muza=muza[16:] parl=list(partia) for n,a in enumerate(parl): #korekta wartości, oryginalne były za ciche a=((a-128) * 3) + 128 if a < 0: a=0 elif a > 255: a=255; parl[n]=a parl=','.join(list("%d" % x for x in parl)) pams.append(parl) print(',\n'.join(pams)) print('};') print ("static const int muza1len = %d;" % lem) #wartości 631808 i 767760 to numery sampli z Audacity przed resamplingiem print ("static const int muza1rpt = %d;" % int((lem *631808)/ 767760)) No i oczywiście kod całości w załączniku: Patefon.zip Zapraszam do Teatru
- 1 odpowiedź
-
- 10
-
-
ESP32 ESP32 Logger.S - samochodowy logger/CAN (re)transmitter z ekranem
sorek opublikował temat w Projekty - DIY
Dzień dobry serdecznie wszystkim! Zachęcony na wykopie przez Forbota zdecydowałem się podzielić swoim projektem, który bazowo miał służyć tylko mi jako On Board Computer do mojego starego BMW E36. Projekt jednak się rozwinął i obecnie jest pełnoprawnym urządzeniem/development boardem bazującym na ESP32. O urządzeniu dokładniej można poczytać na stronie Logger.Sorek.uk - na tej stronie będą też pojawiać się nowości w firmware/hardware gdyż jego development wciąż trwa! Z czego jest zbudowany? Ekran 2.8" TFT ILIi9341 (bardzo podobny do tego) z dotykiem i 65k kolorami (z opcją na założenie dowolnego innego ekranu), Port OBDII, CAN i K-line (ISO 9141), Obsługa kart SD do 32GB, 4 analogowe 0-5V inputy (np. do czujnika sony szerokopasmowej AFR), USB-C, i dlatego iż używam ESP32 mamy też: Bluetooth, Wifi oraz 2 porty serial - jeden do komunikacji z K-line drugi do USB (3 można używać bez ekranu). Warto nadmienić, że dzięki zastosowaniu chipu FTDI oraz demuxera seriala, urządzenie można używać (po przełączeniu pinu w programie urządzenia) bezpośrednio USB <-> K-line dzięki temu jest 100% kompatybilność z programami diagnostycznymi do samochodów i programatorami ECU. Co daje nam k-line? Prędkość przesyłu OBDII typu ELM327 jest bardzo niska. Przy k-line przy bazowej prędkości 9600 możemy osiągnąć 8-20 Hz (w porównaniu do 0.2-1 Hz w wypadku ELMa) a jeśli mamy odpowiednie ECU np. nowsze MS42/MS43 (które możemy znaleźć w takich autach jak BMW E46, E38 czy E39) nawet dochodzącą do 33 Hz! Do daje niesamowitą rozdzielczość. Natomiast komendy typu "telegram" pozwalają wybrać nam jakie elementy chcemy loggować czy retransmitować po CAN. Urządzenie jest w pełni konfigurowalne i firmware z którym go sprzedaje posiada takie funkcje jak konfigurowalne definicje, retransmisje CAN, logi do karty SD, autosleep, autolog na triggerze (np. gdy obroty silnika lub przepustnica przekroczy odpowiednią wartość), oraz Gui które stworzyłem na bazie bibliotek tft_eSPI oraz lvgl. Do urządzenia stworzyłem bibliotekę DS2.h która współpracuje z Arduino. Dzięki temu programowanie na urządzenie jest proste. Przykłady w bibliotece stworzyłem tak, by każdy mógł się połapać o co chodzi i część przykładów ma wgrany OTA update prosto z karty SD. Wystarczy wrzucić plik update.bin na kartę SD, odpalić urządzenie i można wrócić do oryginalnej wersji Firmware - które to wersje również wypuszczam dla wszystkich kupujących urządzenie. Oczywiście przy własnym programowaniu urządzenia możliwości są nieskończone! Jak obecnie wygląda i działa urządzenie można zobaczyć poniżej: Projekt płytki wraz ze stabilizatorami 5V i 3.3V stworzyłem wraz z pomocą bardziej doświadczonych kolegów. Wcześniej urządzenie działało na Arduino i wyglądało zupełnie inaczej! Cena urządzenia to obecnie £100 lub £130 z obudową. W cenie wliczony jest support - powiem tylko że sporo nocy spędziłem na pomoc klientom z Australii i USA by pomóc w integracji Firmware z urządzeniami typu RaceCapture lub JB4! Przykłady zastosowania jako CAN sniffer/transmitter po serialu: Zapraszam do komentarzy! -
ESPoBOT czyli robot mobilny RC z chwytakiem i kamerą FPV
marcinus opublikował temat w Projekty - DIY roboty
No cześć, kilka miesięcy wzlotów i upadków, kłótni z żoną i w końcu jest .... ESPoBOT Robot gąsienicowy RC z kamerą FPV i chwytakiem. Sterowanie oparte o ESP32-wroom 32D. Zacząłem od Arduino, ale z uwagi na problemy z komunikacją dwustronną NRF24L01, przesiadłem się na ESP32. Napędem są 2 silniki 9Vdc z przekładnią 87:1 na podwoziu gąsienicowym z regulacją prześwitu, kontrolowane przez sterownik oparty o układ TB6612. Prędkość silników regulowana płynnie w zakresie 0-30cm/s (1km/h). Robot wyposażony w chwytak umożliwiający chwycenie detalu, podniesienie/opuszczenie z użyciem 3 serw. Kontrola zaciśnięcia chwytaka zrealizowana poprzez pomiar prądu serwa. Regulacja prędkości serw. Zainstalowana kamera 1200TVL do przekazywania obrazu fpv 5.8GHz na telefon, PC lub wyświetlacz AV. Zasięg nadajnika do 500m. Robot wyposażony w oświetlenie LED RGB. Całość zasilana z akumulatora LiPo 1800mAh 11,1V 20C i zabezpieczona bezpiecznikiem polimerowym 4A. Dodatkowo akumulator zabezpieczony programowo przed rozładowaniem. Komunikacja dwustronna z padem na częstotliwości 2.4GHz (ESP-NOW) i zasięgu do 250m. Pad wyposażony w 2 joysticki, 6 guzików, potencjometr, enkoder oraz wyświetlacz OLED 128x64 do wyświetlania parametrów urządzenia. Zasilany z akumulatora 9V 650mAh USB. -
Słowo wstępu Jeśli chodzi o tworzenie własnych PCB, zawsze (oprócz zapachu lutownicy) w powietrzu zawsze wisi trochę magii... Eksplozja kreatywności kończy się urządzeniem, które będzie służyć do rozwiązania konkretnego problemu i ułatwi życie. W naszym starym mieszkaniu miałem gniazdka elektryczne sterowane za pomocą radiowej częstotliwości 433MHz, kontrolowane przez ESP8266. Jednak teraz, gdy zbliżamy się do kolejnych kamieni milowych w naszej wspólnej drodze z moją wspaniałą żoną, w mojej głowie zrodziła się pewna idea i postawiłem sobie wyzwanie, aby zbudować własną Bramkę Zigbee. Chciałbym mieć pełną kontrolę nad urządzeniami, kontrolę nad przepływem między Zigbee a internetem oraz powiadomienia głosowe w jednym urządzeniu. Szczegóły techniczne Pierwszy krok, znany również jako MVP, polegał na stworzeniu urządzenia, które działa jako proxy Ser2Net (zdalny port szeregowy) i potrafi obsługiwać wbudowaną integrację ZHA w Home Assistant poprzez protokół EZSP. Urządzenie miało docelowo również obsługiwać powiadomienia dźwiękowe i posiadać wbudowany czujnik temperatury. Wszystkie kroki zakończyły się powodzeniem. Design funkcjonalny opiera się o następujące układy: ESP32S3 - główny procesor aplikacyjny ze wsparciem USB oraz WiFi. EFR32MG1 (w postaci modułu Ebyte E-180) - Zigbee Network Co-processor (NCP). MAX98357A - Wzmacniacz i kodek audio I2S. Ponadto na płytce zamontowane są: filtr wejściowy zasilania oparty na dławiku ferrytowym w konfiguracji LC, celem filtracji ewentualnych sygnałów RF. układ ograniczający prąd do 1A (~1.5A peak), dodatkowo działający jako soft-start (zapobiega tzw. inrush current, co w przypadku USB jest ważne). regulator napięcia 3.3V (low noise, ultra-low dropout). Software oparty jest o ksIotFrameworkLib a aplikacja składa się z kilku komponentów: AudioPlay - komponent odpowiedzialny za obsługę audio, w tym sterowanie częstotliwością CPU (dekodowanie wymaga 240MHz a bazowo jest 80MHz). Ser2Net - komponent pośredniczacy w komunikacji między HomeAssistant a procesorem sieci Zigbee. TempSensor - komponent odpowiedzialny za pomiary temperatury. Funkcjonalności takie jak zarządzanie połączeniem WiFi, komunikacja MQTT czy konfiguracja parametrów są dostarczane poprzez framework. Galeria multimediów Linki: Strona projektu na hackaday.io
-
Taki mały projekcik - coś dla bezwzrokowców. Czyli nie tylko dla niewidomych i słabowidzących, ale również dla tych, którzy nie mają ochoty albo możliwości gapienia się na wyświetlacz... Czyli coś w rodzaju wypasionego termometru albo (jak kto woli) mini-stacji pogodowej. Ale zacznę może od genezy projektu. Szukając pewnego sprzętu do domu natrafiłem na osobę poszukującą mówiącego termometru pokojowego. Ktoś tam rzucił jakimiś linkami... z ciekawości zajrzałem. No i oczywiście pierwszy do Altix. Termometr co prawda niedrogi, ale za yi jak zwykle w tej firmie "kłopoty ze stanem magazynowym". Kolejne ceny wyglądały raczej mało zachęcająco... Jako że taki "gadający termometr z prognozą pogody" stanowi integralną część mojego "wygodnego domu" zacząłem zastanawiać się, czy coś podobnego (tylko bez tej "wygodnodomowej" otoczki) mogłoby się komuś przydać. No i po dłuższym zastanowieniu wyszło mi coś takiego: W moim przypadku sprawdza się to idealnie, tak że pewnie znaleźli by się zainteresowani... Przede wszystkim musi być to urządzenie DIY. Z uwagi na możliwości zastosowania różnych podzespołów raczej nie byłoby możliwe zaprojektowanie PCB czy obudowy - poza tym urządzenie powinno być możliwe do zbudowania przez początkującego adepta sztuki elektroniki (z możliwością wsadzenia tego na płytkę stykową włącznie). Dodatkowo wszystkie elementy muszą być dostępne w handlu, a przede wszystkim tanie! Wybrałem ESP32 jako "serce" urządzenia. Powodów było kilka, ale najważniejsze było to, że mam dobrze przetestowany syntezator mowy właśnie na ESP. Również ceny modułów są atrakcyjne. Jednocześnie nie mogę wymagać umiejątności użerania się z wersjami bibliotek (które lubią gryźć się albo z definicją płytki, albo ze sobą). Co prawda źródła programu dla Arduino powinny być dostępne, ale warto zadbać o to, aby ktoś kto nie ma zielonego pojęcia o programowaniu mógł wrzucić soft na płytkę i skonfigurować całość. Tak że wymagana by była równioeż wersja binarna, możliwa do prostego załadowania z pomocą esptool (Linux, Mac) czy FlashTool (Windows). Z wstępnych założeń wyszło mi coś takiego: Trzy typy komunikatów: Najkrótszy to tylko godzina i temperatura. Dłuższy to godzina, data, temperatura oraz dodatkowe parametry zależne od możliwości zastosowanych podzespołów (np. ciśnienie i wilgotność) Najdłuższy to ten sam co dłuższy, ale wzbogacony o dane ściągnięte z Internetu Postanowiłem wykorzystać serwis open-meteo.com. Tak więc zgodnie z możliwościami serwisu dane muszą zawierać bieżące warunki pogodowe oraz skróconą prognozę pogody. Uznałem, że rozwiązanie które mam w domu powinno się sprawdzić - czyli prognoza na dziś i jutro, lub po którejś godzinie na jutro i pojutrze. Konieczna jest też możliwość ustawienia wszystkiego co się da za pomocą terminala serial - po to, aby nie było trzeba robić jakichś zależności w czasie kompilacji. Jeśli chodzi o elektronikę, uznałem, że podstawową płytką będzie ESP32 DevKit. Ma wystarczającą ilość pamięci (zarówno RAM jak i Flash), wszystkie potrzebne interfejsy a co ważniejsze - jest tania i dostępna. Konieczny jest również jakiś wzmacniacz I2S - czyli znów najtańsza opcja: MAX98357. Dla niewtajemniczonych: monofoniczny dekoder I2S zawierający wzmacniacz mocy do 3W. Cena jest również zachęcająca, z dostępnością nie ma problemu. Do tego jeden przycisk (dwa to o jeden za dużo), jakiś zasilaczyk 5V o mocy wystarczającej na zasilenie wzmacniacza - i to wszystko. Jako czujniki postanowiłem dopuścić następujące: W pomieszczeniu - BMP180, BMP280, DS18B20. Na zewnątrz - DHT22, DHT11, DS18B20 z możliwością bezprzewodowego połączenia. Oczywiście można nie podłączać któregoś z urządzeń - w najprostszym (najtańszym) przypadku można zastosować tylko DS18B20 do pomiaru temperatury w pomieszczeniu, czas oraz warunki bieżące dla lokalizacji pobierać z Internetu. DHT11 oczywiście nie powinien być stosowany, ale po prostu taki mam Jednocześnie w razie potrzeby byłbym w stanie dorobić BME280 jako czujnik wewnętrzny (jak to słusznie @mkwiatkowski zauważył, odczyt wilgotności może być potrzebny przy pracującej klimatyzacji). Dodatkowym wyposażeniem może być zegarek DS3231 (jeśli istnieje możliwość utraty połączenia z Internetem). Można również dodać detektor IR aby sparować urządzenie z dowolnym pilotem. Prace zacząłem już jakiś czas temu, ale nie chciałem nic pisać dopóki nie uzyskałem pewności, że zadanie jest wykonalne. W tej chwili działają wszystkie moduły, urządzenie sobie gada, coś tam mierzy, nawet serwer www wystawia do bieżących ustawień... Tylko pytanie: czy ktoś to wykorzysta? Czy przypadkiem nir planuję zrobienia kawału dobrej, nikomu niepotrzebnej roboty? Bo co prawda pisząc program musiałem rozwiązać kilka dość interesujących (z punktu widzenia Arduinowo-ESPowego programisty) problemów, ale więcej nie przewiduję, a czego trzeba to już się nauczyłem I dla zachęty: plik mp3 z prognozą pogody (nagrany na pececie, ale to taki sam syntezator jak na ESP32): meteo.zip Prosiłbym o wypowiedzi czy dalsza praca ma sens. czy mam sobie darować i zająć się czymś bardziej pożytecznym. A może jakieś pomysły?
-
Brak komunikacji SPI miedzy ESP32 a czytnikiem RFID-PN532
startrek1p2p opublikował temat w Arduino i ESP
Hej, walczyłem teraz kilka dni z uruchomieniem modułu PN532 podłączonego do ESP32 niestety nieudało mi się ich zainicjować mimo korzystania z dedykowanej biblioteki oraz kilku modułów(trzech) PN532 oraz innych ESP32(też trzech). Wiem że aby komunikacja SPI była uruchomiona trzeba załączyć odpowiednie zwory lub przyciski, u mnie to pierwszy w dół drugi w górę: Wersja biblioteki to 1.3.3, a podłączenie to: PN532_SCK -> (18) PN532_MOSI -> (23) PN532_SS -> (32) PN532_MISO -> (19) Znalazłem na Internecie aby zmienić bitrate z 1MHz do 100KHz nic nie pomogło, tak samo dodanie . delay(10); w funkcji : bool Adafruit_PN532::sendCommandCheckAck Gdzie indziej znalazłem aby dodać na zasilanie kondensator 47uf dalej z tym samym skutkiem. Jako programu testowego używam przykładu z biblioteki dla SPI oto kod: **************************************************************************/ /*! @file readMifareClassic.pde @author Adafruit Industries @license BSD (see license.txt) This example will wait for any ISO14443A card or tag, and depending on the size of the UID will attempt to read from it. If the card has a 4-byte UID it is probably a Mifare Classic card, and the following steps are taken: Reads the 4 byte (32 bit) ID of a MiFare Classic card. Since the classic cards have only 32 bit identifiers you can stick them in a single variable and use that to compare card ID's as a number. This doesn't work for ultralight cards that have longer 7 byte IDs! Note that you need the baud rate to be 115200 because we need to print out the data and read from the card at the same time! This is an example sketch for the Adafruit PN532 NFC/RFID breakout boards This library works with the Adafruit NFC breakout ----> https://www.adafruit.com/products/364 Check out the links above for our tutorials and wiring diagrams These chips use SPI to communicate, 4 required to interface Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! */ /**************************************************************************/ #include <Wire.h> #include <SPI.h> #include <Adafruit_PN532.h> // If using the breakout with SPI, define the pins for SPI communication. #define PN532_SCK (18) #define PN532_MOSI (23) #define PN532_SS (32) #define PN532_MISO (19) // If using the breakout or shield with I2C, define just the pins connected // to the IRQ and reset lines. Use the values below (2, 3) for the shield! #define PN532_IRQ (2) #define PN532_RESET (3) // Not connected by default on the NFC Shield // Uncomment just _one_ line below depending on how your breakout or shield // is connected to the Arduino: // Use this line for a breakout with a SPI connection: Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS); // Use this line for a breakout with a hardware SPI connection. Note that // the PN532 SCK, MOSI, and MISO pins need to be connected to the Arduino's // hardware SPI SCK, MOSI, and MISO pins. On an Arduino Uno these are // SCK = 13, MOSI = 11, MISO = 12. The SS line can be any digital IO pin. // Adafruit_PN532 nfc(PN532_SS); // Or use this line for a breakout or shield with an I2C connection: // Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET); void setup(void) { Serial.begin(115200); while (!Serial) delay(10); // for Leonardo/Micro/Zero Serial.println("Hello!"); nfc.begin(); uint32_t versiondata = nfc.getFirmwareVersion(); if (!versiondata) { Serial.print("Didn't find PN53x board"); while (1) ; // halt } // Got ok data, print it out! Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX); Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); Serial.println("Waiting for an ISO14443A Card ..."); } void loop(void) { uint8_t success; uint8_t uid[] = {0, 0, 0, 0, 0, 0, 0}; // Buffer to store the returned UID uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) // Wait for an ISO14443A type cards (Mifare, etc.). When one is found // 'uid' will be populated with the UID, and uidLength will indicate // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); if (success) { // Display some basic information about the card Serial.println("Found an ISO14443A card"); Serial.print(" UID Length: "); Serial.print(uidLength, DEC); Serial.println(" bytes"); Serial.print(" UID Value: "); nfc.PrintHex(uid, uidLength); if (uidLength == 4) { // We probably have a Mifare Classic card ... uint32_t cardid = uid[0]; cardid <<= 8; cardid |= uid[1]; cardid <<= 8; cardid |= uid[2]; cardid <<= 8; cardid |= uid[3]; Serial.print("Seems to be a Mifare Classic card #"); Serial.println(cardid); } Serial.println(""); } } W kodzie sprawdzałem różnego konstruktora tej klasy, zawsze z takim samym skutkiem: Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS); Adafruit_PN532 nfc(PN532_SS); Czy ktoś się napotkał z takim problemem i go rozwiązał ? Ja już nie mam pomysłów co może być nie tak, oraz jak to naprawić. Poniżej załączam logi z włączonego debugowania PN532DEBUG : Hello! Sending : 0x0, 0x0, 0xFF, 0x5, 0xFB, 0xD4, 0x14, 0x1, 0x14, 0x1, 0x2, 0x0, TIMEOUT! Sending : 0x0, 0x0, 0xFF, 0x2, 0xFE, 0xD4, 0x2, 0x2A, 0x0, TIMEOUT! Didn't find PN53x board -
OpenFontRender (ofr) i strtok_r brak wyświetlania w pętli loop.
99teki opublikował temat w Programowanie
- gdzieś na forum znalazłem program obslugujący strtok_r do dzielenia tekstu wg separatorów. - sam podprogram dzielenia tekstu działa i wyświetla poprawnie tekst na TFT zapisywany w tablicy. - ta sama tablica w pętli loop jednak nie chce się wyświetlać dając błąd o przekroczeniu bufora 256 znaków w OpenFontRender. - jedna tablica DX raz się wyświetla, drugi raz już nie, coś chyba nagrzebałem, tak jakby brakowało w tablicy znaku NULL na końcu. - proszę o pomoc bo nie daje rady z tym problemem. char* DX[10]; void testChar() { char *str; char sz[] = "Prognoza na jutro. Niewielki deszcz. "; char *p = sz; char *del = "."; // separator int indeks = 0; // ------------------------------------- while ((str = strtok_r(p, del, &p)) != NULL) { DX[indeks] = str; ofr.printf("%s\n", DX[indeks]) // tu wyswetla poprawnie indeks += 1; } } // .... loop if (millis() > czas) { Serial.println("."); tft.fillScreen(TFT_BLUE); testChar(); for(wiersz = 0; wiersz < 6; wiersz++) { ofr.setCursor(50, 20 * wiersz); //ofr.printf("%s\n", DX[wiersz]); // tutaj blad ! } czas = millis() + 10000; } -
https://github.com/rkalwak/WeatherStation Wygodniej mi podlinkować do Githuba, gdzie mogę prościej aktualizować i przechowywać obrazki. Wiem, że aplikacja pokazuje trochę głupoty - rozłączyłem coś przenosząc. Stacja pogodowa: Z Arduino i zaraz potem ESP32 bawię się już ponad dwa lata. W sumie to zacząłem bardziej z https://www.nanoframework.net i tam portowałem kilka bibliotek, bo C++ to jest dla mnie epoka kamienia łupanego Padł pomysł, żeby zrobić sobie pogodynkę na bazie ESP32 zaprogramowaną w C# przez NanoFramework. W międzyczasie poznałem Suplę i zacząłem od rolet na SRW-01 a potem na zaprogramowaniu ESP przez GUI-Generic i się zaczęło... Doszedłem do momentu że dopisałem sobie "Kanał ogólnego przeznaczenia", umiem to zbudować ale ze względu na absolutny brak dokumentacji nie umiem tego uruchomić w Dokerze i się poddałem... Chciałem też napisać komunikację z Supla w C# ale z braku czasu odpuściłem. Prędzej czy później to zrobię... Poczytałem trochę co będzie potrzebne, kupiłem niekompletna stacje Sainlogic WS3500 na A****** i tak powstało to cudo, które niedlugo zawiśnie na płocie Funkcjonalności: * 2x temperatura, * wilgotność, * ciśnienie atmosferyczne, * siła i kierunek wiatru, * ilość opadów, * pomiar napięcia baterii, Części: * niekompletna stacja Sainlogic WS3500 - bez tabletu i zasilacza -> nie można jej skonfigurować, ale nieuszkodzona, wyrwana za 100zł * ESP32 DevKit * HDC1080 - czujnik wilgotności i temperatury * MS5611 - czujnik ciśnienia * DS18B20 - czujnik temperatury przy gruncie * LTR390 - czujnik UV i natężenia światła wbudowany w stację, użyty jako ten drugi * AS5600 - enkoder magnetyczny, zastąpił czujnik kierunku wiatru w stacji * czujnik Halla - zastąpil czujnik siły wiatru w stacji , ten od czujnika opadów został * 2 baterie 18650 LiPo i koszyk * lx-lifc1-n - Moduł BMS z ładowarką do akumulatorów Li-ion 2S z USB typu C i obsługą QC, możliwość ładowania z USB albo panelu. * przetwornica step-down * panel fotowoltaiczny dający 7V i ok. 1W wbudowany w stację Schemat i sposób działania ESP32 jest wyciągnięte do zewnętrznej puszki wraz z zasilaniem, ze względu na rozmiar stacji, po prostu się w niej nie mieści. Użyłem 12 żyłowego, 2 metrowego przewodu telefonicznego do połączenia płytki w stacji z tą w puszce. Wykorzystałem panel fotowoltaiczny z kupionej stacji do ładowania akumulatorków, jednak jest za słaby aby w pełni naładować 2 akumulatorki i co najwyżej wydłuża czas pracy na bateriach, który bez panelu wynosi ponad dobę pracy non stop. Stacja wykorzystuje bibliotekę SuplaDevice do komunikacji z Supla i robi to co 10 sek. Problemy 1. Brak schematów do stacji Sainlogic WS3500 i opisów na płytkach Metodą prób i błędów wymyśliłem jak wykorzystać niektóre wbudowane czujniki. LTR390 po wyjęciu i zobaczeniu oznaczeń pinów podpiąłem do ESP i puściłem skaner I2C a potem po adresie znalazłem czujnik, po dobraniu biblioteki zadziałał Niestety ten sam manewr nie zadziałał z czujnikiem temperatury i wilgotności. Mimo, że wygląda jak AHT XX i ma właściwy adres I2C nie działa z żadną biblioteką do tych czujników. Czujnik Halla dla opadów zadziałał. Doświadczalnie trzeba było dobrać jego pojemność. Czujnik Halla dla siły wiatru najpierw działał a potem przestał... Kupiłem inny i działa, ale obserwując dane w Supli, muszę poprawić obliczanie prędkości wiatru. Po kilku godzinach czytania okazało się, że kierunek wiatru jest zrealizowany przez dwa czujniki Halla i coś jeszcze, ale nie rozkminiłem tego... Kupiłem za to enkoder magnetyczny, który podaję kąt w stopniach - idealnie. 2. Zasilanie Pierwsza opcja: 1 akumulatorek 18650 i LDO na 3.3V, owszem ESP się uruchomi, ale z Wifi już się nie połączy przy obciążeniu wszystkimi czujnikami. Użyłem AMS1117 jako LDO. Druga opcja: 2 akumulatorki 18650 i przetwornica StepDown ustawiona na 5V, podane na VIN pin. Działa, ale chyba marnuje możliwości akumulatorków. Dodatkowo dwa akumulatorki to za dużo aby naładować z małego panela dającego max 7V, więc chyba skończy się to większym panelem Trzecia opcja: zasilanie z USB - nie mam póki co dobrego miejsca, żeby stacja wisiała na rozsądnej i dostępnej wysokości i był bezpieczny i wygodny dostęp do prądu. Czwarta opcja: czytałem, że ESP32 DevKit może przyjąć do 12V na VIN pin, podłączył bym bezpośrednio baterie, ale jakoś się nie odważyłem jeszcze, ktoś próbował? 3. Dokładność pomiarów ADC w ESP32 jest trochę słabe, pomiar napięcia akumulatorków jest "mniej więcej", wahania rzędu 0.3V. HDC1080 się bardzo grzeje w obudowie radiacyjnej i generalnie w południe pokazuje głupoty w stosunku do DS18B20 wiszącego luźno w cieniu, aczkolwiek wilgotność pokazuje nieźle co jest dziwne. Więc muszę to przemyśleć. Siła wiatru zdecydowanie będzie lepiej oprogramowana bo nie znając możliwości wiatraczka błądzę w obliczeniach. 4. Supla i jej możliwości Niestety Supla nie ma jeszcze generycznego kanału, któremu można przypisać jednostkę lub kanału stacji pogodowej przewidującego więcej parametrów więc niektóre pomiary musiały zostać przepchnięte przez kanał temperatury, np. napięcie akumulatorków czy kierunek wiatru jako kąt z zakresu 0-360 względem północy. Plany na przyszłość - Pewnie większy panel fotowoltaiczny. - Pomiar napięcia z panelu. - Pomiar poboru prądu przez ESP - mam już moduł INA219 obczajony - Własne płytki zamiast breakout boardów, a przynajmniej ta zewnętrzna. - Pomiar zanieczyszczenia powietrza (SDS0111). - Po przekroczeniu ustawionej godziny przechodzić na tryb "deep_sleep" i budzić co 15 min na minutę aby pomiar wiatru i deszczu był choć trochę użyteczny. Następnie rano o ustalonej godzinie budzić się aby pracować do wieczora w celu wydłużenia pracy na akumulatorkach. - Albo w Supli pojawi się kanał ogólnego przeznaczenia, który będzie wspierał dane, które mogę wysłać albo zgłosze PR ze zmianami
- 29 odpowiedzi
-
- 6
-
-
Operacje plikowe, czy mogę po raz drugi przypisać wartość pod wskaźnik?
_LM_ opublikował temat w Programowanie
Tak jak w tytule plus dodatkowe pytanie, wytłumaczę na podstawie kodu. Mam pamięć która przechowuje parametry transmisji zapisane jako csv (świadomie zrezygnowałem z JSON) następnie po zainstalowaniu systemu plików i odnalezieniu tego z parametrami "reg1.txt"[będzie .csv] robię dwie rzeczy: zliczam ile jest linii(wierszy) w pliku i przesyłam kolejne wiersze do funkcji dekodującej. while ((d = readdir(dh)) != NULL) { printf("DIR %s\n", d->d_name); if(0 == (strcmp(d->d_name,"reg1.txt"))) { FILE * f = fopen("/usb/reg1.txt", "r"); char line[128]; int l = 0; if(mdbTask != NULL){ vTaskSuspend(mdbTask); while(fgets(line, sizeof(line), f)) //where sizeof(buf) is the length of //line you anticipate reading in. { //do something with buf here; //The input of fgets will be NULL as soon //as its input fp has been fully read, then exit the loop l++; } decode_csv(NULL,l); FILE * f = fopen("/usb/reg1.txt", "r"); // printf("test1\n"); while(fgets(line, sizeof(line), f)) //where sizeof(buf) is the length of { decode_csv(line, 0); } Pierwsze otwarcie pliku ma na celu zliczenie linii, jest to później potrzebne gdyż stosuję dynamiczną alokację pamięci. Problem był kiedy chciałem przesłać do funkcji "decode_csv" kolejne linie z pliku. Poradziłem sobie przypisując ponownie wskaźnik file * f nie jestem pewien czy to prawidłowe rozwiązanie? Ewentualnie czy istnieje inny sposób na zliczenie wierszy w pliku tekstowym? Poniżej plik na którym testuję U1,1,4,2,0,1,V I1,1,4,2,6,1,A P1,1,4,2,18,1,W -
Często pojawia się konieczność użycia pakietu płytko w konkretnej wersji. Szczególnie dla ESP32, gdzie różne wersje pakietów mogą działać inaczej, a wersje maior w ogóle nie są ze sobą kompatybilne - jest to wręcz niezbędne. Artuino IDE oczywiście pozwala zainstalować dowolną wersję pakietu, ale tylko jedną konkretną, a proces instalacji wcale nie jest taki szybki. Dodatkowo jeśli dokonaliśmy jakichś zmian w katalogu pakietu (w moim przypadku to dodanie nowych układów partycji i modyfikacja boards.txt) zaczyna być to niespecjalnie wygodne... Postanowiłem więc zrobić prosty przełącznik wersji. Działa dla ESP32, ale można go oczywiście zmodyfikować tak, aby działał dla dowolnych płytek. Rozwiązanie jest niestety linux-only, ale po drobnych zmianach (ścieżki, polecenia powinny działać tak samo) powinno ruszyć na Macu. A na pewno nowoczesny Windows pozwoli na zrobienie podobnego przełącznika... pozwoli, prawda? Kod jest stosunkowo prosty: #!/bin/bash #położenie docelowego pakietu ESP32 dstitem=${HOME}/.arduino15/packages/esp32 #tu są różne wersje srcdir=${HOME}/.arduinoESP32 if [ -n "$1" ]; then if [ "$1" = "off" ] ; then #przygotowanie do instalacji rm ${dstitem} echo "Gotowy do instalacji nowej wersji" exit 0 fi if [ "$1" = "show" ] ; then if a=$(readlink ${dstitem}) ; then basename $a else echo "Brak skonfigurowanej wersji" fi exit 0 fi # parametr to numer wersji src=${srcdir}/$1 if [ ! -d ${src} ] ; then echo "Brak wersji $1" else ln -sfn ${src} ${dstitem} echo "Ustawiona wersja $1" fi else #bez parametrów listuje zawartość katalogu ls ${srcdir} fi Jak tego użyć? Trzeba utworzyć plik o zawartości jak wyżej (ja nadałem mu nazwę espversion) i nadać mu prawa do wykonywania. Skrypt najlepiej utworzyć w katalogu gdzie przechowujemy własne polecenia (np. ~/bin/). Następnie trzeba sobie stworzyć katalog, który będzie zawierał wszystkie wersje pakietów. Ja utworzyłem do tego celu katalog ~/.arduinoESP32. Następnie należy kolejno zainstalować potrzebne wersje, a po instalacji przenieść zawartość pakietu do odpowiedniego miejsca. Przykładowo: po instalacji wersji 3.0.4 należy wykonać coś w stylu: mv ~/.arduino15/packages/esp32 ~/.arduinoESP32/3.0.4 Pamiętać należy, że przed jakąkolwiek próbą instalacji nowej wersji należy usunąć link ~/.arduino15/packages/esp32 - jeśli tego nie zrobimy, wersja zostanie nadpisana! Mamy więc teraz następujące możliwości: Listowanie dostępnych wersji: espversion Pokazanie która wersja jest aktualnie wybrana: espversion show Przygotowanie do instalacji nowej wersji (usunięcie linku): espversion off Zmiana wersji: espversion <numer_wersji> Tak więc teraz można sobie zmieniać wersje w locie, np: espversion 2.0.17 włączy wersję 2.0.17, o ile taką mamy przygotowaną. Pamiętać równie należy, że przy przełączaniu wersji Arduino IDE powinien być wyłączony! Jeśli ktoś korzysta z mojego pyrduino, należy przed kompilacją wydać polecenie: ardu -clean I to tyle. Przyjemnego przełączania życzy ethanak
-
Mam do wykonania logger parametrów pewnego urządzenia, wymaganie jest między innymi takie aby po włożeniu pamięci pendrive logowanie zaczynało się automatycznie. Logger buduję w oparciu o esp32s3 który ma wsparcie sprzętowe USB msc, aby zapoznać się z komponentem udostępnionym przez espressif korzystam i ich przykładu https://components.espressif.com/components/espressif/usb_host_msc https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/msc Urządzenie ma być bezobsługowe, czyli użytkownik wkłada pamięć -> esp sprawdza czy istnieje folder ze specyficzną nazwą powiązaną z jego MAC adres, jeśli taki istnieje to tworzy w tym folderze plik z bieżącą datą i czasem oraz zaczyna zapisywanie danych. -> jeśli folder nie istnieje to musi go założyć i przejść powyższe kroki, ścieżkę buduję w taki sposób: uint8_t chipid[6]; esp_efuse_mac_get_default(chipid); char makedir[128]; sprintf(makedir,"/usb/LOGGER_%02X%02X%02X%02X%02X%02X",chipid[0], chipid[1], chipid[2], chipid[3], chipid[4], chipid[5]); bool directory_exists = stat(makedir, &s)==0; if (!directory_exists) { if (mkdir(makedir, 0775) != 0) { ESP_LOGE(TAG, "mkdir failed with errno: %s", strerror(errno)); } } Następnie plik z oznaczony aktualnym czasem np: 22082024_1444.csv i do niego zapisuje kolejne rekordy danych. Sprawa która mnie zastanawia to właśnie ten moment kiedy ktoś podejdzie i wyjmie pendrive z urządzenia, log będzie co sekundę, można liczyć że akurat pendrive zostanie wyjęty pomiędzy kolejnymi zapisami, ale co jeśli trafi akurat w moment dodawania wpisu do pliku? Można w programowy sposób na to zareagować lub muszę wykombinować jakiś mechanizm aby plik został poprawnie zamknięty przed wyjęciem pamięci z gniazda?
-
- poniżej interfejs do odczytu parametrów stacji Xiaomi z odczytem wilgotności i temperatury oraz stanu baterii zasilającej. - stacja Xiaomi pracuje na BLE 4.0, z M5Stack Core 0 zapewnia zasięg około 8 - 10 metrów. - w M5Stack wbudowano screen capture oraz shutdown. - aby korzystać z czujników, należy adres MAC jednego z czujników wpisać do linii [516] programu i skompilować cały projekt. - załączono także pliki binarne oraz adres programu do wgrywania do flasha esp32. Xiaomi_M5Stack_core_4.zip
-
- 5
-
-
- Xiaomi
- Screen Capture
-
(i 1 więcej)
Tagi:
