Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'Esp8266'.

  • Szukaj wg tagów

    Wpisz tagi, oddzielając przecinkami.
  • Szukaj wg autora

Typ zawartości


Kategorie forum

  • Elektronika i programowanie
    • Elektronika
    • Arduino i ESP
    • Mikrokontrolery
    • Raspberry Pi
    • Inne komputery jednopłytkowe
    • Układy programowalne
    • Programowanie
    • Zasilanie
  • Artykuły, projekty, DIY
    • Artykuły redakcji (blog)
    • Artykuły użytkowników
    • Projekty - DIY
    • Projekty - DIY roboty
    • Projekty - DIY (mini)
    • Projekty - DIY (początkujący)
    • Projekty - DIY w budowie (worklogi)
    • Wiadomości
  • Pozostałe
    • Oprogramowanie CAD
    • Druk 3D
    • Napędy
    • Mechanika
    • Wydarzenia
    • Sprzedam/Kupię/Zamienię/Praca
    • Inne
  • Ogólne
    • Ogłoszenia organizacyjne
    • Dyskusje o FORBOT.pl
    • Na luzie

Kategorie

  • Quizy o elektronice
  • Quizy do kursu elektroniki I
  • Quizy do kursu elektroniki II
  • Quizy do kursów Arduino
  • Quizy do kursu STM32L4
  • Quizy do pozostałych kursów

Szukaj wyników w...

Znajdź wyniki, które zawierają...


Data utworzenia

  • Rozpocznij

    Koniec


Ostatnia aktualizacja

  • Rozpocznij

    Koniec


Filtruj po ilości...

Data dołączenia

  • Rozpocznij

    Koniec


Grupa


Imię


Strona

  1. Dawno mnie tu nie było Prawie rok temu zacząłem mały projekt, który robiłem baaaardzo z doskoku i w końcu mam jego koniec (miał być prezentem na majowe urodziny ). Jest to tester ostrości noży. Popularnym (jedynym??) komercyjnym produktem tego typu są testery BESS, jak tutaj, ale ich ceny wahają się w okolicach 700..1600zł w zależności od modelu. Jest też sporo projektów typu DIY, ale większość z nich opiera się o mini wagi, jak tutaj, które niestety często nie posiadają możliwości zapamiętywania maksymalnego nacisku i trzeba albo sobie nagrywać wyświetlacz albo wizualnie go obserwować i zapamiętać wynik, co wiąże się ze słabą precyzją i sporą niewygodą w użytkowaniu. Znalazłem pojedyncze projekty DIY bazujące o belkę tensometryczną i postanowiłem wykonać taki projekt od podstaw samemu. Ogólne założenia miały mniej więcej odwzorowywać używanie testera podobne jak gotowych produktów, czyli coś takiego, tylko w mniejszej formie: Całość oparłem o ESP8266 na płytce Wemos D1 mini z racji tego, że miałem ich garść pod ręką, do tego belka tensometryczna 2kg, jej wzmacniacz HX711 oraz wyświetlacz - tu wystarczyłby najprostszy tekstowy, ale chciałem "przybajerzyć" wizualnie, więc padło na graficzny tft 128x64. Niestety, na ten wyświetlacz poszło z 70% czasu całego projektu, testy absolutnie wszystkich znalezionych w necie bibliotek, kilka dyskusji na githubie i założonych issues by ostatecznie po ręcznej zmianie kilku hardcodowanych wartości w jednej z tych bibliotek udało się okiełznać wyświetlacz i prawidłowo wyświetlać na nim tekst i obraz w odpowiednich kolorach. Następnym razem tylko OLED na I2C, żadnych TFT po SPI, bo przybyło mi od tego sporo siwych włosów Po okiełznaniu projektu na płytce stykowej: Przyszła pora na zaprojektowanie obudowy: Elementy zwymiarowałem suwmiarką, przeniosłem do Fusion, zrobiłem kilka druków testowych wybranych fragmentów celem poprawienia wymiarów co i tak skończyło się w paru miejscach miejscowym podgrzewaniem hot airem oraz lutownicą, ale już nie chciało mi się ponownie robić poprawek i drukować kolejne 2h głównej części obudowy. Całość jest podzielona na 4 elementy: 1. obudowa główna 2. wieczko dolne z zatrzaskami na PCB, Wemosa i HX711 3. wspornik noża (przyklejony, ułatwiło to druk bez wielu supportów) 4. uchwyt żyłki (umieszczony na belce) Uchwyt żyłki ma wkręcone dwa gwinty, do których przykręca się pokrętła trzymające żyłkę podczas pomiaru: A tak wygląda finalny projekt (niestety, nie można załączyć filmiku): Generalnie kluczową informacją jest wartość max w gramach. Reszta to już wizualne bajery by projekt prezentował się ładniej. Kod do "analogowego" wyświetlacza znaleziony w necie, przerobiony pode mnie (dodana druga, powoli wracająca wskazówka wizualnie pokazująca max). Kodu nie wrzucam, bo to w 90% kod od tych analogowych wskazówek przemieszany chaotycznym dodaniem przeze mnie tekstów tu i tam. Nic godnego analizy Ot, skromne 4 m-ce spóźnienia na urodziny, ale czymże to jest wobec wieczności grunt, że bliżej do poprzednich niż następnych Houk!
  2. Przeglądając listę możliwych integracji Home Assistant w większości jest tam to czego potrzebujemy. Sam byłem w szoku, gdy aplikacja podpowiedziała mi integrację z tunerem audio, o którym bym nawet nie pomyślał że się do tego nadaje. Problem pojawia się, gdy wymyślimy sobie własne DIY, które robi coś unikatowego i chcemy to podłączyć pod automatykę domowa. Jedną z metod jest użycie Template Switch (czyli takiego wirtualnego przełącznika) i powiązanie go funkcją lambda z np. komponentem magistrali UART i komunikowanie się z naszym DIY. Problem w tym, że będziemy musieli poświęcić cały układ WiFi na pomost pomiędzy DIY, a centralką HA. W tym artykule postaram się nakreślić, jak zacząć pisać własne komponenty do ESPHome. Przygotowanie Niby jest do tego instrukcja (custom sensor i custom generic component), ale mimo wszystko po przeczytaniu tego co tam zamieszczono, przejrzeniu przykładów, zapytaniu na oficjalnym kanale na Discordzie, odpowiedź znalazłem dopiero na szarym końcu internetu. Wyjdźmy od tego jak tworzymy aplikacje (wsad np. do ESP8266 tu ESP-01S). Mając postawiony HA i zainstalowany dodatek ESPHome, dodajemy nowy sprzęt: Wybieramy nazwę: Rodzaj płytki: Pomijamy wgrywanie, bo trzeba zmienić coś w konfiguracji. Wybieramy więc edit: Mamy tu kilka domyślnych ustawień: esphome: name: spectrum-display esp8266: board: esp01_1m # Enable logging logger: # Enable Home Assistant API api: ota: password: "xxx" wifi: ssid: !secret wifi_ssid password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "xxx" password: "xxx" captive_portal: Nazwa - potrzebna do mDNS jako hostname. Niestety po aktualizacji coś słabo działa. Na stronie ESPHome sugeruje się używanie statycznego IP, które ma też przyspieszać łączenie: Dlatego dodajemy fragment dotyczący stałego IP: wifi: ssid: !secret wifi_ssid password: !secret wifi_password manual_ip: static_ip: 192.168.0.102 gateway: 192.168.0.1 subnet: 255.255.255.0 # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "xxx" password: "xxx" Co w efekcie zobaczymy w logu: Hasło do WiFi mamy ustawione w ESPHome (secrets). Zapisujemy, wgrywamy podłączając urządzenie przez USB lub wgrywając używając OTA, oczywiście jeżeli znamy hasło i adres urządzenia. W moim przypadku jest ta druga opcja: Gotowe komponenty Komponent to pewna funkcjonalność (nawet bardzo rozbudowana). Np. może to być przełącznik światła dołączonego do konkretnego wyprowadzenia (patrz przykład). W przypadku bardziej rozbudowanych komponentów, np. obsłudze protokołu komunikacji, obsługa zawęża się do wskazania urządzenia (np. DS18B20 1-wire) i podania wyprowadzenia. Takich komponentów możemy dodawać wiele i będą działać niezależnie od siebie. To tak jakbyśmy uruchomili wiele współbieżnych procesów. W praktyce wygląda to bardziej jak kod Arduino, w którym przy pomocy funkcji millis() wykonujemy "współbieżnie" wiele funkcji. Podstawy za nami. Teraz konfigurację można wzbogacić o jakieś podstawowe komponenty. Przykładowo kod do sterowania diodą na płytce pod pinem 2: # Enable Home Assistant API api: light: - platform: binary name: "Onboard LED" output: onboard_led_out output: - id: onboard_led_out platform: gpio pin: GPIO2 Id posłuży nam do dodania tzw. encji czyli kontrolki na panelu widocznym w Home Assistant. Wgrywamy i przechodzimy do głównej strony i edytujemy widok panelu: Od razu widzimy encję: Modyfikujemy wedle uznania: i gotowe! Możemy poklikać w nowo dodany przycisk. Najpewniej logika przycisku będzie odwrócona ze względu na sposób podłączenia LED na płytce ESP-01S. Możemy zmienić to w konfiguracji: output: - id: onboard_led_out platform: gpio pin: GPIO2 inverted: true I działa Własny komponent Napisanie własnego komponentu nie jest aż tak trudne. Autorzy uznali, że będą wzorować się na koncepcji kodu Arduino , w którym można wyróżnić bloki setup() i loop(). Problem w tym, że nie wiadomo gdzie zapisać te pliki. Na stronie z poradnikiem tworzenia własnych komponentów jest co prawda informacja: Ale niewiele to wnosi, bo nigdzie nie jest napisane gdzie jest ten katalog... Z pomocą przychodzi test przykładowej konfiguracji, w której chcę dodać jakiś plik: Czyli jest to ścieżka: /config/esphome/ Sprawdźmy jak wygląda zawartość tego katalogu, tylko zanim do teog przejdziemy, potrzeba nam SSH - bo w systemie HA nie ma domyślnie takich udogodnień. W repozytorium znajdujemy SSH, ustawiamy hasło i możemy się zalogować. Użytkownik root, hasło własne, port 22. Używam polecenie ls z dopiskiem -a, aby wyświetlić ukryte pliki i katalogi: la -a /config/esphome/ W tym miejscu można umieścić własne pliki bibliotek - tuż obok widzimy plik yaml konfiguracji. Ja swoje pliki umieszczam w katalogu custom_components tak by nie tworzyć bałaganu: Wewnątrz katalogu tworzę plik fps_meter.h: touch fps_meter.h Do pracy korzystam z WinSCP i VSC - zapis pliku w VSC od razu zdalnie go aktualizuje: Dla testu wpisuję kod służący do wyznaczania częstotliwości odświeżeni: #include "esphome.h" class FPSCounter : public Component, public Sensor { private: const unsigned long INTERVAL = 5000; unsigned long counter; unsigned long last_millis; float fps; public: float get_setup_priority() const override { return esphome::setup_priority::LATE; } void setup() override { last_millis = millis(); int last_button_state = HIGH; } void loop() override { if(millis() - last_millis > INTERVAL) { fps = (1000.0 * counter) / (millis() - last_millis); ESP_LOGD("FPS_COUNTER", "%lu sec passed, FPS: %f", INTERVAL, fps); publish_state(fps); counter = 0; last_millis = millis(); } ++counter; } }; I zapisuję. Do tego jak działa ten kod jeszcze wrócimy, ale na razie istotne jest, że w pętli wykonywane jest sprawdzenie częstości odświeżania i okresowo informacja wysyła jest do HA. Komponent jest czujnikiem (dziedziczy po klasie Sensor) ponieważ zwraca pewne informacje do centralki. Teraz trzeba użyć naszą klasę. Przechodzimy do konfiguracji i najpierw dodajemy plik biblioteki: esphome: name: spectrum-display includes: - custom_components/fps_meter.h oraz tworzymy komponent: api: sensor: - platform: custom lambda: |- auto fps_meter = new FPSCounter(); App.register_component(fps_meter); return {fps_meter}; sensors: name: "FPS counter" light: - platform: binary name: "Onboard LED" output: onboard_led_out output: - id: onboard_led_out platform: gpio pin: GPIO2 inverted: true Dodajemy typ sensor (to po czym dziedziczy nasz komponent), a w funkcji lambda korzystamy ze zdefiniowanej klasy FPSCounter. Dodajemy też nazwę, która może nam się przydać. Wgrywamy nowy kod i w logu zobaczymy, że coś zostało wyznaczone - mamy częstotliwość odświeżania około 60Hz: Możemy też znaleźć nasz "czujnik" wśród encji: i nasze obie kontrolki działają, powiedzmy "równolegle" Kod programu Wracając na chwilę do kodu programu: #include "esphome.h" class FPSCounter : public Component, public Sensor { public: float get_setup_priority() const override { return esphome::setup_priority::LATE; } void setup() override { } void loop() override { }; widzimy, że szablon jest dość prosty. Tworzymy klasę, która dziedziczy po komponencie i sensorze, aby móc wysyłać informacje (dostępna staje się wtedy metoda publish_state(). Jak wspomniałem setup i loop to metody, które możemy uzupełnić kodem wykonywanym odpowiednio przy starcie i cyklicznie. get_setup_priority() służy ustaleniu jaki priorytet ma nasza klasa. Możliwych narzędzi jest naprawdę wiele, zainteresowanych odsyłam do lektury dokumentacji i analizy przykładów. Kod klasy możemy rozbić na osobne pliki .cpp i .h. Warto jeszcze wspomnieć o czymś, co mnie bardzo mocno zmyliło - o bibliotekach/narzędziach deweloperskich ESPHome. W kodzie widzimy, że dodajemy plik esphome.h, ale nie jest to biblioteka: W pliku tym mamy podlinkowane biblioteki, które będziemy używać, a sam plik wygenerowany zostanie automatycznie... zostanie, ponieważ nasza biblioteka jest w katalogu /config/esphome, ale przed kompilacją jest kopiowana do katalogu projektu: Czyli dla powtórzenia: nasze pliki bibliotek trzymamy w głównym katalogu np. /config/esphome/custom_components/ w pliku .yaml dodajemy ścieżkę: custom_components/plik_biblioteki.h w kodzie pliku plik_biblioteki.h dodajemy esphome.h jakby był tuż obok, bo przed kompilacją zostanie tam przekopiowany tworząc kod pamietamy żeby podlinkować zawartość katalogu src Kuszące może być edytowanie zawartości katalogu src jednak tu uwaga w pliku README.txt: THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY ESPHome automatically populates the build directory, and any changes to this directory will be removed the next time esphome is run. For modifying esphome's core files, please use a development esphome install, the custom_components folder or the external_components feature. Oznacza to, że ten katalog jest wygenerowany automatycznie na bazie pliku .yaml i nie powinniśmy w nim nic mieszać.
  3. Witam, Chcę przedstawić wam moje podejście do tematu sterowników akwarystycznych. Sterownik ten zbudowałem dla swojego dziadka, który chciał załączać automatycznie pompkę, napowietrzacz i światło do oświetlenia akwarium. Zacząłem więc planować, stwierdziłem, że sterownik musi posiadać minimum 2 wyjścia 230V, jedno wyjście 12V z możliwością sterowania PWM, jakieś bajery (odczyt temperatury wody, automatyczne wyłącznie przekaźników, automatyczny karmnik dla ryb itp). Kilka lat temu zbudowałem swój pierwszy sterownik akwarystyczny (nazwałem go V.1), wykorzystałem do tego celu esp8266-12e, moduł podwójnego przekaźnika elektromagnetycznego oraz czujnik DS18B20. Nie mam niestety zdjęć pierwszego modelu. Działał on u mnie przez ponad rok, w tym czasie wynotowałem kilka wad mojego rozwiązania. Największe wady: -Podczas braku dostępu do internetu nie mogłem korzystać z sterownika, -Przekaźnik nie zawsze wyłączał się, -esp zawieszało się podczas załączania przekaźnika (podczas rozbiórki sterownika okazało się, że dobrałem złe wartości rezystorów do LM317 i zamiast 3,3V na esp podawałem 2,95V ) Sterownik V.2 (dla dziadka) Postanowienia: Urządzenie musiało być proste w użytkowaniu, nie mogło tracić swoich podstawowych funkcjonalności podczas braku dostępu do internetu, sterownik powinien pamiętać stany wyjść podczas braku zasilania sterownik powinien automatycznie sterować wyjściami i modułami do niego podłączonymi sterownik powinien sterować wyjściem 12V w sposób płynny, imitować zachód lub wschód słońca na pasku LED musiał posiadać funkcję zdalnego zarządzania swoimi funkcjami Działanie: Sterownik składa się z dwóch części, głównego sterownika oraz karty sieciowej. Sterownik główny odpowiada za włączanie/wyłączanie gniazd 230V, podawaniem sygnału PWM na wyjście, odczyt temperatury/wilgotności z czujników ds18b20 i DHT11, wyświetlanie godziny i innych danych na wyświetlaczu siedmio segmentowym, sprawdzaniu i wykonywaniu "alarmów", komunikowanie się z modułem karty sieciowej poprzez UART. Karta sieciowa - czyli moduł esp8266 (Wemos D1 mini) odpowiada za komunikację z aplikacją Blynk, zbieranie informacji (logów) z działania całego urządzenia i zapisywaniu ich na karcie SD, pobieraniu z internetu godziny do synchronizacji czasu, wysyłaniu wiadomości email z błędami w działaniu urządzenia lub plikami txt z logami, wyświetlaniu statusu połączenia z siecią i aplikacją za pomocą diod LED, komunikacją ze sterownikiem głównym, obsłudze komend wysyłanych przez terminal w aplikacji Blynk, itp. Budowa: W sterowniku głównym znajduje się: Arduino Nano Moduł podwójnego przekaźnika półprzewodnikowego SSR, zdecydowałem się na zmianę rodzaju przekaźnika z powodu tego, że taki przekaźnik jest bezgłośny, zawsze działa (nie zawiesza się tak jak przekaźniki elektromagnetyczne) Moduł zegara czasu rzeczywistego DS1307, odpowiada za dostarczanie dla wyświetlacza aktualnej godziny nawet po wyłączeniu zasilania oraz jako czas potrzebny do działania alarmów. Czujniki DS18B20 (wersja wodoodporna na metrowym przewodzie, metalowa końcówka czujnika znajduje się w wodzie) oraz DHT11 (wyprowadzony jest na zewnątrz obudowy sterownika) 4 przyciski z podświetleniem led w 4 kolorach(czerwony, zielony, niebieski, żółty). Odpowiadają one za włączanie i wyłączanie przekaźników, zmianę jasności paska LED, włączenie karmnika dla ryb oraz wyświetleniu na wyświetlaczu aktualnych temperatur Wyświetlacz siedmio segmentowy TM1637, wyświetla on aktualną godzinę, temperaturę, kody błędów Tranzystor IRF540 wraz z transoptorem PC817 i kilkoma rezystorami Przetwornica step-down (obniża napięcie 12V z zasilacza, na 5V potrzebnych do zasilenia Arduino i reszty modułów) Bezpieczniki 1A na gniazdka 230V oraz 1,2A na zasilaczu 12V Włącznik dźwigniowy (przerywa napięcie zasilania 12V) Zasilacz 12V 1,5A W karcie sieciowej znajduje się: Wemos D1 Mini, z dolutowaną anteną od starego routera tp-link (link do instrukcji takiej operacji instrukcja ) Adapter do karty microSD - SD 3 diody plus rezystory 220Ω micro switch, służy do resetowania płytki przełącznik 3 pozycyjny, służy do wyboru sieci WiFi, np. 1 - sieć1, 2 - sieć2, 3 - sieć serwisowa przełącznik 2 pozycyjny, służy do wyłączenia 3 diod sygnalizacyjnych LED, kiedy nie są potrzebne konwerter stanów logicznych, służy do konwersji napięć między Arduino a Wemosem na magistrali UART moduł pcf8574, steruje diodami oraz automatycznym karmnikiem, w przyszłości może także sterować kolejnymi przekaźnikami Działanie Sterownika głównego void setup: wczytanie ostatnich stanów wyjść z pamięci EEPROM ustawienie przerwania na 1ms (odpowiada za zmianę wartości na pinie PWM oraz dekrementuje zmienną potrzebną do wyświetlania informacji na wyświetlaczu ustawiam wejścia i wyjścia ustawiam stany pinów przy pomocy danych odczytanych z pamięci EEPROM (funkcja przywracania ostatnich stanów pinów) ustawiam jasność paska led (podczas włączenia sterownika, światło powoli rozjaśnia się, do ostatniego zapisanego stanu) Inicjuje zegar, czujniki temperatur, przyciski itp. void loop: W pętli loop sprawdzam: Czy jakiś przycisk został wciśnięty Ustawiam stany pinów, odświeżam informacje na wyświetlaczu Sprawdzam czy jakiś alarm może zostać wykonany Sprawdzam czy karta sieciowa wysłała jakieś dane Liczę ilość przejść pętli w czasie jednej sekundy, co sekundę wyświetlam tę ilość na serial monitorze Sprawdzam, czy minęło 5 sekund, po tym czasie raportuje do karty sieciowej o stanach wyjść ------------------------------------------------------------------------------------------------------------------------ 1.Sprawdzenie przycisków - Arduino obsługuje 4 przyciski, służą one do: Pojedyncze (krótkie wciśnięcie) - włączenie/wyłączenie przekaźnika 1 i 2, zmiany jasności paska LED podłączonego do pinu PWM, załączenie karmnika, Długie wciśnięcie - wyświetlenie na wyświetlaczu siedmio segmentowym temperatury wody/powietrza, szybkie wyłączenie paska LED przycisk niebieski - krótkie wciśnięcie (zmiana stanu przekaźnika 1) / długie wciśnięcie (wyświetlenie na wyświetlaczu temperatury wody) przycisk czerwony - krótkie wciśnięcie (zmiana stanu przekaźnika 2) / długie wciśnięcie (wyświetlenie na wyświetlaczu temperatury powietrza) przycisk zielony - krótkie wciśnięcie (zmiana stanu na pinie PWM ), każde wciśnięcie zwiększa wartość na pinie PWM [0,20,40,60,80,100%] / długie wciśnięcie szybko wygasza pasek LED przycisk żółty - krótkie wciśnięcie. Powoduje mruganie podświetleniem przycisku czerwonego i zielonego przez 10 sekund, wciśnięcie przycisku zielonego spowoduje podjęcie przez sterownik próby załączenia automatycznego karmnika \ wciśnięcie przycisku czerwonego anuluje ten proces, tak samo jak nie wciśnięcie żadnego z przycisków przez 10 sekund void checkbutton() { if (checkbutton_debuce == 1) { Serial.print(F("R_SW_State: ")); Serial.print(R_SW_state); Serial.print(F(" | B_SW_State: ")); Serial.print(B_SW_state); Serial.print(F(" | G_SW_State: ")); Serial.print(G_SW_state); Serial.print(F(" | Y_SW_State: ")); Serial.println(Y_SW_state); } G_SW.read(); B_SW.read(); Y_SW.read(); R_SW.read(); if (B_SW.changed()) { display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(1, false, 1, 1); last_millis2 = 0; B_SW_state = B_SW.toggleState(); if (B_SW_state == 1) { display.setSegments(display_symbol_off, 2, 2); send_command(s1on, 8); } else { display.setSegments(display_symbol_on, 2, 2); send_command(s1off, 8); } screen_timeout = 5000; } if (R_SW.changed()) { display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(2, false, 1, 1); last_millis2 = 0; R_SW_state = R_SW.toggleState(); if (R_SW_state == 1) { send_command(s2on, 8); display.setSegments(display_symbol_off, 2, 2); } else { send_command(s2off, 8); display.setSegments(display_symbol_on, 2, 2); } screen_timeout = 5000; } if (G_SW.pressedFor(LONG_PRESS)) { digitalWrite(GREEN_SW_LIGHT, 0); delay(50); digitalWrite(GREEN_SW_LIGHT, 1); delay(50); digitalWrite(GREEN_SW_LIGHT, 0); delay(50); digitalWrite(GREEN_SW_LIGHT, 1); delay(50); last_millis2 = 0; G_SW_button_cycle = 0; G_SW_state = 0; set_pwm_led_1(G_SW_state); led_step = 2; Serial.println("Green Button LONG PRESS DETECTED !"); display.clear(); display.showNumberDec(map(G_SW_state, 0, 255, 0, 100), false, 3, 1); display.setSegments(display_symbol_s, 1, 0); while (true) { G_SW.read(); if (G_SW.wasReleased()) { break; } } screen_timeout = 5000; return; } if (G_SW.changed()) { digitalWrite(GREEN_SW_LIGHT, 1); last_millis2 = 0; G_SW_button_cycle++; if (G_SW_button_cycle >= 6) { G_SW_button_cycle = 0; } switch (G_SW_button_cycle) { case 0: G_SW_state = 0; break; case 1: G_SW_state = 51; break; case 2: G_SW_state = 102; break; case 3: G_SW_state = 153; break; case 4: G_SW_state = 204; break; case 5: G_SW_state = 255; break; } display.clear(); display.showNumberDec(map(G_SW_state, 0, 255, 0, 100), false, 3, 1); display.setSegments(display_symbol_s, 1, 0); vpwm1[4] = (byte)G_SW_button_cycle * 2; send_command(vpwm1, 8); delay(40); if (G_SW_state != 255) { digitalWrite(GREEN_SW_LIGHT, 0); } led_step = led_step_button; screen_timeout = 5000; } if (Y_SW.pressedFor(LONG_PRESS)) { //locking feeding option Serial.println("Yellow Button LONG PRESS DETECTED !"); while (true) { Y_SW.read(); if (Y_SW.wasReleased()) { //send_command(auto_feed, 9); delay(25); break; } } return; } if (Y_SW.changed()) { bool wait2pressB = 0; bool w2p_ledstate = 1; unsigned long y_long_press_ms = millis(); unsigned long y_long_press_ms_led = millis(); while (wait2pressB == 0) { green_led_switch_on = 0; R_SW.read(); G_SW.read(); if (millis() - y_long_press_ms_led > 500) { digitalWrite(GREEN_SW_LIGHT, w2p_ledstate); digitalWrite(RED_SW_LIGHT, w2p_ledstate); w2p_ledstate = !w2p_ledstate; y_long_press_ms_led = millis(); } if (G_SW.changed()) { digitalWrite(GREEN_SW_LIGHT, G_SW_state); digitalWrite(RED_SW_LIGHT, R_SW_state); send_command(auto_feed, 9); delay(25); green_led_switch_on = 1; return; } else if (R_SW.changed()) { digitalWrite(GREEN_SW_LIGHT, G_SW_state); digitalWrite(RED_SW_LIGHT, R_SW_state); green_led_switch_on = 1; return; } else { } if (millis() - y_long_press_ms > 10000) { digitalWrite(GREEN_SW_LIGHT, G_SW_state); digitalWrite(RED_SW_LIGHT, R_SW_state); green_led_switch_on = 1; return; } } } } 2.Ustawienie odpowiednich stanów pinów: Ta funkcja odpowiada za zmianę stanów wyjść do których podłączony jest moduł przekaźników oraz za zmianę informacji na wyświetlaczu (godziny). void set_pin_states() { set_power_230V_1(B_SW_state); set_power_230V_2(R_SW_state); display_value_on_7s_display(Y_SW_state); } void set_power_230V_1(bool s) { digitalWrite(power_230V_1, !s); digitalWrite(BLUE_SW_LIGHT, s); } void set_power_230V_2(bool s) { digitalWrite(power_230V_2, !s); digitalWrite(RED_SW_LIGHT, s); } void display_value_on_7s_display(byte dis) { if (screen_timeout > 0) { return; } int current_t = 0; current_t = H * 100 + MIN; display.showNumberDecEx(current_t, 64, true); } 3."Alarmy": Poprzednia wersja sterownika posiadała ogromną wadę, chodzi o automatyczne załączanie przekaźników lub innych modułów. W poprzedniej wersji za załączanie automatyczne odpowiadała aplikacja Blynk z widżetem Timer, było to dość wygodne wyjście, ponieważ całą konfigurację (co ma się włączyć, kiedy, itp.) robiło się w przejrzystym menu aplikacji. To rozwiązanie miało jednak dużą wadę, podczas braku dostępu do internetu, sterownik nie wykonywał żadnych akcji. W drugiej wersji postanowiłem całkowicie oddzielić od siebie warstwę internetową (komunikacji z internetem) od warstwy podstawowych funkcjonalności, takich jak automatyczne włączanie/wyłączanie przekaźników, automatyczna zmiana jasności, włączenie karmnika itp. Co to jest "alarm"? - alarmem nazywam jedną funkcję, która wykona się o danej godzinie, minucie, dniu. Przechowywany jest w formie 5 jedno bajtowych komórek, ułożonych obok siebie w pamięci EEPROM. Alarm składa się z: A (Action- Pierwsza komórka, przechowuje numer funkcji, która ma zostać wykonana.) D (Day- Dzień tygodnia w który musi wykonać się alarm. 1 - poniedziałek / ... / 7 - niedziela / 8 - każdy dzień tygodnia) H (Hour - godzina. 0 - 23) M (Minute - minuta, 0 - 59) V (Value - parametr funkcji, np. funkcja 1 przyjmuje parametr 1 lub 0 [ włączenie / wyłączenie przekaźnika 1]) Ograniczenia - sterownik może przechować maksymalnie 80 alarmów, dwa lub więcej alarmów nie może zostać wykonanych w tej samej minucie Funkcja sprawdzająca, czy jakiś alarm może zostać wykonany: void check_alarm() { if (alarm_onoff == 1) { int p = 0; int n = 0; while (n <= ile_alarmow) { if (EEPROM.read(n * 5) != 127) { p = (byte)(n * 5); byte alarm_d, alarm_h, alarm_min, alarm_action, alarm_val; alarm_action = (byte)EEPROM.read(p); alarm_d = (byte)EEPROM.read(p + 1); alarm_h = (byte)EEPROM.read(p + 2); alarm_min = (byte)EEPROM.read(p + 3); alarm_val = (byte)EEPROM.read(p + 4); get_current_time(); if ((alarm_d == 8 || alarm_d == weekd) && alarm_onoff == 1) { if (alarm_h == H && alarm_min == MIN) { Serial.print(F("Wykonam akcje nr. ")); Serial.println(alarm_action); Serial.print(F("Alarm is trigered on ")); Serial.print(alarm_h); Serial.print(F(":")); Serial.print(alarm_min); Serial.print(F(":")); Serial.println(S); execute_alarm((byte)alarm_action, (byte)alarm_val); alarm_onoff = 0; alarm_timer.reset(); //1 minuta przerwy między alarmami } } else { //Serial.print(F("Nie wykonam akcji nr. ")); //Serial.println(i); } } n++; } } } void execute_alarm(byte execute_action_number, byte execute_value) { Serial.print(F("I'm executing function number ")); Serial.print(execute_action_number); Serial.print(F(" with value: ")); Serial.println(execute_value); if (execute_action_number == 1 || execute_action_number == '1') { bool action_flag; if (execute_value == 1) { action_flag = 1; } else if (execute_value == 0) { action_flag = 0; } else { action_flag = 0; } B_SW_state = action_flag; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(1, false, 1, 1); if (B_SW_state == 1) { display.setSegments(display_symbol_off, 2, 2); } else { display.setSegments(display_symbol_on, 2, 2); } screen_timeout = 5000; //set_power_230V_1(action_flag); } else if (execute_action_number == 2 || execute_action_number == '2') { bool action_flag2; if (execute_value == 1) { action_flag2 = 1; } else if (execute_value == 0) { action_flag2 = 0; } else { action_flag2 = 0; } R_SW_state = action_flag2; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(2, false, 1, 1); last_millis2 = 0; R_SW_state = R_SW.toggleState(); if (R_SW_state == 1) { send_command(s2on, 8); display.setSegments(display_symbol_off, 2, 2); } else { send_command(s2off, 8); display.setSegments(display_symbol_on, 2, 2); } screen_timeout = 5000; //set_power_230V_2(action_flag2); } else if (execute_action_number == 3 || execute_action_number == '3') { if (execute_value < 11 && execute_value > -1) { G_SW_state = map(execute_value, 0, 10, 0, 255); Serial.print(F("Setting led pwm value by alarm to ")); Serial.println(G_SW_state); if (execute_value == 0) { G_SW_button_cycle = 0; } else if (execute_value > 0 && execute_value <= 2) { G_SW_button_cycle = 1; } else if (execute_value > 2 && execute_value <= 4) { G_SW_button_cycle = 2; } else if (G_SW_state > 4 && G_SW_state <= 6) { G_SW_button_cycle = 3; } else if (G_SW_state > 6 && G_SW_state <= 8) { G_SW_button_cycle = 4; } else if (G_SW_state > 8 && G_SW_state <= 10) { G_SW_button_cycle = 5; } else { G_SW_button_cycle = 0; } led_step = led_step_alarm; //set_pwm_led_1(execute_value); display.clear(); display.showNumberDec(map(G_SW_state, 0, 255, 0, 100), false, 3, 1); display.setSegments(display_symbol_s, 1, 0); screen_timeout = 5000; } else { Serial.println(F("Wrong value !")); } } else if (execute_action_number == 4 || execute_action_number == '4') { Serial.println(F("Automatic feeding !!!")); //byte auto_feed[9] = {0x7E, 'A', 'F', ';', 'e', 'E', 'G', 0, 0x7F} send_command(auto_feed, 9); } else if (execute_action_number == 5 || execute_action_number == '5') { //re_send_time[9] = {0x7E, 'R', 'E', ':', 'S', '8', 'M', 0, 0x7F}; Serial.println(F("Alarm Automatic time syncing !!!")); send_command(re_send_time, 9); } else if (execute_action_number == 6 || execute_action_number == '6') { set_brightness(execute_value); } else if (execute_action_number == 7 || execute_action_number == '7') { if (EEPROM.read(1010) == 0) { //EEPROM.write(1010, 1); eeprom_optimalization(1010, 1); if (execute_value == 0) { //reset arduino Serial.println(F("Resseting arduino...")); resetFunc(); } else if (execute_value == 1) { //reset esp Serial.println(F("Resseting esp...")); send_command(reset_esp_command, 9); } else if (execute_value == 2) { //reset arduino and esp Serial.println(F("Resseting esp...")); send_command(reset_esp_command, 9); Serial.println(F("Resseting arduino...")); resetFunc(); } else { Serial.println(F("Wrong reset value!range:0-2")); //EEPROM.write(1010, 0); eeprom_optimalization(1010, 0); } } } else if (execute_action_number == 8 || execute_action_number == '8') { //send_all_mail } else if (execute_action_number == 9 || execute_action_number == '9') { //deleta_all_sd_card_files } else { Serial.println(F("Wrong alarm !!!")); } } 4.Sprawdzenie czy karta sieciowa wysłała jakieś dane: Esp8266 i Arduino Nano komunikują się ze sobą poprzez UART. Wszystkie informacje przesyłane są w postaci komend rozpoczynających się od znaku 0x7E, dalej reszta komendy wraz z parametrami, suma kontrolna, znak 0x7F, 3 znaki nowej lini '\n'. Przykładowa komenda wysyłana przez Arduino do Esp: 0x7E, 'n', '1', 'o', 'n', '+', (wyliczona suma kontrolna), 0x7F, '\n', '\n', '\n' Funkcja sprawdzająca odebrane dane w karcie sieciowej oraz Arduino jest prawie taka sama, polega na nasłuchiwaniu czy jest coś nadawane. Jeżeli tak to funkcja zapisuje do buffera bajt po bajcie aż do otrzymania trzech znaków nowej lini '\n' pod rząd. Jeżeli otrzymała te 3 znaki, rozpoczyna liczenie sumy kontrolnej od początku komendy do ostatniego bajtu danych w komendzie. Następnie porównuje wyliczoną sumę kontrolną z odebraną sumą, jeżeli sumy zgadzają się (są takie same), to Arduino lub Esp wykonuje daną komendę, np. komenda (0x7E, 'n', '1', 'o', 'n', '+', (wyliczona suma kontrolna), 0x7F, '\n', '\n', '\n') odpowiada za ustawienie w aplikacji Blynk stanu przycisku nr. 1 na włączony (podczas włączenia przekaźnika 1 przy pomocy przycisku na obudowie, Arduino musi wysłać tą informacje do karty sieciowej, aby użytkownik miał podgląd na aktualny stan przekaźnika). void recive_data() { bool control_val = 1; espSerial.flush(); if (espSerial.available() && control_val == 1) { espSerial.flush(); //digitalWrite(13, 1); delay(3); r_array_counter = 0; r_array[r_array_counter] = '\n'; timestart = millis(); //byte character_counter = 0; bool detect_end = 0; byte add_val = 2; while (espSerial.available() && r_array_counter < 25 && (millis() - timestart) < 400 && control_val == 1) { delay(1); byte character = espSerial.read(); r_array[r_array_counter] = character; r_array_counter++; r_array[r_array_counter] = '\n'; if (character != '\n' && control_val == 1) { if (serial_reciv_debug == 1) { Serial.print(F("{")); Serial.print(character); Serial.print(F("}")); } } if (character == '\n' && r_array[r_array_counter - 3] == '\n' && r_array[r_array_counter - 2] == '\n' && r_array[r_array_counter - 4] == 0x7F) { detect_end = 1; } if (detect_end == 1 && control_val == 1) { detect_end = 0; r_array_counter -= 1; if (serial_reciv_debug == 1) { Serial.print(F(" [r_array_counter]-> ")); Serial.println(r_array_counter); Serial.println(F(" - new line symbol detect!")); } byte chk = 0; uint8_t i = 0; for (i = 0; i < r_array_counter - (2 + add_val); i++) { chk ^= r_array[i]; } Serial.print("Control sum = "); Serial.println(chk); if (r_array[0] == 0x7E && r_array[r_array_counter - (1 + add_val)] == 0x7F && (byte)r_array[r_array_counter - (2 + add_val)] == (byte)chk && control_val == 1) { if (serial_reciv_debug == 1) { Serial.println(F("Good control sum !")); } last_millis2 = 0; if (r_array[1] == 's' && (r_array[3] == '$' || r_array[3] == '#')) { switch (r_array[2]) { case '1': if (r_array[4] == 'o' && r_array[5] == 'n') { B_SW_state = 1; set_power_230V_1(1); B_SW.m_toggleState = 1; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(1, false, 1, 1); display.setSegments(display_symbol_off, 2, 2); screen_timeout = 5000; } else if (r_array[4] == 'o' && r_array[5] == 'f') { B_SW_state = 0; set_power_230V_1(0); B_SW.m_toggleState = 0; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(1, false, 1, 1); display.setSegments(display_symbol_on, 2, 2); screen_timeout = 5000; } else { } break; case '2': if (r_array[4] == 'o' && r_array[5] == 'n') { R_SW_state = 1; set_power_230V_2(1); R_SW.m_toggleState = 1; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(2, false, 1, 1); display.setSegments(display_symbol_off, 2, 2); screen_timeout = 5000; } else if (r_array[4] == 'o' && r_array[5] == 'f') { R_SW_state = 0; set_power_230V_2(0); R_SW.m_toggleState = 0; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(2, false, 1, 1); display.setSegments(display_symbol_on, 2, 2); screen_timeout = 5000; } else { } break; } control_val = 0; } ... } } } 5.Sprawdzanie wydajności programu: Podczas pisania programu dla tego sterownika, musiałem sprawdzać wydajność danego rozwiązania, np. ile czasu zajmuje włączenie/wyłączenie jednej funkcji. Napisałem więc prostą funkcję, która sprawdza ile raz wykonują się wszystkie funkcje w funkcji main. Co sekundę Arduino wysyła na serial monitor informację ile razy wykonała się funkcja main. void framerate() { fr++; if (S != fr_sec) { Serial.print("##> FRAMERATE = "); Serial.print(fr); Serial.println(" fps <##"); fr = 0; fr_sec = S; } } 6.Funkcja wysyłające dane do karty sieciowej: Sterownik co 5 sekund wysyła do karty sieciowej informację o aktualnych temperaturach wody i powietrza oraz wilgotność. Wysyła je w formie komendy: 0x7E, '%', (wilgotność powietrza), 'T', (temp. powietrza liczba całkowita), (temp. powietrza liczba po przecinku), '@', (temp. wody liczba całkowita), (temp. wody liczba po przecinku), (suma kontrolna), 0x7F, '\n', '\n', '\n' Co 10 sekund wysyła dane oraz zapisuje stan wyjść w pamięci EEPROM, jeżeli są one inne od ostatnio zapisanych ( jest to potrzebne do działania funkcji przywracania stanów podczas wyłączenia sterownika). Wysyłane dane składają się z informacji o stanach wyjść sterownika (stan przekaźnika 1 i 2, wartość na pinie PWM). Komenda wygląda następująco: 0x7E, (stan przekaźnika 1), '&', (stan przekaźnika 2), '^', (wartość na pinie PWM przeskalowana od 0 do 10), '%', (suma kontrolna) , 0x7F, '\n', '\n', '\n' void execute_millis_functions() { if ((millis() - last_millis1) > 5000) { send_sensors_data(); last_millis1 = millis(); } if ((millis() - last_millis2) > 10000) { sync_data(); Serial.print(F("****> power_loos_counter = ")); Serial.println(power_loos_counter); if (B_SW_state != EEPROM.read(1001)) { //EEPROM.write(1001, B_SW_state); eeprom_optimalization(1001, B_SW_state); power_loos_counter++; } if (R_SW_state != EEPROM.read(1002)) { //EEPROM.write(1002, R_SW_state); eeprom_optimalization(1002, R_SW_state); power_loos_counter++; } if (G_SW_state != EEPROM.read(1003)) { //EEPROM.write(1003, G_SW_state); eeprom_optimalization(1003, G_SW_state); power_loos_counter++; } last_millis2 = millis(); } if (alarm_onoff == 0 && alarm_timer.isReady()) { alarm_onoff = 1; } if (timer1.isReady()) { timer_clock(); } } void sync_data() { //sync[9] = {0x7E, 0, '&', 0, '^', 0, '%', 0, 0x7F}; if (B_SW_state == 1) { data_2_sync[1] = 1; } else if (B_SW_state == 0) { data_2_sync[1] = 0; } else { } if (R_SW_state == 1) { data_2_sync[3] = 1; } else { data_2_sync[3] = 0; } if (G_SW_button_cycle == 0) { data_2_sync[5] = 0; } else if (G_SW_button_cycle == 1) { data_2_sync[5] = 2; } else if (G_SW_button_cycle == 2) { data_2_sync[5] = 4; } else if (G_SW_button_cycle == 3) { data_2_sync[5] = 6; } else if (G_SW_button_cycle == 4) { data_2_sync[5] = 8; } else if (G_SW_button_cycle == 5) { data_2_sync[5] = 10; } else { data_2_sync[5] = 0; } send_command(data_2_sync, 9); } KOD Sterownika (Zalecam wykorzystanie bibliotek dołączonych do tego artykułu, ponieważ zmieniałem w nich kilka rzeczy): aquarium_driver_v2.zip Działanie Karty Sieciowej (esp8266) void setup(): void setup() { Serial.begin(9600); EEPROM.begin(1000); //0-405 - alarms eeprom //507-998 - epprom variable space //1000-4096 - log eeprom space delay(50); pcf8574.pinMode(Wifi_status_led, OUTPUT); pcf8574.pinMode(Blynk_status_led, OUTPUT); pcf8574.pinMode(Connection_status_led, OUTPUT); pcf8574.pinMode(feeding_pin, OUTPUT); pcf8574.pinMode(retraction_pin, OUTPUT); pcf8574.pinMode(wifi_network_sw_a, INPUT_PULLUP); pcf8574.pinMode(wifi_network_sw_b, INPUT_PULLUP); pcf8574.begin(); pcf8574.digitalWrite(Wifi_status_led, OFF); pcf8574.digitalWrite(Blynk_status_led, OFF); pcf8574.digitalWrite(Connection_status_led, OFF); pcf8574.digitalWrite(feeding_pin, OFF); pcf8574.digitalWrite(retraction_pin, OFF); for (int i = 0; i < 880; i++) { message_aray[i] = 127; } feeding_time = EEPROM.read(510); //510 retraction_time = EEPROM.read(511); //511 feeding_today = EEPROM.read(509); //509 delay(100); set_network(); byte datalog_d, datalog_m, datalog_y; datalog_d = EEPROM.read(523); datalog_m = EEPROM.read(524); datalog_y = EEPROM.read(525); String dd_str = ""; if (datalog_d < 10) { dd_str = "0"; } dd_str += String(datalog_d); String dm_str = ""; if (datalog_m < 10) { dm_str = "0"; } dm_str += String(datalog_m); String dy_str = ""; if (datalog_y < 10) { dy_str = "0"; } dy_str += String(datalog_y); current_log_filename = "L" + dd_str + dm_str + dy_str + ".txt"; cmd.print("Log filename is "); cmd.println(current_log_filename); cmd.flush(); if (EEPROM.read(522) == 1) { EEPROM.write(522, 0); EEPROM.commit(); send_mail(0); } else if (EEPROM.read(522) == 2) { EEPROM.write(522, 0); EEPROM.commit(); send_mail(1); } else { } if (WiFi.status() == 6) { pcf8574.digitalWrite(Wifi_status_led, OFF); } unsigned long startWifi = millis(); WiFi.mode(WIFI_STA); WiFi.begin(E_ssid.c_str(), E_pass.c_str()); bool wifi_flag = false; while (WiFi.status() != WL_CONNECTED) { delay(100); pcf8574.digitalWrite(Wifi_status_led, wifi_flag); wifi_flag = !wifi_flag; if (millis() > startWifi + myWifiTimeout) { Serial.println("Time out"); break; } } Blynk.config(auth, server, port); checkBlynk(); timer.setInterval(functionInterval, myfunction); // run some function at intervals per functionInterval timer.setInterval(blynkInterval, checkBlynk); // check connection to server per blynkInterval timer.setInterval(500L, period_function); timer.setInterval(5000L, sync_alarms); // sync alarm && check_logs(); timer.setInterval(15000L, check_day_change); cmd.clear(); blynk_led.off(); setSyncInterval(1000); dp_network(); if (debug_log_string.length() > 0) { cmd.println(debug_log_string); cmd.flush(); } add_log(48, datalog_d, datalog_m, datalog_y); add_log(30, 1, 999, 999); check_day_change(); } Zainicjowanie UARTA oraz pamięci "EEPROM" esp8266 Ustawienie pinów modułu ekspandera pcf8574 (wejścia dla przełącznika do wyboru sieci WiFi, wyjścia do sterowania karmnikiem oraz dla diod LED wyświetlających stan połączenia z siecią WiFi, połączenia z serwerem aplikacji Blynk oraz potwierdzenie o otrzymaniu komendy od Arduino na magistrali UART) Odczytanie parametrów z pamięci "EEPROM" (karta sieciowa odczytuje: informacje potrzebne do działania karmnika) Odczytanie z przełącznika wybranej sieci wifi oraz wczytanie nazwy Sieci (SSID) oraz hasła do sieci Ustawienie nazwy pliku z logami sterownika Próba połączenia się z siecią Konfiguracja ustawień aplikacji Blynk i sprawdzenie połączenia z serwerem aplikacji Ustawienie Timerów Dodanie do logów informacji o starcie karty sieciowej ------------------------------------------------------------------------------------------------------------------------ 4.Sterownik może obsłużyć maksymalnie 3 sieci WiFi, 2 sieci użytkownika oraz jedną sieć serwisową. Na karcie sieciowej znajduje się przełącznik 3 pozycyjny, pierwsze dwie pozycje to sieci użytkownika, ostatnia, to sieć serwisowa. Po sprawdzeniu przez kartę sieciową stanu przełącznika, odczytuje z pamięci "EEPROM" nazwę sieci (SSID) oraz hasło, jeżeli wybrano 3 pozycję przełącznika, SSID ustawiane jest na SIEC_SERWISOWA oraz hasło SERWISANT123. Ma to na celu zapobieganie blokadzie karty sieciowej, przez źle podane hasło. Hasło do sieci WiFi można zmienić przy pomocy telefonu, służy do tego komenda add_wifi=(numer pod którym ma zostać zapisana nowa sieć, 1 lub 2)-(SSID maks 20 znaków),(Hasło maks 20 znaków) 5.Podczas załączania się karty sieciowej tworzona jest nazwa pliku, do którego będą zapisywane dane. Nazwa zawiera aktualną datę, np. L010321.txt, urządzenie zapisuję do tego pliku oraz pliku DATALOG.txt informację o działaniu sterownika oraz karty sieciowej w postaci logów. void loop(): void loop() { recive_data(); check_feeding(); if (millis() - repair_data_timer >= 10000 && EEPROM.read(526) == 1) { repair_data_timer = millis(); byte repair_number = check_is_data_ok(); if (repair_number != 0) { repair_data(repair_number); } } if (millis() >= pwm_timeout_time) { pwm_timeout = 0; } if (Blynk.connected()) { Blynk.run(); } timer.run(); if (millis() - one_hour_timer >= 60000 && one_hour_timer != 0) { //rechecking values one_hour_timer = 0; E_value_eeprom_Succes_write_counter_esp = 0; E_value_eeprom_Fail_write_counter_esp = 0; E_value_eeprom_Succes_write_counter_arduino = 0; E_value_eeprom_Fail_write_counter_arduino = 0; E_arduino_alarm_syncing_counter = 0; } } 1.Sprawdzenie czy otrzymano jakieś dane 2.Włączenie/wyłączenie karmnika 3.Sprawdzenie poprawności danych 4.Callback Blynk'a 5.Resetowanie kodów błędu po upłynięciu 1h ------------------------------------------------------------------------------------------------------------------------ 1. Funkcja ta działa tak samo jak funkcja sprawdzająca czy otrzymano dane z sterownika 2. Karta sieciowa odpowiada za sterowanie modułem karmnika, sterowany jest dwoma pinami z modułu pcf8574 podłączonego do Wemosa. Stan wysoki na pierwszym pinie załącza karmienie, stan wysoki na drugim pinie zaczyna cofać pokarm do zbiornika. 3. Ten element programu służy do sprawdzania, czy wszystko z danymi (np. stany wyjść, zmienne zapisane w "EEPROMIE" itp.) jest ok. Np. komórka 510 w pamięci "EEPROM" przechowuje czas podawania pokarmu, zakres to 0-29 sekund. Cały sterownik umożliwia zmianę wartości w pamięci EEPROM zdalnie przy pomocy komend, np. write_esp_eeprom=(komórka pamięci "EEPROM" w zakresie 0-1000),(wartość od 0 do 255) lub write_ard_eeprom=(komórka pamięci EEPROM w zakresie 0-1023),(wartość od 0 do 255). Przez przypadek mogę ustawić wartość w komórce 510 na 255, oznacza to, że karmnik będzie podawał pokarm przez ponad 4 minuty ! Jest to dość niebezpieczne dla ryb. Funkcja ta sprawdza więc czy wszystkie wpisane wartości w odpowiednich komórkach są poprawne, jeżeli nie, to sterownik informuje o tym użytkownika oraz stara się naprawić dane, np. ustawia wartość w komórce 510 na 2 sekundy. Sterownik może wysłać maila jeżeli usterka jest bardziej złożona, np. sterownik oraz karta sieciowa korzystają z pamięci EEPROM, pozwala ona na zachowanie wartości nawet po zaniku zasilania, niestety korzystanie z tej pamięci jest ograniczone ilością zapisów (średnio około 100 000), po tej wartości z czasem mogą pojawiać się problemy z pamięcią, może ona ulec uszkodzeniu. Napisałem funkcję która ma na celu kontrolowanie zużycia tej pamięci, void eeprom_optimalization(int E_case, int E_val2w, bool add2counter, bool safe_mode) { if (E_val2w > 255 || E_val2w < 0) { cmd.println("Value to write is out of range!"); add_log(31, E_case, E_val2w, 0); return; } if (safe_mode == 1) { //int eeprom_case_not2write[7] = {507, 508, 509, 510, 511, 512, 513}; for (int i = 0; i < eeprom_case_not2write_length; i++) { if (E_case == eeprom_case_not2write[i]) { cmd.println("This eeprom case cannot be written!"); cmd.flush(); add_log(31, E_case, E_val2w, 1); return; } } } else { for (int i = 0; i < eeprom_case_not2write_length; i++) { if (E_case == eeprom_case_not2write[i]) { cmd.println("This eeprom case is dangerous to written!"); cmd.flush(); break; } } } byte val2write = (byte)E_val2w; byte E_val_read = EEPROM.read(E_case); if (E_val_read != E_val2w) { EEPROM.write(E_case, val2write); if (add2counter == 1) { int r_e = ((EEPROM.read(514) << 8) + EEPROM.read(515)); r_e += 1; byte r_e_b = highByte(r_e); EEPROM.write(514, r_e_b); r_e_b = lowByte(r_e); EEPROM.write(515, r_e_b); cmd.print("Writting eeprom succesful counter = "); cmd.println(r_e); } add_log(31, E_case, E_val2w, 2); } else { if (add2counter == 1) { int r_e = ((EEPROM.read(516) << 8) + EEPROM.read(517)); r_e += 1; byte r_e_b = highByte(r_e); EEPROM.write(516, r_e_b); r_e_b = lowByte(r_e); EEPROM.write(517, r_e_b); cmd.print("Writting eeprom fail counter = "); cmd.println(r_e); } add_log(31, E_case, E_val2w, 3); } EEPROM.commit(); } Ma ona kilka ważnych według mnie funkcji: sprawdzenie czy podana wartość mieści się w zakresie 0-255 sprawdzenie czy podany numer komórki nie jest zapisany na liście z niebezpiecznymi komórkami do zapisu, jeżeli tak to albo odmówi zapisu w danym miejscu albo poinformuje użytkownika, że trzeba uważać na tą komórką pamięci sprawdzenie czy aktualna wartość w podanej komórce jest inna od podanej, jeżeli tak to następuje zapis oraz zwiększenie się licznika udanych zapisów Sterownik sprawdza czy ilość zapisów nie przekracza wartości maksymalnej, np. 1500 zapisów, jeżeli przekracza, to informuje o tym użytkownika wysyłając mu wiadomość email. 5. Jeżeli coś z danymi jest nie tak, to użytkownik może dostać wiadomość, nie chcę jej jednak otrzymywać często, wystarczy przypomnienie np. co godzinę. Dlatego karta sieciowa ustawia jedno godzinny time out na wysyłanie wiadomości, jeżeli w czasie jednej godziny użytkownik nie naprawi usterki (nie zresetuje przy pomocy komendy licznika zapisów) to wyśle on kolejną wiadomość. Obsługa komend przez kartę sieciową: Podczas budowy i programowania tego urządzenia miałem na celu stworzenie czegoś w rodzaju zdalnego zarządzania całym urządzeniem, obecnie sterownik pracuje u dziadka dlatego muszę mieć możliwość kontrolowania jego działania zdalnie. Dla przykładu, jedyną opcją na dodanie alarmu jest wpisanie go w formie komendy na telefonie. Plusem tego rozwiązania jest to, że nie muszę jechać do dziadka aby zmienić np. godzinę włączenia światła itp., mam także podgląd na to czy wszystko działa. Komendy wprowadzane są w widżecie Terminal w aplikacji Blynk, kod obsługujący komendy: BLYNK_WRITE(V6) { String cmd_request = " "; cmd_request = param.asStr(); if (String("cls") == cmd_request) { cmd.clear(); add_log(51, 1, 999, 999); } else if (cmd_request.startsWith("show_alarms=") || cmd_request.startsWith("sha=")) { uint8_t index_val_1 = cmd_request.indexOf('='); uint8_t cmd_val = cmd_request.substring(index_val_1 + 1).toInt(); show_all_alarms[3] = cmd_val + '0'; send_command(show_all_alarms, 8); add_log(52, cmd_val, 999, 999); } else if (String("alarms_counter") == cmd_request || String("ac") == cmd_request) { int e_count = 0, n = 0; while (n <= ile_alarmow) { if (EEPROM.read((n * 5) + 1) != 127) { e_count++; } n++; } cmd.print("Esp alarms: "); cmd.println(e_count); send_command(show_alarms_counter, 8); add_log(53, e_count, 999, 999); } else if (cmd_request.startsWith("display_alarm=") || cmd_request.startsWith("da=")) { uint8_t index_val_1 = cmd_request.indexOf('='); uint8_t ter_val = cmd_request.substring(index_val_1 + 1).toInt(); if (ter_val <= ile_alarmow) { cmd.print("Displayed alarm "); cmd.print(ter_val); cmd.println(" :"); cmd.print("-Esp: "); cmd.flush(); int p = ter_val * 5; cmd.print("f(x): "); cmd.print(EEPROM.read(p + 4)); cmd.print("|D: "); cmd.print(EEPROM.read(p + 1)); cmd.print("|H: "); cmd.print(EEPROM.read(p + 2)); cmd.print("|MIN: "); cmd.print(EEPROM.read(p + 3)); cmd.print("|X: "); cmd.println(EEPROM.read(p + 5)); cmd.flush(); show_alarm[4] = ter_val + '0'; send_command(show_alarm, 8); } else { cmd.print("Wrong alarm case (0-"); cmd.print(ile_alarmow); cmd.println(")"); cmd.flush(); } add_log(54, ter_val, 999, 999); } else if (cmd_request.startsWith("set_alarm=") || cmd_request.startsWith("sa=")) { int eep_szuf = 1; int eep_buff = 1; uint8_t eep_a, eep_b, eep_c, eep_d, eep_f; uint8_t index_val_1, index_val_2, index_val_3, index_val_4, index_val_5, index_val_6; int cmd_val_1, cmd_val_2, cmd_val_3, cmd_val_4, cmd_val_5, cmd_val_6; index_val_1 = cmd_request.indexOf('='); index_val_2 = cmd_request.indexOf(','); index_val_3 = cmd_request.indexOf('-'); index_val_4 = cmd_request.indexOf(':'); index_val_5 = cmd_request.indexOf('&'); index_val_6 = cmd_request.indexOf('*'); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); cmd_val_2 = cmd_request.substring(index_val_2 + 1).toInt(); cmd_val_3 = cmd_request.substring(index_val_3 + 1).toInt(); cmd_val_4 = cmd_request.substring(index_val_4 + 1).toInt(); cmd_val_5 = cmd_request.substring(index_val_5 + 1).toInt(); cmd_val_6 = cmd_request.substring(index_val_6 + 1).toInt(); if (cmd_request[index_val_1] == '=' && cmd_request[index_val_2] == ',' && cmd_request[index_val_3] == '-' && cmd_request[index_val_4] == ':' && cmd_request[index_val_5] == '&' && cmd_request[index_val_6] == '*') { if ((cmd_val_2 >= 0 && cmd_val_2 < 9) && (cmd_val_3 > -1 && cmd_val_3 < 24) && (cmd_val_4 > -1 && cmd_val_4 <= 60)) { if (cmd_val_1 == 127) { cmd.print("[auto-case]-"); for (int w = 0; w <= ile_alarmow; w++) { if (EEPROM.read((w * 5) + 1) == 127) { eeprom_optimalization(0, w, 1, 0); byte x = EEPROM.read(0); cmd.print("Setting auto alarm value to "); cmd.println(x); cmd.flush(); //EEPROM.commit(); eep_szuf = x * 5; eep_buff = eep_szuf / 5; break; } } } else { if (cmd_val_1 >= 0 && cmd_val_1 <= ile_alarmow) { cmd.print("Setting manual alarm value to "); cmd.println(cmd_val_1); eep_szuf = cmd_val_1 * 5; eep_buff = eep_szuf / 5; } else { cmd.print("Wrong manual case value!"); return; } } cmd.println(); cmd.flush(); cmd.print("Setting alarm "); cmd.print(eep_buff); cmd.print(" on the day "); cmd.print(cmd_val_2); cmd.print(" ("); cmd.print(cmd_val_3); cmd.print(":"); cmd.print(cmd_val_4); cmd.print(") - function: "); cmd.print(cmd_val_5); cmd.print(" with value: "); cmd.println(cmd_val_6); cmd.flush(); eeprom_optimalization(eep_szuf + 1, cmd_val_2, 1, 0); eeprom_optimalization(eep_szuf + 2, cmd_val_3, 1, 0); eeprom_optimalization(eep_szuf + 3, cmd_val_4, 1, 0); eeprom_optimalization(eep_szuf + 4, cmd_val_5, 1, 0); eeprom_optimalization(eep_szuf + 5, cmd_val_6, 1, 0); //EEPROM.commit(); set_alarm[2] = cmd_val_2 + '0'; set_alarm[3] = cmd_val_3 + '0'; set_alarm[4] = cmd_val_4 + '0'; set_alarm[6] = cmd_val_5 + '0'; set_alarm[7] = cmd_val_6 + '0'; set_alarm[9] = eep_buff + '0'; send_command(set_alarm, 13); } else { cmd.println("Wrong time !"); cmd.flush(); } } else { cmd.println("Wrong command structure!"); cmd.flush(); } add_log(26, eep_buff, cmd_val_5, cmd_val_6); } else if (String("alarm_help") == cmd_request || String("ah") == cmd_request) { cmd.clear(); cmd.println("write: set_alarm=y,D-H:M&F*X$A"); String y_nr_str = "y - nr. szufladki (127 = auto, 0-" + String(ile_alarmow) + " counter)"; cmd.println(y_nr_str); cmd.println("D - dzien (1-7) 8=all"); cmd.println("H - godzina (0-23)"); cmd.println("M - minuta (0-59)"); cmd.println("F - funkcja (1-6)[alarm_function=help]"); cmd.println("x - wartosc funkcji"); cmd.println(""); cmd.flush(); cmd.println("______________"); cmd.println("F: 1 - gniazdo 1 [1 or 0] (pompa)"); cmd.println(" 2 - gniazdo 2 [1 or 0]"); cmd.println(" 3 - pasek led [0 - 100]"); cmd.println(" 4 - karmnik [none]"); cmd.flush(); cmd.println(" 5 - auto time syncing [none]"); cmd.println(" 6 - display brightness [0 - 3]"); cmd.println(" 7 - reset device [0-2:arduino/esp/all]"); cmd.flush(); add_log(55, 1, 999, 999); } else if (cmd_request.startsWith("rm_alarm=") || cmd_request.startsWith("rma=")) { uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 < 0 || cmd_val_1 >= ile_alarmow) { cmd.print("Wrong alarm number - range(0-") ; cmd.print(ile_alarmow); cmd.println(")"); add_log(56, cmd_val_1, 0, 999); } else { cmd.print("Removing alarm nr. "); cmd.println(cmd_val_1); rm_single_alarm[3] = cmd_val_1 + '0'; int n = cmd_val_1; n = n * 5; eeprom_optimalization(n + 1, 127, 1, 0); eeprom_optimalization(n + 2, 127, 1, 0); eeprom_optimalization(n + 3, 127, 1, 0); eeprom_optimalization(n + 4, 127, 1, 0); eeprom_optimalization(n + 5, 127, 1, 0); //EEPROM.commit(); send_command(rm_single_alarm, 8); add_log(56, cmd_val_1, 1, 999); } } else if (String("rm_all_alarm") == cmd_request) { send_command(rm_all_alarms, 8); for (int n = 1; n <= ((ile_alarmow + 1) * 5) - 1; n++) { eeprom_optimalization(n, 127, 0, 0); //EEPROM.commit(); if (n % 10 == 0) { cmd.print("Removed case from "); cmd.print(n - 10); cmd.print(" to "); cmd.println(n); } } eeprom_optimalization(0, 0, 1, 0); //EEPROM.commit(); add_log(57, 1, 999, 999); } else if (String("millis_usage") == cmd_request || String("mu") == cmd_request) { float x, z; cmd.println(); cmd.print("ESP8266: "); x = millis() / (1000 * 60); int millis_esp = millis() / (1000 * 60); cmd.print(millis() / (1000 * 60)); cmd.print(" min. / 71,580 min. - "); z = x / 71580; cmd.print(z, 3); cmd.println(" %"); send_command(disp_millis, 6); add_log(58, millis_esp, 999, 999); } else if (cmd_request.startsWith("read_ard_eeprom") || cmd_request.startsWith("rae")) { //eeprom_action[11] = {0x7E, 'E', 0, 'P', '.', 0, 0, 'm', 0, 'A', 0, 0x7F}; uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 > 1023 || cmd_val_1 < 0) { cmd.println("Wrong value (0-1023)"); add_log(59, cmd_val_1, 0, 999); } else { byte Bdata_a = 0, Bdata_b = 0; Bdata_a = highByte(cmd_val_1); Bdata_b = lowByte(cmd_val_1); cmd.print("highByte = "); cmd.print(Bdata_a); cmd.print(" - lowByte = "); cmd.println(Bdata_b); //eeprom_action[12] = {0x7E, 'E', 0, 'P', '.', 0, 0, 'm', 0, 'A', 0, 0x7F}; eeprom_action[2] = 1 + '0'; eeprom_action[5] = Bdata_a; eeprom_action[6] = Bdata_b; eeprom_action[8] = 0 + '0'; send_command(eeprom_action, 12); add_log(59, cmd_val_1, 1, 999); } } else if (cmd_request.startsWith("write_ard_eeprom=") || cmd_request.startsWith("wae=")) { uint8_t index_val_1, index_val_2; int cmd_val_1, cmd_val_2; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); index_val_2 = cmd_request.indexOf(','); cmd_val_2 = cmd_request.substring(index_val_2 + 1).toInt(); if (cmd_val_1 > 1023 || cmd_val_1 < 0 || cmd_val_2 > 255 || cmd_val_2 < 0) { cmd.println("Wrong value (val1: 0-1023 val2: 0-255)"); add_log(60, cmd_val_1, cmd_val_2, 0); } else { byte Bdata_a = 0, Bdata_b = 0; Bdata_a = highByte(cmd_val_1); Bdata_b = lowByte(cmd_val_1); cmd.print("highByte = "); cmd.print(Bdata_a); cmd.print(" - lowByte = "); cmd.println(Bdata_b); //eeprom_action[12] = {0x7E, 'E', 0, 'P', '.', 0, 0, 'm', 0, 'A', 0, 0x7F}; cmd.print("Writening ard eeprom case "); cmd.print(cmd_val_1); cmd.print(" with value of "); cmd.println(cmd_val_2); eeprom_action[2] = 2 + '0'; eeprom_action[5] = Bdata_a; eeprom_action[6] = Bdata_b; eeprom_action[8] = cmd_val_2; send_command(eeprom_action, 12); add_log(60, cmd_val_1, cmd_val_2, 1); } } else if (cmd_request.startsWith("sync_time=")) { //set_time[12] = {0x7E, 'T', 0, 0, 0, '/', 0, 0, 0, 's' 0, 0x7F}; //H:Min-D/M.Y uint8_t i_val_1, i_val_2, i_val_3, i_val_4, i_val_5; int val_1, val_2, val_3, val_4, val_5; i_val_1 = cmd_request.indexOf('='); val_1 = cmd_request.substring(i_val_1 + 1).toInt(); i_val_2 = cmd_request.indexOf(':'); val_2 = cmd_request.substring(i_val_2 + 1).toInt(); i_val_3 = cmd_request.indexOf('-'); val_3 = cmd_request.substring(i_val_3 + 1).toInt(); i_val_4 = cmd_request.indexOf('/'); val_4 = cmd_request.substring(i_val_4 + 1).toInt(); i_val_5 = cmd_request.indexOf('.'); val_5 = cmd_request.substring(i_val_5 + 1).toInt(); String currentTime = String(val_1) + ":" + String(val_2) + ":" + String(second()); String currentDate = String(val_3) + " " + String(val_4) + " " + String(val_5 + 2000); cmd.print(currentTime); cmd.print(" - "); cmd.println(currentDate); cmd.flush(); set_time[2] = val_1 + '0'; set_time[3] = val_2 + '0'; set_time[4] = second() + '0'; set_time[6] = val_3 + '0'; set_time[7] = val_4 + '0'; set_time[8] = val_5 + '0'; send_command(set_time, 12); add_log(61, val_1, val_2, val_3); } else if (String("auto_sync_time") == cmd_request || String("ast") == cmd_request) { String currentTime = String(hour()) + ":" + minute() + ":" + second(); String currentDate = String(day()) + " " + month() + " " + year(); cmd.print(currentTime); cmd.print(" - "); cmd.println(currentDate); set_time[2] = hour() + '0'; set_time[3] = minute() + '0'; set_time[4] = second() + '0'; set_time[6] = day() + '0'; set_time[7] = month() + '0'; set_time[8] = year() - 2000 + '0'; send_command(set_time, 12); add_log(62, hour(), minute(), day()); } else if (cmd_request.startsWith("Arduino_info=") || cmd_request.startsWith("ai=")) { uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); ard_info[3] = (byte)cmd_val_1 + '0'; send_command(ard_info, 8); add_log(63, cmd_val_1, 999, 999); } else if (String("arduino_info_help") == cmd_request || String("aih") == cmd_request) { cmd.println("1 - show arduino alarm syncing counter"); cmd.println("2 - print arduino eeprom in serial monitor"); cmd.println("3 - reset alarm syncing counter"); cmd.println("4 - show arduino eeprom succesful writing counter"); cmd.flush(); cmd.println("5 - show arduino eeprom fail writing counter"); cmd.println("6 - reset arduino eeprom succesful writing counter"); cmd.println("7 - reset arduino eeprom fail writing counter"); cmd.flush(); cmd.println("8 - reset arduino eeprom succesful and fail writing counter"); cmd.flush(); add_log(64, 1, 999, 999); } else if (cmd_request.startsWith("read_esp_alarm") || cmd_request.startsWith("rea")) { uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); int n = 0; int p = 0; byte ac_2_log = 0; byte log_score = (byte)cmd_val_1; if (cmd_val_1 == 0 || cmd_val_1 == 1) { }else{ cmd.println("Wrong argument (0 or 1)"); cmd.flush(); add_log(65, log_score, ac_2_log, 999); return; } while (n <= ile_alarmow) { if (cmd_val_1 == 0) { if (EEPROM.read((n * 5) + 1) != 127 && n <= ile_alarmow) { p = n * 5; cmd.print("Szuf."); cmd.print(n); cmd.print(" ->f(x): "); cmd.print(EEPROM.read(p + 4)); //1 cmd.print(" |D: "); cmd.print(EEPROM.read(p + 1)); //2 cmd.print("|H: "); cmd.print(EEPROM.read(p + 2)); //3 cmd.print("|MIN: "); cmd.print(EEPROM.read(p + 3)); //4 cmd.print("|X: "); cmd.println(EEPROM.read(p + 5)); //5 cmd.flush(); ac_2_log += 1; } } else { p = n * 5; cmd.print("Szuf."); cmd.print(n); cmd.print(" ->f(x): "); cmd.print(EEPROM.read(p + 1)); cmd.print(" |D: "); cmd.print(EEPROM.read(p + 2)); cmd.print("|H: "); cmd.print(EEPROM.read(p + 3)); cmd.print("|MIN: "); cmd.print(EEPROM.read(p + 4)); cmd.print("|X: "); cmd.println(EEPROM.read(p + 5)); cmd.flush(); ac_2_log += 1; } n++; } add_log(65, log_score, ac_2_log, 999); } else if (String("rm_esp_all_alarm") == cmd_request) { int n = 0; for (n = 1; n <= ((ile_alarmow + 1) * 5) - 1; n++) { eeprom_optimalization(n, 127, 0, 0); //EEPROM.commit(); if (n % 10 == 0) { cmd.print("Removed case from "); cmd.print(n - 10); cmd.print(" to "); cmd.println(n); } } //EEPROM.commit(); add_log(66, 1, 999, 999); } else if (cmd_request.startsWith("write_esp_eeprom=") || cmd_request.startsWith("wee=")) { uint8_t index_val_1; int cmd_val_1; uint8_t index_val_2; int cmd_val_2; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); index_val_2 = cmd_request.indexOf(','); cmd_val_2 = cmd_request.substring(index_val_2 + 1).toInt(); if (cmd_val_1 > -1 && cmd_val_1 < 1000 && cmd_val_2 > -1 && cmd_val_2 < 256) { eeprom_optimalization(cmd_val_1, cmd_val_2, 0, 0); //EEPROM.commit(); } else { cmd.print("Error value! Val1 = Range (0-1000) / Val2 = Range(0-255)"); cmd.flush(); add_log(67, cmd_val_1, cmd_val_2, 0); return; } cmd.print("Writening to Esp eeprom case "); cmd.print(cmd_val_1); cmd.print(" value "); cmd.println(cmd_val_2); cmd.flush(); add_log(67, cmd_val_1, cmd_val_2, 1); } else if (cmd_request.startsWith("read_esp_eeprom=") || cmd_request.startsWith("ree=")) { uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 > -1 && cmd_val_1 < 1000) { cmd.print("Reading Esp eeprom case "); cmd.print(cmd_val_1); cmd.print(" = "); cmd.println(EEPROM.read(cmd_val_1)); //EEPROM.commit(); cmd.flush(); add_log(68, cmd_val_1, EEPROM.read(cmd_val_1), 1); } else { cmd.println("Wrong value range(0-1000)!"); cmd.flush(); add_log(68, cmd_val_1, 0, 0); } } else if (String("clear_arduino_eeprom") == cmd_request) { cmd.println("Clearing Arduino eeprom with value 127"); cmd.flush(); send_command(clear_ard_eeprom, 8); add_log(69, 1, 999, 999); } else if (String("clear_esp_eeprom") == cmd_request) { for (int n = 0; n < 4096; n++) { eeprom_optimalization(n, 127, 0, 1); } //EEPROM.commit(); add_log(70, 1, 999, 999); } else if (String("what's_time") == cmd_request || String("wt") == cmd_request) { //what_is_time[9] = {0x7E, 'W', 'H', 'a', '|', 'S', 0, 0x7F}; send_command(what_is_time, 8); add_log(71, 1, 999, 999); } else if (String("debug_help") == cmd_request || String("dh") == cmd_request) { cmd.clear(); cmd.flush(); cmd.println("Debuging option: "); cmd.println("write: debug_mode=x"); cmd.println(); cmd.println("______________"); cmd.println(" [0]-disable all debuging info"); cmd.flush(); cmd.println(" [1]-enable recive data debug"); cmd.println(" [2]-enable check control sum debug"); cmd.println(" [3]-enable send command debug"); cmd.println(" [4]-enable sync alarms debug"); cmd.println(" [5]-enable thingspeak data debug"); cmd.flush(); cmd.println(" [6]-enable feeding info debug"); cmd.println(" [7]-enable data about syncing pwm value"); cmd.println(" [8]-enable show free ram space"); cmd.println(" [9]-enable show add log result"); cmd.flush(); cmd.println(" [10]-enable check data is ok debug"); cmd.println(" [11]-enable send check is new day command debug"); cmd.flush(); /* bool D_recive_data_single_char = 0; bool D_check_control_sum = 0; bool D_sync_alarms = 0; bool D_send_command = 0; */ add_log(72, 1, 999, 999); } else if (cmd_request.startsWith("debug_mode") || cmd_request.startsWith("dm")) { uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); byte D_ok = 1; if (cmd_val_1 == 0) { D_recive_data_single_char = 0; D_check_control_sum = 0; D_sync_alarms = 0; D_send_command = 0; D_thingspeak_info = 0; D_feeding_info = 0; D_pwm_syncing_value = 0; D_show_ram = 0; D_add_log = 0; D_check_is_data_ok = 0; D_check_is_data_error = 0; D_checkday_change = 0; } else if (cmd_val_1 == 1) { D_recive_data_single_char = 1; } else if (cmd_val_1 == 2) { D_check_control_sum = 1; } else if (cmd_val_1 == 3) { D_send_command = 1; } else if (cmd_val_1 == 4) { D_sync_alarms = 1; } else if (cmd_val_1 == 5) { D_thingspeak_info = 1; } else if (cmd_val_1 == 6) { D_feeding_info = 1; } else if (cmd_val_1 == 7) { D_pwm_syncing_value = 1; } else if (cmd_val_1 == 8) { D_show_ram = 1; } else if (cmd_val_1 == 9) { D_add_log = 1; } else if (cmd_val_1 == 10) { D_check_is_data_ok = 1; D_check_is_data_error = 1; } else if(cmd_val_1 == 11) { D_checkday_change = 1; } else { cmd.println("Wrong argument!"); cmd.flush(); D_ok = 0; } add_log(73, cmd_val_1, D_ok, 999); } else if (String("feed_info") == cmd_request || String("fi") == cmd_request) { feeding_time = EEPROM.read(510); //510 retraction_time = EEPROM.read(511); //511 feeding_today = EEPROM.read(509); //509 F_pump_pin = EEPROM.read(508); cmd.print("1. Today feeding "); cmd.print(feeding_today); cmd.println(" times"); cmd.println(); cmd.flush(); cmd.println("2. Feeding settings: "); cmd.print(" a) Feeding time = "); cmd.print(feeding_time); cmd.println(" s"); cmd.print(" b) Retraction time = "); cmd.print(retraction_time); cmd.println(" s"); cmd.flush(); cmd.print(" c) Max fedding per day = "); cmd.println(max_daily_feeding_counter); cmd.flush(); cmd.print(" d) OFF this socket, when feeding (0-none/1-blue/2-red/3-all)= "); cmd.println(F_pump_pin); cmd.flush(); cmd.print(" e) time to turn on socket (min)= "); cmd.println(time_to_return); cmd.flush(); cmd.print(" f) feeding status: "); if (EEPROM.read(513) == 0) { cmd.println("Ok"); } else if (EEPROM.read(513) == 1) { cmd.println("No feeding today"); } else if (EEPROM.read(513) == 2) { cmd.println("Feeding is permanently Off"); } else { cmd.println("Error with value! out of range(0-2)"); add_log(74, 3, 999, 999); return; } cmd.flush(); add_log(74, EEPROM.read(513), 999, 999); } else if (cmd_request.startsWith("feed_sett=") || cmd_request.startsWith("fs=")) { uint8_t index_val_1; uint8_t cmd_val_1; uint8_t index_val_2; uint8_t cmd_val_2; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); index_val_2 = cmd_request.indexOf(','); cmd_val_2 = cmd_request.substring(index_val_2 + 1).toInt(); if (cmd_val_1 == 1) { if (cmd_val_2 > 0 && cmd_val_2 < 30) { eeprom_optimalization(510, cmd_val_2, 1, 0); //EEPROM.commit(); feeding_time = EEPROM.read(510); cmd.print("Setting feeding time to "); cmd.print(feeding_time); cmd.println(" s"); cmd.flush(); add_log(75, 1, cmd_val_2, 1); } else { cmd.print("Wrong argument 1s - 29s"); cmd.flush(); add_log(75, 1, cmd_val_2, 0); } } else if (cmd_val_1 == 2) { if (cmd_val_2 > 0 && cmd_val_2 < 30) { eeprom_optimalization(511, cmd_val_2, 1, 0); //EEPROM.commit(); retraction_time = EEPROM.read(511); cmd.print("Setting retraction time to "); cmd.print(retraction_time); cmd.println(" s"); cmd.flush(); add_log(75, 2, cmd_val_2, 1); } else { cmd.print("Wrong argument 1s - 29s"); cmd.flush(); add_log(75, 2, cmd_val_2, 0); } } else if (cmd_val_1 == 3) { bool add_log75 = 1; if (cmd_val_2 == 1) { cmd.println("Setting socket number to blue, number 1"); eeprom_optimalization(508, 1, 1, 0); //EEPROM.commit(); F_pump_pin = EEPROM.read(508); } else if (cmd_val_2 == 2) { cmd.println("Setting socket number to red, number 2"); eeprom_optimalization(508, 2, 1, 0); //EEPROM.commit(); F_pump_pin = EEPROM.read(508); } else if (cmd_val_2 == 3) { cmd.println("Setting socket number to red and blue, number 3"); eeprom_optimalization(508, 3, 1, 0); //EEPROM.commit(); F_pump_pin = EEPROM.read(508); } else if (cmd_val_2 == 0) { cmd.println("Setting socket number to none, number 0"); eeprom_optimalization(508, 0, 1, 0); //EEPROM.commit(); F_pump_pin = EEPROM.read(508); } else { cmd.println("Error value! range(1-blue / 2-red / 3-red and blue / 0-none)"); add_log75 = 0; } add_log(75, 3, cmd_val_2, add_log75); } else if (cmd_val_1 == 4) { bool add_log75 = 1; if (cmd_val_2 > 0 && cmd_val_2 <= 60) { eeprom_optimalization(507, cmd_val_2, 1, 0); //EEPROM.commit(); time_to_return = EEPROM.read(507); cmd.print("Setting time_to_return value to "); cmd.print(time_to_return); cmd.println(" min"); } else { cmd.println("Wrong value. range(1-60)"); add_log75 = 0; } cmd.flush(); add_log(75, 4, cmd_val_2, add_log75); } else { cmd.println("Wrong function number: from 1 to 4"); cmd.flush(); add_log(75, 5, 999, 999); } } else if (String("feed_sett_info") == cmd_request || String("fsi") == cmd_request) { cmd.clear(); cmd.println(" feed_sett=x,y"); cmd.println(""); cmd.println("---------------------------------"); cmd.flush(); cmd.println(""); cmd.println("--> 1 - setting feeding time in seconds (y = 1-60)"); cmd.println(""); cmd.println("--> 2 - setting retraction time in seconds (y = 1-60)"); cmd.println(""); cmd.println("--> 3 - setting socket color to off when feeding (y: 0= none | 1= blue | 2= red | 3= all)"); cmd.flush(); add_log(76, 1, 999, 999); } else if (cmd_request.startsWith("set_display_brightnes=") || cmd_request.startsWith("sdb=")) { uint8_t index_val_1; uint8_t cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 > -1 && cmd_val_1 <= 3) { cmd.print("Setting display brightness to "); cmd.println(cmd_val_1); cmd.flush(); //set_disp_brightnes[8] = {0x7E, 'S', 'd', 0, 'B', '?', 0, 0x7F}; set_disp_brightnes[3] = cmd_val_1 + '0'; send_command(set_disp_brightnes, 8); add_log(77, cmd_val_1, 1, 999); } else { cmd.println("Wrong value - range(0-3)"); cmd.flush(); add_log(77, 4, 0, 999); } } else if (cmd_request.startsWith("reset_device=")) { uint8_t index_val_1; uint8_t cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); switch (cmd_val_1) { case 0: cmd.println("Resetting arduino..."); send_command(reset_ard, 9); add_log(78, cmd_val_1, 1, 999); break; case 1: cmd.println("Resetting esp..."); save_data(1); //save_data(0); delay(100); add_log(78, cmd_val_1, 1, 999); ESP.restart(); break; case 2: cmd.println("Resetting arduino and esp..."); send_command(reset_ard, 9); save_data(1); //save_data(0); delay(100); add_log(78, cmd_val_1, 1, 999); ESP.restart(); break; default: cmd.println("Wrong value! Range(0-2)(arduino/esp/all)"); add_log(78, 3, 0, 999); return; } } else if (cmd_request.startsWith("add_wifi=")) { uint8_t index_val_1 = cmd_request.indexOf('='); uint8_t cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); uint8_t index_val_2 = cmd_request.indexOf('-'); uint8_t index_val_3 = cmd_request.indexOf(','); String cmd_val_2 = cmd_request.substring(index_val_2 + 1, index_val_3); String cmd_val_3 = cmd_request.substring(index_val_3 + 1); if (cmd_val_1 == 1 || cmd_val_1 == 2) { int offset = 600 + (25 * (cmd_val_1 - 1)) + (cmd_val_1 - 1); for (int i = 0; i <= 25; i++) { eeprom_optimalization(offset + i, 0, 0, 1); } //EEPROM.commit(); byte len = cmd_val_2.length(); for (int i = 0; i < len; i++) { eeprom_optimalization(offset + i, cmd_val_2[i], 0, 0); } if (EEPROM.commit() == 1) { cmd.println("Writting ssid - OK!"); } else { cmd.println("Writting ssid - ERROR!"); add_log(79, cmd_val_1, 2, 999); return; } offset = 600 + (25 * (cmd_val_1 + 1)) + (cmd_val_1 + 1); for (int i = 0; i <= 25; i++) { eeprom_optimalization(offset + i, 0, 0, 0); } //EEPROM.commit(); len = cmd_val_3.length(); for (int i = 0; i < len; i++) { eeprom_optimalization(offset + i, cmd_val_3[i], 0, 0); } if (EEPROM.commit() == 1) { cmd.println("Writting pass - OK!"); } else { cmd.println("Writting pass - ERROR!"); add_log(79, cmd_val_1, 2, 999); return; } add_log(79, cmd_val_1, 1, 999); } else { cmd.println("Wrong value! Range(1-2)"); add_log(79, cmd_val_1, 0, 999); } } else if (cmd_request.startsWith("display_wifi=") || cmd_request.startsWith("dw=")) { uint8_t index_val_1 = cmd_request.indexOf('='); int cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 == 1 || cmd_val_1 == 2) { cmd.print("E_ssid->"); cmd.print(read_eeprom_wifi(cmd_val_1 - 1)); cmd.print("<- \ E_pass->"); cmd.print(read_eeprom_wifi(cmd_val_1 + 1)); cmd.println("<-"); cmd.flush(); } else if (cmd_val_1 == 0) { cmd.print("E_ssid->"); cmd.print(network0_E_ssid); cmd.print("<- \ E_pass->"); cmd.print(network0_E_pass); cmd.println("<-"); cmd.flush(); } else { cmd.println("Wrong value! Range(0-2)"); add_log(80, cmd_val_1, 0, 999); return; } add_log(80, cmd_val_1, 1, 999); } else if (String("Clear_all_wifi=AquaDzz@iadek19") == cmd_request) { for (int i = 600; i <= 703; i++) { eeprom_optimalization(i, 0, 0, 1); } //EEPROM.commit(); add_log(81, 1, 999, 999); } else if (String("daw") == cmd_request || String("display_all_wifi") == cmd_request) { for (int i = 0; i < 3; i++) { cmd.print(i); cmd.print("["); for (int j = 0; j <= 25; j++) { cmd.print((char)(EEPROM.read(600 + (25 * i) + i + j))); if (j != 25) { cmd.print(","); } } cmd.println("]"); cmd.flush(); } add_log(82, 1, 999, 999); } else if (cmd_request.startsWith("auto_feeding_off=") || cmd_request.startsWith("afo=")) { uint8_t index_val_1 = cmd_request.indexOf('='); byte cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 >= 0 && cmd_val_1 <= 2) { eeprom_optimalization(513, cmd_val_1, 1, 0); //EEPROM.commit(); if (cmd_val_1 == 0) { cmd.println("This function is off, feeding every day"); } else if (cmd_val_1 == 1) { cmd.println("feeding today is off, next feeding tomorrow"); } else if (cmd_val_1 == 2) { cmd.println("feeding every day is off, next feeding when this function will be off"); } else { cmd.println("Error"); add_log(83, cmd_val_1, 0, 999); return; } cmd.flush(); add_log(83, cmd_val_1, 1, 999); } else { cmd.println("Wrong value! range(0-2: no/one_day/every_day)"); add_log(83, 3, 0, 999); } } else if (cmd_request.startsWith("set_light_speed=") || cmd_request.startsWith("sls=")) { uint8_t index_val_1; uint8_t cmd_val_1; uint8_t index_val_2; uint8_t cmd_val_2; uint8_t sval_1 = 127; uint8_t sval_2 = 127; uint8_t sval_3 = 127; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); index_val_2 = cmd_request.indexOf(','); cmd_val_2 = cmd_request.substring(index_val_2 + 1).toInt(); if (cmd_val_1 >= 1 && cmd_val_1 <= 4) { if (cmd_val_2 >= 0 && cmd_val_2 <= 255 && cmd_val_2 != 127) { //-------------------------------0----1----2---3---4---5---6----7---8---9---10--11-- //send_light_time_value[12] = {0x7E, 'L', 'I', 0, 'Q', 0, 'H', '|', 0, '@', 0, 0x7F}; if (cmd_val_1 == 1) { sval_1 = cmd_val_2; cmd.print("Setting led_step_button value to "); cmd.println(sval_1); } else if (cmd_val_1 == 2) { sval_2 = cmd_val_2; cmd.print("Setting led_step_app value to "); cmd.println(sval_2); } else if (cmd_val_1 == 3) { sval_3 = cmd_val_2; cmd.print("Setting led_step_alarm value to "); cmd.println(sval_3); } else if (cmd_val_1 == 4) { sval_1 = cmd_val_2; sval_2 = cmd_val_2; sval_3 = cmd_val_2; cmd.print("Setting led_step_button value to "); cmd.println(sval_1); cmd.print("Setting led_step_app value to "); cmd.println(sval_2); cmd.print("Setting led_step_alarm value to "); cmd.println(sval_3); } else { cmd.println("Error!"); } send_light_time_value[3] = sval_1; send_light_time_value[5] = sval_2; send_light_time_value[8] = sval_3; send_command(send_light_time_value, 12); } else { cmd.println("Wrong second value! Range(0-255) without 127"); } } else { cmd.println("Wrong value! Range (1-4)"); } cmd.flush(); add_log(84, cmd_val_1, cmd_val_2, 999); } else if (String("wifi_info") == cmd_request || String("wi") == cmd_request) { cmd.clear(); cmd.print("* Selected wifi id = "); cmd.println(network_id); if (network_id != 0) { cmd.print("* wifi eeprom SSID = "); cmd.println(read_eeprom_wifi(network_id - 1)); cmd.print("* wifi eeprom PASS = "); cmd.println(read_eeprom_wifi(network_id + 1)); } else { cmd.print("* wifi default SSID = "); cmd.println(network0_E_ssid); cmd.print("* wifi default PASS = "); cmd.println(network0_E_pass); } cmd.flush(); cmd.print("* wifi ssid value = "); cmd.println(E_ssid); cmd.print("* wifi pass value = "); cmd.println(E_pass); cmd.flush(); cmd.println("*******************************"); cmd.print("digitalRead P6 = "); cmd.println(pcf8574.digitalRead(wifi_network_sw_a)); cmd.print("digitalRead P7 = "); cmd.println(pcf8574.digitalRead(wifi_network_sw_b)); cmd.flush(); add_log(85, network_id, 999, 999); } else if (String("esp_info") == cmd_request || String("ei") == cmd_request) { cmd.clear(); cmd.print("* Firmware version: "); cmd.println(F_version); cmd.print("* EEPROM eeprom_optimalization writing succesful counter = "); cmd.println((EEPROM.read(514) << 8) + EEPROM.read(515)); cmd.print("* EEPROM eeprom_optimalization writing fail counter = "); cmd.println((EEPROM.read(516) << 8) + EEPROM.read(517)); cmd.println(ESP.getFlashChipId()); cmd.flush(); cmd.print("* Free heap: "); cmd.println(ESP.getFreeHeap()); cmd.print("* save log counter: "); cmd.println(save_log_counter()); cmd.print("* buffor log counter: "); cmd.println(log_buffor_counter()); cmd.print("* file name = "); cmd.println(read_file_name()); cmd.flush(); add_log(86, 1, 999, 999); } else if (String("display_log") == cmd_request || String("dl") == cmd_request) { //send_data_log = true; int dl_counter = display_log(); add_log(87, dl_counter, 999, 999); } else if (String("log_info") == cmd_request || String("li") == cmd_request) { //send_data_log = true; cmd.print("* LOGS counter = "); int li_counter = log_buffor_counter(); cmd.println(li_counter); cmd.print("* kolejka size = "); cmd.println(kolejka_size()); cmd.flush(); cmd.print("* kolejka empty = "); cmd.println(kolejka_empty()); cmd.print("* current_log_filename = "); cmd.println(current_log_filename); cmd.flush(); add_log(88, li_counter, kolejka_size(), (byte)kolejka_empty()); } else if (String("send_all_logs") == cmd_request) { cmd.println("ESP8266 will restart!"); EEPROM.write(522, 1); EEPROM.commit(); save_data(1); //save_data(0); delay(100); add_log(89, 1, 999, 999); ESP.restart(); } else if (cmd_request.startsWith("send_log=")) { uint8_t index_val_1; String cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1); if (!SD.begin(D8)) { send_mail_serial("initialization failed!", 1); add_log(32, 1, 999, 999); add_log(92, 0, 999, 999); return; } if (SD.exists(cmd_val_1)) { cmd.println("ESP8266 will restart!"); EEPROM.write(522, 2); EEPROM.commit(); save_file_name(cmd_val_1); cmd.println(read_file_name()); cmd.flush(); save_data(1); //save_data(0); delay(1000); ESP.restart(); } else { cmd.println("This file doesn't exist!"); cmd.flush(); } } else if (cmd_request.startsWith("change_filename=")) { uint8_t index_val_1; String cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1); save_file_name(cmd_val_1); delay(50); read_file_name(); } else if (String("sd_card_info") == cmd_request || String("sci") == cmd_request) { if (!SD.begin(D8)) { cmd.println("initialization failed!"); add_log(32, 1, 999, 999); delay(50); add_log(90, 0, 999, 999); return; } cmd.println("initialization done."); cmd.flush(); File root = SD.open("/"); printDirectory(root, 0); cmd.println("done!"); cmd.flush(); add_log(90, 1, 999, 999); } else if (cmd_request.startsWith("remove_file=")) { uint8_t index_val_1; String cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1); if (!SD.begin(D8)) { cmd.println("initialization failed!"); add_log(32, 1, 999, 999); return; } cmd.println("initialization done."); cmd.flush(); File root = SD.open("/"); if (!SD.exists(cmd_val_1)) { cmd.println("This file doesn't exist!"); cmd.flush(); add_log(91, 0, 999, 999); return; } SD.remove(cmd_val_1); if (!SD.exists(cmd_val_1)) { cmd.println("The file was deleted successfully !"); cmd.flush(); } else { cmd.println("The file could not be deleted !"); cmd.flush(); } add_log(91, 1, 999, 999); } else if (String("Remove_All_Files") == cmd_request) { if (!SD.begin(D8)) { cmd.println("initialization failed!"); add_log(32, 1, 999, 999); return; } cmd.println("initialization done."); cmd.flush(); File root = SD.open("/"); delay(100); rm(root, rootpath); if (!DeletedCount && !FailCount && !FolderDeleteCount) { add_log(17, DeletedCount, FailCount, 0); } else { cmd.print("Deleted "); cmd.print(DeletedCount); cmd.print(" file"); if (DeletedCount != 1) { cmd.print("s"); } cmd.print(" and "); cmd.print(FolderDeleteCount); cmd.print(" folder"); if (FolderDeleteCount != 1) { cmd.print("s"); } cmd.println(" from SD card."); if (FailCount > 0) { cmd.print("Failed to delete "); cmd.print(FailCount); cmd.print(" item"); if (FailCount != 1) { cmd.print("s"); } } add_log(17, DeletedCount, FailCount, 0); FailCount = 0; FolderDeleteCount = 0; DeletedCount = 0; } } else if (String("help") == cmd_request) { cmd.clear(); cmd.println("You can use this command: "); cmd.println("-------------------------------------------"); cmd.println("* cls"); cmd.println("* show_alarms or sha"); cmd.println("* alarms_counter or ac"); cmd.println("* display_alarm=[nr.szuf.] or da"); cmd.println("* set_alarm=[check in alarm_help or ah] or sa"); cmd.flush(); cmd.println("* rm_alarm=[remove alarm nr.] or rma"); cmd.println("* rm_all_alarm"); cmd.println("* millis_usage or mu"); cmd.println("* read_ard_eeprom=[nr.szuf.] or rae"); cmd.println("* write_ard_eeprom=x,y[szuf.0-1023/val.0-255] or wae"); cmd.println("* sync_time=[H:Min-D/M.Y]H=0-23|Min=0-59|Y=1-100"); cmd.flush(); cmd.println("* auto_sync_time or ast"); cmd.println("* Arduino_info=[check in arduino_info_help or aih] or ai"); cmd.println("* read_esp_alarm=[0-used/1-all] or rea"); cmd.println("* rm_esp_all_alarm"); cmd.println("* write_esp_eeprom=x,y[E.case 0-1023/val.0-255 or wee"); cmd.flush(); cmd.println("* read_esp_eeprom=x[nr.szuf.] or ree"); cmd.println("* clear_arduino_eeprom"); cmd.println("* clear_esp_eeprom"); cmd.println("* what's_time[arduino rtc time] or wt"); cmd.flush(); cmd.println("* debug_help or dh"); cmd.println("* debug_mode=[0/10] or dm"); cmd.println("* feed_info or fi"); cmd.println("* feed_sett=[check in feed_sett_info or fsi]"); cmd.println("* set_display_brightnes= x[x= 0-3] or sdb=x"); cmd.flush(); cmd.println("* add_wifi=x-y,z[nr.0-3 | ssid | pass]"); cmd.println("* display_wifi=x[nr.0-3]"); cmd.println("* reset_device=[0-arduino/1-esp/2-all]"); cmd.println("* display_all_wifi or daw"); cmd.println("* auto_feeding_off=[0-2:no/one_day/every_day]"); cmd.flush(); cmd.println("* set_light_speed=[x=1-4:button/app/alarm/all | y=0-100] or sls"); cmd.println("* wifi_info or wi"); cmd.println("* esp_info or ei"); cmd.println("* display_log or dl"); cmd.println("* log_info or li"); cmd.flush(); cmd.println("* clear_all_logs"); cmd.println("* sd_card_info or sci"); cmd.println("* send_all_logs"); cmd.println("* send_log=(log name, log0001.txt)"); cmd.flush(); cmd.println("* change_filename=(log name, log0001.txt)"); cmd.println("* remove_file=(filename - L0001.txt) or Remove_All_Files"); cmd.println("-------------------------------------------"); cmd.flush(); add_log(100, 1, 999, 999); } else { cmd.print("Wrong command: "); cmd.write(param.getBuffer(), param.getLength()); cmd.println(" <- Try command: help"); add_log(100, 0, 999, 999); } cmd.flush(); } Jak to działa? Istnieją dwie opcje, użytkownik wpisał poprawną komendę lub wpisał nie znaną komendę. W pierwszym przypadku wykonuje się fragment kodu przypisany do danej komendy, np. komenda cls odpowiada za wyczyszczenie terminala. Jeżeli podano nieznaną komendę, to na terminalu wyświetli się napis Wrong command: (podana komenda) <- Try command: help. Zaprogramowałem także coś w stylu spisu treści, po wpisaniu komendy help, na terminalu pojawią się wszystkie dostępne komendy oraz ich parametry. Spis komend: * cls * show_alarms or sha * alarms_counter or ac * display_alarm=[nr.szuf.] or da * set_alarm=[check in alarm_help or ah] or sa * rm_alarm=[remove alarm nr.] or rma * rm_all_alarm * millis_usage or mu * read_ard_eeprom=[nr.szuf.] or rae * write_ard_eeprom=x,y[szuf.0-1023/val.0-255] or wae * sync_time=[H:Min-D/M.Y]H=0-23|Min=0-59|Y=1-100 * auto_sync_time or ast * Arduino_info=[check in arduino_info_help or aih] or ai * read_esp_alarm=[0-used/1-all] or rea * rm_esp_all_alarm * write_esp_eeprom=x,y[E.case 0-1023/val.0-255 or wee * read_esp_eeprom=x[nr.szuf.] or ree * clear_arduino_eeprom * clear_esp_eeprom * what's_time[arduino rtc time] or wt * debug_help or dh * debug_mode=[0/10] or dm * feed_info or fi * feed_sett=[check in feed_sett_info or fsi] * set_display_brightnes= x[x= 0-3] or sdb=x * add_wifi=x-y,z[nr.0-3 | ssid | pass] * display_wifi=x[nr.0-3] * reset_device=[0-arduino/1-esp/2-all] * display_all_wifi or daw * auto_feeding_off=[0-2:no/one_day/every_day] * set_light_speed=[x=1-4:button/app/alarm/all | y=0-100] or sls * wifi_info or wi * esp_info or ei * display_log or dl * log_info or li * clear_all_logs * sd_card_info or sci * send_all_logs * send_log=(log name, log0001.txt) * change_filename=(log name, log0001.txt) * remove_file=(filename - L0001.txt) or Remove_All_Files Logi: Dodałem tę funkcję jedynie w celach edukacyjnych (chciałem przetestować, czy tworzenie logów ma jakikolwiek sens oraz czy jest to trudne). Okazało się jednak, że logi mogą być pomocne, dzięki nim mogłem testować, czy alarmy wykonują się poprawnie o zadanej godzinie, dlaczego i kiedy resetuje się samoczynnie sterownik itp. Tworzenie logów polega na zapisywaniu w buforze w pamięci RAM informacji o wykonaniu się jakiegoś zdarzenia, o czasie wykonania się tego zdarzenia oraz o jego parametrach. Przykładowo, zauważyłem, że karta sieciowa resetuje się samoczynnie co jakiś czas. Dzięki zapisanym logą mogę dowiedzieć się kiedy dokładnie sterownik zresetował się, (znam moment zakończenia się funkcji setup) oraz przeanalizować co mogło taki reset wykonać. void setup() { ... add_log(48, datalog_d, datalog_m, datalog_y); add_log(30, 1, 999, 999); //Karta sieciowa wystartowała check_day_change(); } Jestem w stanie sprawdzić dzień, godzinę i minutę wykonania się praktycznie każdego elementu programu sterownika lub karty sieciowej, co jest bardzo pomocne. Każdy log składa się z 8 bajtów danych: D- dzień | H- godzina | M- minuta | A- co to jest za log | Val1_lB / Val1_hB- zapisany na maksymalnie dwóch bajtach parametr | Val2- drugi parametr | Val3- trzeci, ostatni parametr przykładowy log: 100->D19H20M14A1&1#127, 127 Taki log mówi mi, że 19-stego dnia miesiąco o godzinię 20:14 ktoś wcisnął na obudowie sterownika przycisk niebieski i teraz stan na przekaźniku nr.1 jest wysoki. Wiem także, że nie podano 2 i 3 parametru ponieważ mają one wartość 127, nie wiem dokładnie dlaczego wybrałem tą liczbę a nie na przykład 255, ale stosuję tą zasadę w mechanizmie logów oraz podczas odczytywania alarmów. 100-> oznacza, że jest to setny zapisany log od czasu wyzerowania licznika logów, ten licznik zapisywany jest także w pamięci "EEPROM". Spis wszystkich logów: LOG ACTION: -> ARDUINO <- * 1 - blue switch was press in arduino (val1 = 1[on] / 2[off]) * 2 - red switch was press in arduino (val1 = 1[on] / 2[off]) * 3 - green switch was press in arduino (val1 = 0-10) * 4 - feeding button was press in arduino (val1 = None) * 5 - arduino was lock feeding option * 6 - arduino execute alarm (val1 = alarm number | val2 = alarm value) * 7 - arduino was reset * 8 - The arduino eeprom has been cleared -> ESP <- * 17 - delete all sd card files (val1= rm_file counter \ val2 = fail_counter \ val3 = status[1-ok/0-error]) * 18 - repair data function is running (val1=val2repair|val2=1-repair_automaticly/2-repair_manual/0-error value) * 19 - sending error data value log (val1 = 1) * 20 - V0 button has been pressed (val1 = 1[on] / 2[off]) * 21 - V1 button has been pressed (val1 = 1[on] / 2[off]) * 22 - v2 led pwm slider (val1 = 0-10) * 23 - feeding button has been pressed * 24 - feeding has been ended * 25 - feeding has been cancled (val1 = 1[to much feeding] | 2[feeding is off today] | 3[feeding is off] | 4[feeding is now running]) * 26 - setting alarm (val1 = number of allarm | val2 = function) * 27 - executing terminal command (val1 = command number | val2 = ?) * 28 - clear all logs * 29 - log_counter (val1 = counter) * 30 - esp8266 boot up succesful val1 =1 * 31 - write to esp eeprom (val1 = eeprom case | val2 = value | val3 = 0-error value/1-safe_mode_trigered/2-eeprom write/3-eeprom don't write) * 32 - sd card initialization failed * 33 - email was send * 34 - save log on sd card (val1= 0 Can't create file!/ 1-save ok /) val2 = send_counter * 35 - recived button blue change state command (val1 = 1-on/0-off) * 36 - recived button red change state command (val1 = 1-on/0-off) * 37 - recived button green change state command (val1 = 0-10) * 38 - recived button yellow press - feeding (val1 = 1-feeding_ok/0-feeding error/2-feeding_is_now_run | val2= feeding_today_counter) * 39 - It's new day (val1 = yesterday | val2 = today) * 40 - resetting esp trigered by alarm (val1=1) * 41 - syncing time trigered by alarm (val1 = 1) * 42 - recived alarm synchronized counter from arduino (val1 = counter) * 43 - recived millis from arduino (val1 = ard_millis) * 44 - recived arduino eeprom case value (val1 = arduino eeprom case / val2 = eeprom value) * 45 - recived send to esp eeprom succesful writing counter (1-eeprom succesful writing counter|2-eeprom fail writing counter|?-wrong value) * 46 - recived and printing in cmd (val1 = funkcja | val2 = day | val3 = value) * 47 - recived arduino rtc time (val1 = hour | val2 = day | val3 = week day) * 48 - changed log filename (val1 = day | val2 = month | val3 = year) * 49 - save_file_name (val1 = 1-ok/0-error) 51 - Action = cls | val1 = 1 | val2 = None | val3 = None 52 - Action = show_alarms | val1 = cmd_val | val2 = None | val3 = None 53 - Action = alarms_co unter | val1 = e_count | val2 = None | val3 = None 54 - Action = display_alarm | val1 = ter_val | val2 = None | val3 = None 55 - Action = alarm_help | val1 = 1 | val2 = None | val3 = None 56 - Action = rm_alarm | val1 = 1(done)/0(error) | val2 = None | val3 = None 57 - Action = rm_all_alarm | val1 = 1 | val2 = None | val3 = None 58 - Action = millis_usage | val1 = millis_esp | val2 = None | val3 = None 59 - Action = read_ard_eeprom | val1 = arduino_eeprom_case | val2 = 1(done)/0(error) | val3 = None 60 - Action = write_ard_eeprom | val1 = arduino_eeprom_case | val2 = value_to_write | val3 = 1(done)/0(error) 61 - Action = sync_time | val1 = godzina | val2 = minuta | val3 = dzien 62 - Action = auto_sync_time | val1 = godzina | val2 = minuta | val3 = dzien 63 - Action = Arduino_info | val1 = cmd_val_1 | val2 = None | val3 = None 64 - Action = arduino_info_help | val1 = 1 | val2 = None | val3 = None 65 - Action = read_esp_alarm | val1 = 0(only alarm)/1(all eeprom cases)/?(error, wrong value 0 or 1) | val2 = ile_alarmow_odczytano | val3 = None 66 - Action = rm_esp_all_alarm | val1 = 1 | val2 = None | val3 = None 67 - Action = write_esp_eeprom | val1 = EEPROM case | val2 = value to write | val3 = 0(error!Wrong value 1 or 2)/1(Done) 68 - Action = read_esp_eeprom | val1 = EEPROM case | val2 = read value | val3 = 0(error!Wrong eeprom case)/1(Done) 69 - Action = clear_arduino_eeprom | val1 = 1 | val2 = None | val3 = None 70 - Action = clear_esp_eeprom | val1 = 1 | val2 = None | val3 = None 71 - Action = what's_time | val1 = 1 | val2 = None | val3 = None 72 - Action = debug_help | val1 = 1 | val2 = None | val3 = None 73 - Action = debug_mode | val1 = debug_option | val2 = (0-error value/1-ok) | val3 = None 74 - Action = feed_info | val1 = (0-feeding\1-not today\2-off\3-wrong value) | val2 = None | val3 = None 75 - Action = feed_sett | val1 = 1-5 | val2 = cmd_val_2 | val3 = 1(Done)/0(Error) 76 - Action = feed_sett_info | val1 = 1 | val2 = None | val3 = None 77 - Action = set_display_brightnes | val1 = 0-3 | val2 = 1(Done)/0(Error) | val3 = None 78 - Action = reset_device | val1 = 0-2 | val2 = 1(Done)/0(Error) | val3 = None 79 - Action = add_wifi | val1 = wifi_number | val2 = 1(Done)/0(Error)/2(commit error) | val3 = None 80 - Action = display_wifi | val1 = number of wifi to displayed | val2 = 1(Done)/0(Error) | val3 = None 81 - Action = Clear_all_wifi=AquaDzz@iadek19 | val1 = 1 | val2 = None | val3 = None 82 - Action = display_all_wifi | val1 = 1 | val2 = None | val3 = None 83 - Action = auto_feeding_off | val1 = 1 | val2 = None | val3 = None 84 - Action = set_light_speed | val1 = cmd_val_1 | val2 = cmd_val_2 | val3 = None 85 - Action = wifi_info | val1 = network_id | val2 = None | val3 = None 86 - Action = esp_info | val1 = 1 | val2 = None | val3 = None 87 - Action = display_log | val1 = display_log_counter | val2 = None | val3 = None 88 - Action = log_info | val1 = log_counter | val2 = kolejka_size | val3 = kolejka_empty 89 - Action = send_all_logs | val1 = 1 | val2 = None | val3 = None 90 - Action = sd_card_info | val1 = (1-sd card read ok\ 0-sd card read error) | val2 = None | val3 = None 91 - Action = remove_file | val1 = 1-delete ok/0-error | val2 = None | val3 = None 92 - Action = send_log | val1 = 1-ok/0-error | val2 = None | val3 = None 93 - Action = | val1 = 1 | val2 = None | val3 = None 94 - Action = | val1 = 1 | val2 = None | val3 = None 95 - Action = | val1 = 1 | val2 = None | val3 = None 96 - Action = | val1 = 1 | val2 = None | val3 = None 97 - Action = | val1 = 1 | val2 = None | val3 = None 98 - Action = | val1 = 1 | val2 = None | val3 = None 99 - Action = | val1 = 1 | val2 = None | val3 = None 100 - Action = help or kommand unknown | val1 = (1-help ok/0-command unknown) | val2 = None | val3 = None Funkcja dodająca logi: void add_log(byte action, int val1, int val2, int val3) { /* if (kolejka_size() >= 400) { cmd.println("Buffor is full! Saving data on sd card!"); cmd.flush(); save_data(1); //save_data(0); } */ if (val1 < 0 || val1 > 65000) { cmd.print("Error! Log_add function - val1 out of range(0-65000) ->"); cmd.println(val1); cmd.flush(); return; } byte val1_hb = 0, val1_lb = 0; val1_hb = highByte(val1); val1_lb = lowByte(val1); byte log_D = day(); byte log_H = hour(); byte log_M = minute(); if (log_D < 0 || log_D > 31) { cmd.print("Error! add_log function - problem with day() value! Out of range(0-31) ->"); cmd.println(log_D); return; } if (log_H < 0 || log_H > 23) { cmd.print("Error! add_log function - problem with hour() value! Out of range(0-23) ->"); cmd.println(log_H); return; } if (log_M < 0 || log_M > 59) { cmd.print("Error! add_log function - problem with minute() value! Out of range(0-59) ->"); cmd.println(log_M); return; } int push_case = kolejka_push(log_D); kolejka_push(log_H); kolejka_push(log_M); kolejka_push(action); kolejka_push(val1_hb); kolejka_push(val1_lb); if (val2 > 255 || val2 < 0) { kolejka_push(127); } else { kolejka_push(val2); } if (val3 > 255 || val3 < 0) { kolejka_push(127); } else { kolejka_push(val3); } if (D_add_log == 1) { cmd.print("Setting log case "); cmd.print(push_case / 8); cmd.print("-"); cmd.print(kolejka_size()); cmd.print(" at "); cmd.print(message_aray[push_case + 1]); cmd.print(":"); cmd.flush(); cmd.print(message_aray[push_case + 2]); cmd.print(", day "); cmd.print(message_aray[push_case]); cmd.print(" -> Action "); cmd.print(message_aray[push_case + 3]); cmd.print(" with val1: "); cmd.flush(); cmd.print((message_aray[push_case + 4] << 8) + message_aray[push_case + 5]); cmd.print(" with val2: "); cmd.print(message_aray[push_case + 6]); cmd.print(" with val3: "); cmd.println(message_aray[push_case + 7]); cmd.flush(); } } Zapisywanie danych na karcie SD: Tworzenie logów już działa, ale co z ich przechowywaniem w pamięci RAM? Jak wiadomo pamięć RAM kiedyś się skończy, nie mogę więc zapisywać w niej setek czy tysięcy logów, kolejnym problemem jest to, że po utracie zasilania wszystkie moje logi przepadną. W pierwszym momencie planowałem zapisywać logi w pamięci EEPROM, szybko zrezygnowałem jednak z tego pomysłu, ponieważ ograniczał mnie rozmiar tej pamięci. Zdecydowałem się na użycie karty pamięci jako magazynu na dane. Nie zrezygnowałem jednak z buffera na dane, powodem tej decyzji jest to, że dla karty SD nie jest zdrowe ciągłe otwieranie i zamykanie plików, np. 30 razy na minutę, 24 godziny na dobę przez cały rok. Lepszym pomysłem jest hurtowe zapisywanie kilku logów podczas jednego otwarcia i zamknięcia pliku. Aktualnie zapisuję na karcie pamięci 75 logów na jedno otwarcie pliku, buffer może pomieścić maksymalnie 110 logów, mam więc zapas w razie problemu z zapisem w danej chwili. wywołanie funkcji zapisującej na karcie SD: if (kolejka_size() >= 600) { save_data(1); } Co 5 sekund urządzenie sprawdza czy w bufferze znajduje się więcej niż 74 logi (ponieważ 600 komórek / 8 komórek na log = 75 logów). Jeżeli tak to następuje próba zapisu. Próba zapisania danych występuje także podczas resetowania się karty sieciowej przy użyciu alarmu lub komendy (sterownik główny lub karta sieciowa mogą resetować się auto magicznie, jeżeli ustawie taki alarm), ma to na celu zapobieganie przed utraceniem danych. funkcja zapisująca dane: void save_data(bool datafile_all) { if (!SD.begin(D8)) { cmd.println("initialization failed!"); cmd.flush(); add_log(32, 1, 999, 999); return; } else { sd_card_work = true; } long log_counter = readLongFromEEPROM(518); cmd.println("Saving data from buffer to sd card!"); cmd.print("Saving "); cmd.print(kolejka_size()); cmd.println(" logs !"); cmd.print("Log name = "); cmd.println(current_log_filename); cmd.print("Log counter = "); cmd.println(log_counter); cmd.flush(); File dataFile; String full_datalog_name = "DATALOG.txt"; if (datafile_all == 1) { full_datalog_name = current_log_filename; } if (!SD.exists(full_datalog_name)) { cmd.println(full_datalog_name + " doesn't exist! Creating file in progress... "); dataFile = SD.open(full_datalog_name, FILE_WRITE); dataFile.close(); if (!SD.exists(full_datalog_name)) { cmd.println("Can't create file!"); cmd.flush(); add_log(34, 0, 999, 999); return; } else { cmd.println("File was created!"); cmd.flush(); } } else { cmd.println("File " + full_datalog_name + " existing!"); cmd.flush(); } log_counter = readLongFromEEPROM(518); byte wd = 0; String dataString = ""; int send_counter = 0; byte k_size = kolejka_size(); while (kolejka_empty() == false) { if (wd == 0) { dataString = ""; dataFile = SD.open(full_datalog_name, FILE_WRITE); if (!dataFile) { cmd.println("Error When opening file " + full_datalog_name); dataFile.close(); return; } } dataString += String(log_counter); dataString += "->D"; dataString += String(kolejka_pop()); dataString += 'H'; dataString += String(kolejka_pop()); dataString += 'M'; dataString += String(kolejka_pop()); dataString += 'A'; dataString += String(kolejka_pop()); dataString += '&'; byte war1, war2; war1 = kolejka_pop(); war2 = kolejka_pop(); int wartosc = (war1 << 8) + war2; dataString += String(wartosc); dataString += '#'; dataString += String(kolejka_pop()); dataString += '$'; dataString += String(kolejka_pop()); dataString += '\n'; send_counter++; if (wd == 25 || kolejka_empty() == true) { dataFile.println(dataString); cmd.println("Writing " + full_datalog_name + " OK!"); dataFile.close(); dataFile = SD.open("DATALOG.txt", FILE_WRITE); if (!dataFile) { cmd.println("Error When opening file DATALOG.txt"); dataFile.close(); return; } dataFile.println(dataString); cmd.println("Writing DATALOG.txt OK!"); dataFile.close(); wd = -1; } wd++; log_counter++; } writeLongIntoEEPROM(518, log_counter); cmd.print("Log counter -> "); cmd.println(readLongFromEEPROM(518)); cmd.print("Succesful save "); cmd.print(send_counter); cmd.println(" logs"); cmd.flush(); if (dataString.length() > 0) { cmd.println("String is no empty!"); cmd.flush(); dataFile.close(); dataFile = SD.open(full_datalog_name, FILE_WRITE); if (!dataFile) { cmd.println("Error When opening file " + full_datalog_name); dataFile.close(); return; } dataFile.println(dataString); cmd.println("Writing " + full_datalog_name + " OK!"); dataFile.close(); dataFile = SD.open("DATALOG.txt", FILE_WRITE); if (!dataFile) { cmd.println("Error When opening file DATALOG.txt"); dataFile.close(); return; } dataFile.println(dataString); cmd.println("Writing DATALOG.txt OK!"); dataFile.close(); } SD.end(); add_log(34, 1, send_counter, 999); } Na początku sprawdzam, czy mogę odczytać / zapisać coś na karcie pamięci, jeżeli tak to wypisuje na terminalu w aplikacji Blynk informację o nazwie pliku do którego zostaną zapisane dane, ilość zapisywanych logów, licznik wszystkich zapisanych logów. Następnie sprawdzane jest czy plik o podanej nazwie istnieje, ta nazwa powstaje podczas uruchamiania się karty sieciowej na podstawie aktualnej daty (przykładowa nazwa L010321.txt). Jeżeli nie istnieje, to jest tworzony. Kolejnym krokiem jest odczytanie wszystkich komórek z buffera, zapisując je jednocześnie w postaci stringów z dodanymi przerywaczami w postaci litery i znaków na karcie SD. Na koniec na terminalu wypisuje status zapisu, zamykam plik, dodaje log z informacją o zapisie oraz liczbie zapisanych logów. Wysyłanie danych w wiadomości email: Podczas programowania tego urządzenia chciałem w jakiś sposób przesyłać dane z karty pamięci na zewnętrzny serwer, pocztę, dysk google lub jako dane w tabelkach w google spreedsheet online. Testowałem właśnie to ostatnie rozwiązanie, korzystałem z tego poradnika. Działało to dość słabo, bez problemu mogłem wysyłać dane do 7 komórek na raz (jeden alarm), ale wysyłanie trwało około 5 sekund. Wysłanie 75 alarmów zajęło by około 7 minut, niestety nie był to jedyny problem. Podczas hurtowego wysyłania alarmów, esp często się zawieszało lub niektóre alarmy były pomijane. Zrezygnowałem więc z tego pomysłu. Kolejnym pomysłem było wysyłanie logów jako stringi w wiadomości email, postanowiłem użyć widżetu email z aplikacji Blynk. Pozwala on na proste wysyłanie wiadomości na podane adres email (nadal korzystam z tego rozwiązania podczas wysyłania kodów z błędami). Jedną z wad tego rozwiązania było ograniczenie do maks. 1200 znaków na wiadomość, jeden alarm może składać się maksymalnie z 33 znaków (np. 34432->D28H13M44A45&12345#127$127), czyli teoretycznie w jednej wiadomości mogę wysłać 36 logów, w praktyce mniej ponieważ tytuł wiadomość też zabiera znaki. Pomyślałem, że mogę wysyłać właśnie tak pocięte wiadomości odczytywane z karty SD, jednak jest to trochę karkołomne rozwiązanie. W jeden dzień jestem w stanie wyprodukować nawet 600 logów, wysyłanie ich wszystkich na pocztę jest trochę mało wygodne. Szukałem rozwiązania pozwalającego na wysłanie pliku txt na pocztę lub serwer, i tak tworze taki plik na karcie SD, więc dlaczego go nie wykorzystać. Znalazłem projekt EMailSender, jest moim zdaniem świetny. Pozwala on na przesyłanie różnych plików, np. .txt, .jpg na pocztę gmail. Działa on na esp8266, esp32, czy też arduino z podłączoną kartą sieciową. Pliki na poczcie otrzymuję jako załącznik. Jedyną wadą jest długi czas wysyłani się plików. Podczas wysyłania plików na pocztę, karta sieciowa resetuje się i wchodzi w tryb wysyłania wiadomości. Podczas tego trybu aplikacja Blynk ani inne funkcje karty sieciowej nie działają, działa jednak wysyłanie komend do sterownika głównego. Zmodyfikowałem trochę bibliotekę EMailSender, dzięki temu mogę obliczyć ile procent wiadomości zostało wysłane, te informację wysyłam do sterownika głównego, a on pokazuje je na wyświetlaczu. Podczas wysyłania wiadomości na serial monitorze mogą pojawić się dane, np. o błędzie podczas wysyłania, błędzie podczas logowania się lub informacja o pomyślnym wysłaniu wiadomości, wszystkie te dane zapisuję w bufferze, a po połączeniu się esp z aplikacją Blynk, wyświetlam je na terminalu. void send_mail(bool send_own_file) { String datafile_name = "DATALOG.txt"; send_mail_serial("Starting!", 1); send_mail_serial("Initializing SD card...", 1); if (!SD.begin(D8)) { send_mail_serial("initialization failed!", 1); add_log(32, 1, 999, 999); return; } if (send_own_file == 1) { datafile_name = read_file_name(); } send_mail_serial("initialization done.", 1); File root = SD.open("/"); delay(100); if (WiFi.status() != WL_CONNECTED) { static uint16_t attempt = 0; send_mail_serial("Connecting to ", 0); WiFi.begin(E_ssid.c_str(), E_pass.c_str()); send_mail_serial(String(E_ssid.c_str()), 1); uint8_t i = 0; while (WiFi.status() != WL_CONNECTED && i++ < 50) { delay(200); send_mail_serial(".", 0); } ++attempt; send_mail_serial("", 1); if (i == 51) { send_mail_serial("Connection: TIMEOUT on attempt: ", 0); send_mail_serial(String(attempt), 1); if (attempt % 2 == 0) send_mail_serial("Check if access point available or SSID and Password", 1); return; } send_mail_serial("Connection: ESTABLISHED", 1); //send_mail_serial("Got IP address: ", 0); //send_mail_serial(String(WiFi.localIP()), 1); } byte datalog_d, datalog_m, datalog_y; datalog_d = EEPROM.read(523); datalog_m = EEPROM.read(524); datalog_y = EEPROM.read(525); String dd_str = ""; if (datalog_d < 10) { dd_str = "0"; } dd_str += String(datalog_d); String dm_str = ""; if (datalog_m < 10) { dm_str = "0"; } dm_str += String(datalog_m); String dy_str = ""; if (datalog_y < 10) { dy_str = "0"; } dy_str += String(datalog_y); current_log_filename = "L" + dd_str + dm_str + dy_str + ".txt"; EMailSender::EMailMessage message; message.subject = "Logi z sterownika"; message.message = "Pobierz plik z logami :)<br>"; message.mime = MIME_TEXT_PLAIN; byte message_size = 0; if (send_own_file == 0) { message_size = 2; } else { message_size = 1; } EMailSender::FileDescriptior fileDescriptor[message_size]; fileDescriptor[0].filename = (datafile_name); fileDescriptor[0].url = ("/" + datafile_name); fileDescriptor[0].storageType = EMailSender::EMAIL_STORAGE_TYPE_SD; if (message_size > 1) { fileDescriptor[1].filename = (current_log_filename); fileDescriptor[1].url = ("/" + current_log_filename); fileDescriptor[1].storageType = EMailSender::EMAIL_STORAGE_TYPE_SD; } EMailSender::Attachments attachs = {message_size, fileDescriptor}; EMailSender::Response resp = emailSend.send("[email protected]", message, attachs); send_mail_serial("Sending status: ", 1); send_mail_serial(String(resp.status), 1); send_mail_serial(String(resp.code), 1); send_mail_serial(String(resp.desc), 1); SD.end(); EEPROM.write(522, 3); EEPROM.commit(); add_log(33, 1, 999, 999); } kod karty sieciowej (Zalecam użyć dołączonych prze zemnie bibliotek): kod_karty_sieciowej.zip Automatyczny karmnik: Do budowy automatycznego karmnika wykorzystałem projekt ze strony thingiverse. Dodałem tylko własną obudowę na silnik oraz zrezygnowałem z górnego mieszalnika. W środku obudowy znajduje się: mostek h (LM293D), odpowiada on za sterowanie silniczkiem (4,5V) z przekładnią z zabawki 3 diody led, pierwsza odpowiada za informowanie o zasilaniu karmnika (odłączyłem ją ponieważ niepotrzebnie świeciła cały dzień), 2 i 3 dioda świeci odpowiednio podczas procesu podawania pokarmu lub powrotu do pojemnika Do działania karmnika potrzebny jest także stabilizator napięcia 12v na 5v, znajduję się on jednak poza obudową karmnika. Postanowiłem umieścić go w małej prostokątnej obudowie wraz z bezpiecznikiem i gniazdem LAN. Tak karmnik jest połączony ze sterownikiem głównym poprzez moduł z gniazdem LAN na skrętce komputerowej Wykorzystuje tam 6 żył na zasilanie oraz po jednej żyle na sterowanie mostkiem h. Aktualnie moduł karmnika oraz moduł z gniazdem LAN dla jego zasilania, odłączone są od sterownika głównego (mam zamiar wykorzystać je w swoim sterowniku). Obudowa: Obudowa sterownika głównego to puszka natynkowa hermetyczna z wyciętym otworem wentylacyjnym na wieczku oraz przyklejonej w tym miejscu kratki wydrukowanej na drukarce 3D, otworem na bezpiecznik z prawej strony, otworem do podwójnego gniazdka 230v, otworem i ramką dla wyświetlacza oraz otworami dla przycisków w wieczku obudowy. Obudowa dla karty sieciowe została w pełni wydrukowana na drukarce 3D, jest ona dość spora, ale i tak ledwo zmieściłem się ze wszystkimi potrzebnymi elementami. W pierwotnej wersji z sterownika głównego wystawał przymocowany na krótkim przewodzie prostokątny moduł z elektroniką sterującą automatycznym karmnikiem, znajdował się w niej bezpiecznik, stabilizator napięcia oraz złącze do podłączenia karmnika na długim przewodzie. Usunąłem go jednak, ponieważ dziadek nie korzystał z karmnika Nie jestem zadowolony z finalnego efektu, następnym razem wykorzystam dużo większą obudowę w której zmieszczę całą elektronikę sterownika głównego oraz karty sieciowej. Podsumowanie Jestem zadowolony z działania tego urządzenia, podczas testowania go w swoim akwarium z rybami nie sprawiał mi problemów. Co nie wyszło? moim zdaniem większość kodu mogła by być napisana lepiej, aktualnie widzę kilka błędów np. możliwość wykonania się tylko jednego alarmu na minutę, brak odpowiednich zabezpieczeń podczas wpisywania komend w terminalu, lekki bałagan w kodzie Moduł karmnika powinien być podłączony do sterownika a nie do karty sieciowej, takie podłączenie czasami uniemożliwia karmienie podczas braku dostępu do internetu lub podczas startu karty sieciowej Wysyłanie kilku dniowych plików z logami trwa nawet kilka godzin, nie jest to jednak do końca wada, ponieważ mogę wysyłać taki plik w nocy Automatyczny karmnik nie jest wykorzystywany przez dziadka, trochę mu nie ufa wygląd sterownika jest moim zdaniem ok, ale wygląd karty sieciowej jest dość śmieszny, tym bardziej doklejonej na gorący klej anteny od routera podczas testów nigdy nie miałem problemów z działaniem karty sieciowej nawet gdy była oddalona od routera w lini prostej na 15 metrów przez 3 ściany i podłogę, jednak podczas próby uruchomienia całego urządzenia u dziadka okazało się, że nowy router z światłowodem oddalony o 6 metrów w linii prostej przez 3 ściany nie jest w stanie połączyć się z kartą sieciową, problemem mogły być także inne sieci WiFi ponieważ mieszkanie znajduje się w bloku. Dlatego na szybko musiałem dodać zewnętrzną antenę ze starego routera TP-linka, po tej modyfikacji wszystko działa bez problemu Co wyszło? Urządzenie jest stabilne, nie zdarzają się mu już samo resety lub zawieszanie się Sterownik główny jest autonomiczny, działa nawet kiedy karta sieciowa jest wyłączona Alarmy działają bez problemu Pasek LED podłączony do sterownika działa bardzo dobrze, powoli rozjaśniające i ściemniające się światło nie straszy ryb Logi tworzone przez sterownik pozwalają na sprawdzenie co działa nie tak jak powinno, jest to moim zdaniem fajne narzędzie które będę używać w przyszłych projektach Najważniejsza rzecz, mój dziadek jest zadowolony z działania tego urządzenia, nie musi już codziennie kilka razy sięgać do listwy i wkładać/wyciągać wtyczkę od pompki, napowietrzacza oraz zasilacza do oświetlenia LED Plany na przyszłość Podczas budowy i programowania tego urządzenia nauczyłem się sporo rzeczy, aktualnie pracuję nad czymś w stylu uniwersalnego sterownika do oświetlenia zewnętrznego / systemu nawadniania / sterownika do mojego akwarium. Ma on mieć możliwość dodawania alarmów poprzez wyświetlacz lcd 20x4 i menu z 4 przyciskami, sterowania wyjściami poprzez telefon, odczytywanie danych z czujników itp. Chcę wykorzystać i ulepszyć kilka pomysłów z tego sterownik w moim nowym urządzeniu. Dziękuje za przeczytanie tego artykułu, mam nadzieję że ktoś z mojego rozwiązania coś wykorzysta. Powodzenia we wszystkich waszych przyszłych projektach
  4. Przyszedł nowy rok nowe możliwości i ..... zmiana kalendarza .... Niestety moja żona zawaliła tu troszkę sprawę i nie zaopatrzyła mnie w nowy kalendarz ( zawsze to robiła ) . Cóż przy jej nawale pracy nie dziwię się że mogła zapomnieć .... Po tym przydługim wstępie przejdę do sedna. Z racji potrzeby chwili postanowiłem zbudować sobie owy kalendarz sam, oczywiście w założeniu że będzie : - wieczny -uwzględnia lata przestępne - zegar automatycznie przestawia się z czasu zimowego na letni i odwrotnie - będzie posiadał funkcję alarmu - będzie sygnalizował pełne godziny sygnałem dźwiękowym - pełna obsługa z poziomu przeglądarki np. w telefonie lub komputerze W związku z powyższymi założeniami wykopałem jakiś zapomniany moduł ESP8266-12F, wyświetlacz graficzny LCD ze sterownikiem ST7735, oraz kawałek uniwersalnej płytki ( na prawdę nie potrzeba tu jakichś cudów ), no i dla uproszczenia konstrukcji i softu buzzer z generatorem. Zegar pobiera czas z internetowego serwera czasu, więc z założenia jest dokładny oprogramowanie obrabia wszystkie tematy związane ze strefa czasową, czasem zimowym / letnim. ustawianie godziny alarmu ( budzenia ) odbywa się całkowicie przez interfejs przeglądarkowy ( http), to samo tyczy się włączenia lub wyłączenia sygnalizacji pełnej godziny, tak że w oprogramowaniu zaszyty jest prosty serwer http w celu obsługi tychże funkcji. Po prostu w pasek adresowy przeglądarki wpisujemy adres IP naszego zegara ( wyświetla się on na wyświetlaczu przy starcie, gdy poprawnie zostanie zainicjowana komunikacja przez WiFi ). Kod źródłowy ( INO ) spakowany jest linuksowym kompresorem ( tar.gz ) z rozpakowaniem nie będzie najmniejszego problemu. Pozdrawiam !!! Kalendarz_zegar_esp8266.tar.gz
  5. Interaktywna zabawka dla kotów gwarantująca zabawę w każdej chwili, żaden kot nie oprze się uciekającej czerwonej kropce. Jest to niewielkie pudełeczko z wbudowanym modułem wifi i banalnie prostą obsługą. Główne funkcje: sterowanie dowolnym urządzeniem z przeglądarką internetową. losowe ruchy lasera o zmiennej prędkości. ustawianie czasu jak długo ma działać. ustawianie harmonogramów automatycznego włączenia. regulacja jasności lasera. regulacja zakresu ruchu i prędkości lasera. możliwość sterowania z dowolnego miejsca na świecie przez internet. sterowanie za pomocą google asystenta. prosta konfiguracja. Zabawka może być zasilana dowolną ładowarką od telefonu, może to być również powerbank. Przy pierwszym uruchomieniu zabawki, zostanie uruchomiona nowa sieć wifi ..::LASERCAT::.. wystarczy połączyć się z nią i wskazać naszą sieć domową, a po zrestartowaniu urządzenie automatycznie podłączy się do niej i już możemy korzystać z zabawki. Z tyłu znajduje się wejście zasilania micro USB, jak w telefonie oraz przycisk. Krótkie wciśnięcie to włączenie/wyłączenie lasera, przytrzymanie przez 3 sek. powoduje rozłączenie obecnej sieci wifi i uruchomienie ponownej konfiguracji. Gdy urządzenie jest już podłączone do naszej sieci wifi to po wpisaniu adresu zabawki w przeglądarce internetowej zobaczymy panel sterujący: Zastosowany laser jest małej mocy, taki sam jak w innych tego typu zabawkach czy bazarkowych wskaźnikach. Dodatkowo dla bezpieczeństwa jest możliwość ustawienia mocy świecenia lasera od 0% do 100%. Pozostałe ustawienia pozwolą dostosować zakres ruchów do miejsca w którym znajduje się zabawka i określić czy kropka ma się poruszać tylko po podłodze, czy częściowo wchodzić na ścianę co może dostarczyć dodatkowej frajdy dla kota. Schemat jest bardzo prosty: Widok płytki PCB: Jak zwykle w garażowym zaciszu metodą "żelazkową" - elektronicy używają żelazka zdecydowanie częściej jak ich partnerki - powstaje mała płytka. Płytka została zabezpieczona przed utlenianiem lakierem PVB16. Całą robotę wykonuje tutaj tani i lubiany układ ESP8266, który posiada moduł WiFi. Dioda laserowa jest zasilana źródłem prądowym dodatkowo kluczowanym z PWM-a co pozwala płynnie regulować jasność od 0% do 100%. Skoro już bebechy mam, to teraz trzeba to wszystko złożyć w całość. Obudowę wykonałem ze sklejki wyciętej laserowo, składanej na wczepy palcowe. No to składamy: Dodanie serwomechanizmów do których przyczepiony jest laser. Oczywiście bez trytytki projekt by się nie udał No i sprzęt jest gotowy, ale co nam po sprzęcie jak on zupełnie nie wie co ma robić? Nie wie, że teraz trzeba machać tym laserkiem tak żeby kot ganiał w tę i we w tę Trzeba to wszystko zaprogramować. Uruchamiamy nasze ulubione IDE czyli Visual Studio Code z wtyczką PlatformIO i zaczynamy pisać program. Soft został napisany z wykorzystaniem Arduino Core, a na całość składa się kilka części: główny program sterujący silniczkami, wyznaczanie losowych ścieżek. serwer www, który udostępnia ładny panel sterowania. konfiguracja sieci WiFi z wykorzystaniem Captive Portal. multicast DNS. stworzenie strony www (html + css + javascript). obsługa komunikacji po websockecie. zdalne wgrywanie plików przez stronę www, np. zmiana wyglądu głównej strony. zdalna aktualizacja oprogramowania bez zbędnych kabli. W oczekiwaniu na gotowe oprogramowanie tester cierpliwie czeka. Panel sterujący dostępny z poziomu przeglądarki internetowej nie jest hostowany nigdzie na zewnątrz, całość znajduje się w zabawce, wykorzystałem bootstrapa plus kilka dodatkowych komponentów. Zastosowany mDNS pozwala połączyć się z urządzeniem wpisując w przeglądarce adres "lasercat.local" zamiast adresu IP. Niestety na chwilę obecną android nie wspiera tego typu rozwiązań, ale na iPhonach działa to bardzo dobrze. Na filmie mała prezentacja z trochę już wybawionym głównym testerem Elroyem a poniżej pokazano jak można włączyć zabawkę po prostu mówiąc do telefonu "Ok Google, włącz laser"
  6. Dzień dobry, mam : ESP8266 + NodeMCU v3 modul-wifi-esp8266-nodemcu-v3 umiem podłączyć ( i odczytywać wartości ) z kilku czujników temperatury ... sukces ale nie umiem poradzić sobie z tym aby z innych płytek shelly pm1 1 generacja ( oczywiście z adapterem i zew czujnikiem temp ) shelly-1-pm pobierać wartości aktualne temperatur i odczytywać na tej w/w płytce "ESP8266 + NodeMCU v3" Co chcę uzyskać , po co tak chcę zrobić ? Shelly działają poprawnie , wysyłają poprawnie do chmury shelly , mogę się logowac tam lokalnie i ale w taki sposób widzę tylko aktualne temperatury i temperaturę min i max w konkretnej godzinie, nie widzę jak zmienia się temperatura w ciągu godziny. Poza lokalnym panelem w shelly mozna też wywołać status ( http://ip/status ) , są tam te wartości aktualne ale i pozostałe inne dane urzadzenia. ......................................{ "0":{"hwID":"tu-chyba-id-czujnika0","tC":39.50,"tF":103.10}, "1":{"hwID":"tu-chyba-id-czujnika1","tC":41.44,"tF":106.59} }...................................... Nie wiem jak ruszyć dalej. Czy ktoś jest w stanie podpowiedzieć ?
  7. Od dawna interesowały mnie pomiary warunków meteorologicznych w mojej miejscowości, pierwsza stacja meteorologiczna, którą zbudowałem około roku 2010, wykonana była na mikrokontrolerze Atmega32. Do komunikacji z światem wykorzystywała moduł LAN Wiznet 7010a. Stacja ta była oprogramowana w języku BASCOM. Projekt który chcę zaprezentować dzisiaj działa już od roku 2018 i został oprogramowany w środowisku Arduino. Stacja została podzielona na 2 moduły, pierwszy pomiarowy oparty jest na klonie Arduino Nano oraz drugi odbiorczy którego sercem jest ESP8266 NodeMCU v3, służy on również do wyświetlania aktualnych pomiarów na wyświetlaczu LED dot matrix o wymiarach 8x56 punktów. Na pracach stolarskich się nie będziemy skupiać napiszę tylko że klatka meteorologiczna została wykonana z drewna sosnowego i umieszczona na wysokości 2 m. Moduł Pomiarowy Czujniki jakie zastosowałem to dwie sztuki DS18B20 pierwszy zajmuje się pomiarem temperatury przy gruncie na wysokości 5cm, drugi pełni rolę zapasowego czujnika temperatury na wypadek uszkodzenia się głównego czujnika BME280. Do pomiaru prędkości wiatru wykorzystuję wiatromierz firmy Maplin na jeden obrót wiatromierza przypadają 2 impulsy z kontaktronu który jest w nim zamontowany, producent dostarcza również odpowiedni wzór według którego można obliczyć rpm oraz prędkość wiatru w km/h. Dane mierzone przez wiatromierz możemy podzielić na dwie wartości, pierwsza to chwilowa prędkość, druga prędkość w porywach, aby uśrednić wartości mierzone program zlicza impulsy z 5s a następnie dokonuje odpowiednich obliczeń. Zebrane dane przesyłane są do drugiego urządzenia poprzez moduły radiowe które działają na częstotliwości 433,92 MHz. W tym celu zastosowana została biblioteka RCSwitch. Każda mierzona wartość jest wysyłana jako osobna transmisja. aby rozróżnić pomiary z konkretnych czujników mierzona wartość mnożona jest przez 100 a następnie dodawana jest liczba 100 000 dla pierwszego czujnika, 200 000 dla drugiego itd. Przykład kodu który realizuje tę funkcję poniżej: // temperatura sensor BME codetosend = temp * 100 + (1 * 100000); mySwitch.send(codetosend, 24); // wilgotnosc sensor BME codetosend = hum * 100 + (2 * 100000); mySwitch.send(codetosend, 24); Moduł Wewnętrzny Obudowa, która idealnie nadawała się do implementacji wewnętrznego modułu pochodzi z tunera IPTV Motorola VIP1910-9. Przedni panel został wykonany z ciemnego półprzepuszczalnego plastiku który idealnie nadaje się do umieszczenia w nim wyświetlacza. Sercem urządzenia jest układ ESP8266. "Moduł wewnętrzny" został również wyposażony w czujnik temperatury oraz wilgotności DHT22, dodatkowo w celu prezentacji zmierzonych wartości dołączone zostało 7 szt. modułów wyświetlacza LED dot matrix z układem MAX7219. Do obsługi tej matrycy zastosowałem bibliotekę Max72xxPanel.h która współpracuje z biblioteką Adafruit_GFX.h w ten sposób nie byłem zmuszony implementować do rozwiązania własnych czcionek. Matryca ta oprócz modułowej konstrukcji umożliwia również sterowaniem jasnością podświetlania, w tym celu aby uprzyjemnić użytkowanie w porach nocnych odbiornik został wyposażony w fotorezystor dzięki któremu potrafi określić natężenie oświetlenia otoczenia i odpowiednie ustawienie podświetlenia. Na wyświetlaczu w pierwszej kolejności wyświetlam aktualną godzinę oraz temperaturę wewnątrz pomieszczenia oraz wilgotność, po około jednej minucie wyświetlane są informacje odczytane z stacji meteo czyli temperatura wilgotność i ciśnienie, postanowiłem nie wyświetlać tutaj informacji dotyczących prędkości wiatru oraz temperatury przy gruncie. Decyzję tą podjąłem na podstawie użytkowania innego podobnego rozwiązania, akurat jak chcemy odczytać godzinę to wyświetlane są inne informacje. Dodatkowo w godzinach nocnych, które zostały ustawione w sztywnych ramach czasowych między 21:00 a 7:00 informacje odczytane z stacji meteo zostały okrojone tylko do temperatury. W projekcie zostały zastosowane 2 rodzaje animacji pierwsza z nich, przesuwa tekst z prawej strony wyświetlacza na lewą, z możliwością zatrzymania w interesujących momentach. Drugi rodzaj to pionowa animacja. Mikrokontroler również poprzez protokół NTP i bibliotekę time.h pobiera aktualną godzinę i datę. Za odbiór danych z pierwszego układu odpowiedzialny jest moduł radiowy którego obsługą tak jak w poprzednim module zajmuje się biblioteka RCswitch. Poniżej fragment programu który demonstruje w jaki sposób odbierane i dekodowane są dane: rc = mySwitch.getReceivedValue(); // czujnik temperatury powietrza BME280 if (abs(rc)>=50000&& abs(rc)<150000) { rc=(rc-100000)/100; if (rc > -50 and rc < 60) { temp1 = rc; Serial.print("Czujnik BME280 - temperatura: \t"); Serial.println(rc); matrix.drawPixel(55,0,1); matrix.write(); } } // czujnik wilgotności BME280 if (abs(rc)>=150000 && abs(rc)<250000) { rc=(rc-200000)/100; if (rc > 5 and rc <= 100) { hum = rc; Serial.print("Czujnik BME280 - wilgotnowsc: \t"); Serial.println(rc); matrix.drawPixel(55,1,1); matrix.write(); } } Dzięki zastosowaniu zewnętrznej anteny oraz odbiornika opartego na superheterodynie, zasięg w otwartym terenie to około 250 m. Po odebraniu danych z pierwszego układu poprzez moduł radiowy następuje przekazanie ich do serwera z systemem Domoticz. Domoticz to bardzo lekki system automatyki domowej, który pozwala monitorować i konfigurować różne urządzenia, przełączniki, czujniki takie jak temperatura, opady deszczu, wiatr, promieniowanie ultrafioletowe (UV), zużycie energii elektrycznej, zużycie gazu, zużycie wody i wiele więcej. Wykresy dostępne są również na stronie www http://meteo.palowice.net Poniżej film z działania odbiornika, smużenie animacji które występuje na filmiku ludzie oko nie rejestruje. Gdyby kogoś interesował kod to również zamieszczam: meteo.zip
  8. Witam chcę aby moja płytka esp8266 wyświetliła mi dares mac. Lecz po załadowaniu programu ten wykonuje się w pętli i wyświetla tylko końcówkę adresu mac. Arduino ide 2.2.1 #include <ESP8266WiFi.h> void setup(){ Serial.begin(115200); Serial.println(); Serial.print("ESP8266 Board MAC Address: "); Serial.println(WiFi.macAddress()); } void loop(){ } A podczas załadowywania i kompilacji kodu konsola wyświetla następujące informacje: "/Users/wojteknowak/Library/Arduino15/packages/esp8266/tools/python3/3.7.2-post1/python3" -I "/Users/wojteknowak/Library/Arduino15/packages/esp8266/hardware/esp8266/3.1.2/tools/upload.py" --chip esp8266 --port "/dev/cu.usbserial-120" --baud "115200" "" --before default_reset --after hard_reset write_flash 0x0 "/private/var/folders/l7/t8lbg_612y1_t6_rzt9m_f6h0000gp/T/arduino/sketches/6EC888AB277E4E894591D3001568582D/WiFiScan.ino.bin" esptool.py v3.0 Serial port /dev/cu.usbserial-120 Connecting.... Chip is ESP8266EX Features: WiFi Crystal is 26MHz MAC: 48:3f:da:40:59:d4 Uploading stub... Running stub... Stub running... Configuring flash size... Auto-detected Flash size: 4MB Compressed 270848 bytes to 199167... Writing at 0x00000000... (7 %) Writing at 0x00004000... (15 %) Writing at 0x00008000... (23 %) Writing at 0x0000c000... (30 %) Writing at 0x00010000... (38 %) Writing at 0x00014000... (46 %) Writing at 0x00018000... (53 %) Writing at 0x0001c000... (61 %) Writing at 0x00020000... (69 %) Writing at 0x00024000... (76 %) Writing at 0x00028000... (84 %) Writing at 0x0002c000... (92 %) Writing at 0x00030000... (100 %) Wrote 270848 bytes (199167 compressed) at 0x00000000 in 19.3 seconds (effective 112.2 kbit/s)... Hash of data verified. Leaving... Hard resetting via RTS pin...
  9. Cześć, urządzenie do monitorowania pogody to chyba najbardziej popularny i pewnie oklepany temat ale chciałem spróbować zrobić je samemu zwłaszcza, że można to zrobić na wiele sposobów. Jest to pierwsza wersja rozwojowa i już widzę, że będę musiał wprowadzić jedną poprawkę ale o tym w podsumowaniu. Projekt zakłada monitorowanie: temperatury wilgotności jasności (aby w przyszłości można było skorelować ją z temperaturą) poziomu naładowania akumulatora Całość chciałem oprzeć na jednym mikrokontrolerze zapewniającym odczyt danych z czujników wraz z dostępem do sieci, który dodatkowo mógłby być zasilany z akumulatora. Nie chciałem też projektować dedykowanego PCB ani używać gotowego układu w postaci Arduino Uno, bo to dość duża płytka no i nie jest tania. Mój wybór padł na układ ESP8266 (w wersji NodeMCU v3), ponieważ spełnia praktycznie wszystkie wymagania i ma większe możliwości niż gołe Arduino. Poza tym chciałem poznać i sprawdzić skryptowy język Lua dla NodeMCU, który podobno jest przeznaczony do IoT (Internet of Things). Jako czujnika temperatury i wilgotności użyłem AM2320, który ma dość duży zakres temperatur (-40°C do 80°C) oraz łatwo da się programować w Lua. Jeszcze się nie zdecydowałem w jakiej obudowie umieszczę ESP ale całość z czujnikiem wygląda tak: Zastosowane elemety: ESP8266 (w wersji NodeMCU v3) AM2320 Akumulator Li-Pol Akyga 980mAh 3,7V Ładowarka Li-Pol TP4056 pojedyncza cela 1S 3,7V microUSB z zabezpieczeniami Fotorezystor 10-20kΩ 2x BC547; 3x 10kΩ; 2x 100kΩ; 1x 220kΩ Niestety układ ESP posiada tylko jeden pin dla przetwornika ADC o oznaczeniu A0, dlatego szukałem w internecie jak można go współdzielić do pomiaru jasności fotorezystora i poziomu akumulatora. Jednak jedyne co znalazłem to sposoby na odczyt z różnych czujników (fotorezystor, termistor, itp) za pomocą wystawiania logicznej jedynki na porcie D (wystawione "1" daje zasilanie na czujnik, a "0" nie) podpiętej do danego czujnika. Musiałem więc wymyślić własne rozwiązanie, które nie jest idealne ale działa: Jedynka wystawiona na pin D5 pozwala na odczyt wartości napięcia z fotorezystora, a zero podaje napięcie z akumulatora. Zworka JP1 pozwala na odłączenie akumulatora od zasilania ESP co jest przydatne podczas programowania, wówczas ESP zasilany jest z portu USB. Czujniki umieściłem w obudowie i przykleiłem do ramy okiennej: Program umieszczony w ESP odczytuje wartości z czujników co minutę i udostępnia w postaci strony internetowej Podsumowanie: Ze względu na to, że na płytce NodeMCU użyty jest stabilizator 3,3V pozwalający podawać mu napięcie niewiele większe niż na wyjściu to założyłem, że zasilanie bezpośrednio z akumulatora wystarczy tak długo aż napięcie akumulatora zbliży się do 3,3V. Nie spowoduje to całkowitego rozładowania, więc jest bezpieczne a układ się sam wyłączy. Mimo, że układ sprawuje się dobrze to jednak zauważyłem, że czasami działa niestabilnie i po bliższym przyjrzeniu się schematowi płytki NodeMCU zauważyłem podłączoną diodę pomiędzy VIN a zasilaniem ESP, która powoduje spadek napięcia o 0,7V. Prawdopodobnie ma ona zabezpieczać przed zamianą biegunów zasilania ale dodatkowo wprowadza spadek napięcia, który w przypadku zasilania z portu USB nie ma znaczenia. W kolejnej wersji dodam przetwornicę 5V i zobaczę jak wtedy będzie działać. Jeżeli ktoś z kolegów lub koleżanek znalazł sposób jak lepiej rozwiązać pomiar napięcia z czujnika i akumulatora przy pomocy pinu A0 bez użycia dodatkowego przetwornika ADC, to chętnie poczytam. Docelowo chciałbym przechowywać pomiary na malince i mieć podgląd danych historycznych ale jeszcze nie znalazłem odpowiedniego oprogramowania, bo albo jest zbyt złożone w konfiguracji i ciężkie dla maliny albo zbyt proste. Oprogramowanie ESPHome użyte w projekcie "Zabezpieczenie akumulatora 3,7V Li-Pol - hardware & software" wygląda ciekawie, więc może się nada, zobaczymy. sp_code.zip
  10. W zeszłym tygodniu zabrałem się za kolejny ciekawy projekt, miernik poziomu jakości powietrza z pomiarem podstawowych parametrów pogodowych. Nie będę się rozpisywał na temat projektu gdyż jest on doskonale opisany przez autora na stronie: https://github.com/hackerspace-silesia/Smogomierz, ja osobiście korzystałem z tej instrukcji. Części: WiFi ESP8266 NodeMCU v3 czujnik cząstek stałych PM 1, 2.5 i 10 - PMS7003 adapter do czujnika PMS czujnik temperatury, wilgotności i ciśnienia BME280 Złożenie całości zajmuje nie więcej niż 15 minut. W kolejnym kroku ładujemy kod (zgodnie z instrukcją) ja skorzystałem z gotowej paczki Smogly. System po skonfigurowaniu przez panel WWW jest gotowy do współpracy z popularnymi serwisami monitorującymi typu AirMonitor, aui.eco, Luftdaten.info czy thingspeak (z którego w moim przypadku korzystam jako pośrednika danych w celu ich wysyłki do 'Ramki cyfrowej - centrum informacji' - opisywanej w innym wątku. Po zamontowaniu całości w obudowie, zapewnieniu przepływu powietrza, całość działa bez zarzutu (aktualnie w trakcie testowania). W celu codziennego monitorowania parametrów najbardziej sprawdzi się wg mnie serwis aqi.eco, z którym smogly działa bez problemów. Serwis aqi.eco automatycznie generuje dedykowaną stronę dla każdego czujnika na której można sprawdzić bieżące odczyty oraz historię długoterminową na wykresach. W serwisie jest też dostępna mapa czujników w Polsce i Europie. Po testach podzielę się z Państwem wrażeniami i opinią.
  11. Dzisiaj znów coś software-only: biblioteczka do prostej komunikacji UDP, taka po prostu konsola udp. Działa na obu ESP. Na pececie do komunikacji można użyć netcata (Linux, cygwin, pewnie Mac też), załączonego programu ardumon.py (Linux), i prawdopodobnie Ncat (Windows, nie mam jak sprawdzić). Biblioteczka jeszcze nie skończona, ale obiecałem koledze @SOYER że wrzucę Można zacząć testować ESPUdpConsole.zip - feedback mile widziany.
  12. Witam, Próbuję od jakiegoś tygodnie zflashować ESP8266ex które jest częścią płytki Arduino 2560+ESP 8266, wszystko łączy się z PC za pomocą UART >>> USB kontrolowany zworkami na płytce. Problem polega na tym że, nie mogę wgrać niczego innego na tę ESP niż ESP-easy. A zależy mi, żeby ESP na tej płytce pełniło funkcję serwera www podłączonego do sieci do którego za pomocą UART 3 (Połączenie Atmega2560 >>> ESP8266) będę przesyłał informacje z czujników. Ma ktoś może sprawdzony firmware lub instrukcję do tego jak to skonfigurować? Próbowałem wgrywać aktualny firmware AT od Esperriffa, ale nadal Monitor szeregowy wyświetla mi tylko krzaki i nie odpowiada na żadne komendy AT.
  13. Cześć na esp8266 wrzuciłem ESP8266_Smogomierz_2.8.1_PMS_build_27.11.2021.bin, lecz po flashu płytka nie wstaje, brakuje wifi Niestety nie pojawia się oczekiwana sieć wifi ;/ Podpowie ktoś co może być nie tak? Flashuję tak: esptool.py -p /dev/ttyUSB0 --before default_reset --after hard_reset --chip esp8266 write_flash --flash_size=detect 0x1000 ESP8266_Smogomierz_2.8.1_PMS_build_27.11.2021.bin esptool.py v4.5.1 Serial port /dev/ttyUSB0 Connecting.... Chip is ESP8266EX Features: WiFi Crystal is 26MHz MAC: 30:83:98:85:a4:4e Uploading stub... Running stub... Stub running... Configuring flash size... Auto-detected Flash size: 4MB Flash will be erased from 0x00001000 to 0x000aafff... Compressed 695568 bytes to 461673... Wrote 695568 bytes (461673 compressed) at 0x00001000 in 40.7 seconds (effective 136.7 kbit/s)... Hash of data verified. Leaving... Hard resetting via RTS pin...
  14. Swego czasu postanowiłem - dla oszczędności kasy i miejsca w obudowie - przerzucić się na proste moduły (typu ESP32-WROOM lub ESP12E). Na pierwszej doświadczalnej płytce miałem wszystkie elementy potrzebne do podłączenia konwertera USB-UART, ale w docelowych urządzeniach (np. czytak) absolutnie nie były one potrzebne. Postanowiłem stworzyć sobie taki własny pseudo-standard gniazda programowania i zrobić przejściówkę do popularnego konwertera HW-417. Nie będę się za dużo rozpisywał, napiszę tylko kilka rzeczy: Schemat całości to po prostu fragment ESP32 Dev Kit. Układ sprawdzony, działa zarówno z ESP32 (WROOM, WROVER) jak i ESP8266 (ESP-12E). Nie próbowałem robić żadnej specjalnej płytki, użyłem kawałka płytki uniwersalnej i części wyciągniętych z szuflady. Gniazdo programowania ma dodatkowy pin 3.3V. W tej chwili nie jest nigdzie podłączony, ale w planach mam przeróbkę płytki tak, aby można było podłączyć się albo do +5V z USB, albo do zewnętrznego zasilacza. Na zdjęciu widać, że wtyczka programatora jest ośmio- a nie sześciopinowa. Po prostu czytak ma dwa dodatkowe piny przeznaczone do podłączenia +5V do ładowania akumulatora (i bardzo się cieszę że to przewidziałem, bo z modułu ładowarki odpadło mi gniazdo USB). A ośmio[inowa żeńska wtyczka pasuje do sześciopinowego gniazda. Gniazdo do podłączenia ma 7 pinów, mimo iż sam konwerter ma sześć. Siódmy pin służy do podłączenia sygnału RTS. Niestety - wersja esptool-ftdi umożliwiająca wykorzystanie pinów DTR/CTS zamiast DTR/RTS nie jest rozwijana, a jakoś nie bardzo miałem czas żeby sprawdzić czy da się jakoś połączyć kod aktualnej wersji (CTS/RTS) z taką trochę zapomnianą gałęzią Zdjęcie kiedyś pokazywałem, ale dla przypomnienia jeszcze raz (na zdjęciu widać przewód łączący dolutowany do HW-417 pin RTS z przejściówka). Przejściówka sprawdza się zarówno przy programowaniu ESP32, jak i ESP8266. Ponieważ przewidziana jest do współpracy z esptool - powinna działać z Arduino IDE, PlatformIO jak i różnymi wynalazkami (jak mój "pyrduino"). Przyciski RESET i BOOT są tak na wszelki wypadek - esptool i tak steruje sygnałami RTS i DTR potrzebnymi do zresetowania płytki i/lub przejścia w stan uploadu.
  15. Potrzebowałem zrobić kilka urządzeń IOT na ESP - 01 ponieważ nie miałem pod ręką żadnego tak zwanego "dongla" usb, musiałem coś wykombinować aby praca szła nieco sprawniej. Ponieważ druciarstwo i prowizorki są dobre tylko w przysłowiach, powstało to mini urządzenie. W samym schemacie jak i budowie niema nic nowego ani odkrywczego, nawet się zastanawiałem czy pokazywać to "cudo" tutaj, ale że faktycznie ułatwia mi pracę to niech już będzie, a może ktoś skorzysta. Zatem schemat to typowe podłączenie esp z trybem automatycznego wgrywania firmware, za pomocą zworek J1 i J2 można przełączyć piny UARTa na złącze J3. Na złączu J3 wyprowadziłem wszystkie dostępne GPIO z ESP01. Ja często robię tak że tylko pierwszą wersję oprogramowania wgrywam z UART później przeważnie używam FOTA UDP bo szybciej działa. Co do samego konwertera USB - UART wybrałem ten firmy msx ponieważ działa niezawodnie i pozwala osiągnąć wysokie transfery podczas flashowania do 3Mb/s. Schematic_espProg_2023-01-12.pdf Na koniec kilka zdjęć poglądowych. Do wszystkiego wydrukowałem niewielką obudowę, zastanawiałem się czy nie dorobić zaczepów do płytki stykowej jednak ostatecznie zrezygnowałem z tego pomysłu bo to jest tak małe że nie przeszkadza w normalnym użytkowaniu. Oczywiście nie od razu wszystko musiało zadziałać więc musiałem zrobić krosa TX/RX A tutaj prowizorka w pełnej krasie Tu paczka plików jakby ktoś chciał sobie odtworzyć projekcik. Moduł nie zawiera elementów SMD - muszę gdzieś wyrobić stare zapasy - co myślę, jest pewnym ułatwieniem. Na PCB jest kilka zworek które są zaznaczone jako ścieżki na górnej warstwie. stl_f3d.zip espProg.zip
  16. Witam. Wykorzystałem Platformio do wgrania plików przez Spiffs do esp8266. Pytanie jak zmienić wielkość pamięci np. z 1MB na 2 MB ?
  17. Witam, w internecie zakupiłam zaskakująco tanio płytkę ESP8266 Wemos NodeMCU V3 z linka: https://shopee.pl/Moduł-NodeMCU-V3-Lua-WIFI-zintegrowany-ESP8266-dodatkowa-pamięć-32M-Flash-USB-CH340G-i.597261045.15003633781 . Zachęcona pozytywnymi opiniami zakupiłam płytki, wcześniej kupowałam z tego sklepu czujniki do Arduino i działały bez problemu. Niestety podłączając płytkę (a kupiłam ich 5) żadnej nie wykrywa komputer (ani Win8 ani Win11), przyciskając przycisk reset płytka się świeci, czujniki do niej podłączone świecą, więc zasilanie dociera. Kombinowałam z sterownikami, próbą instalacji flasha, zmianą prędkości portów na komputerach, różne porty USB, niestety nic nie pomaga. Komputery nie wykrywają urządzenia i dalej nie wiem jak to ruszyć. Jeśli złamałam jakiś regulamin przepraszam, jest to mój pierwszy post, szukałam podobnych tematów, ale nigdzie nie mogę znaleźć. Z góry dziękuję za odpowiedź i ciepło pozdrawiam
  18. Witam, mam problem nagłej potrzeby. Prostu układ: esp8266 nodemcu v3 podłączony pod zasilacz impulsowy 12V przekaźnik 8 kanałowy z optoizolacją zasilany na 5V Z Zasilania 12V idzie też prąd na stabilizator 5V - ten zasila tylko i wyłącznie wejście Vcc płytki przekaźnika. problem jest taki, że przekaźnik normalnie działa w przypadku gdy styk IN1 załącze z arduino leonardo. Wszystko jest idealnie z arduino. Natomiast gdy spróbuję to samo zrobić z esp zamiast arduino, to na przekaźniku zapala się tylko LED że 1 styk został załączony, jednak nie słychać nawet charakterystycznego "trzasku" przełączania styku a sam kanał nie działa i nic się nie dzieje oprócz led sygnalizującego załączenie styku. Problem palący czasowo, ktos wie czemu arduino normalnie załącza styk na przekaźniku a esp już nie? I jak to rozwiązać?
  19. Dzisiaj kolej na automatykę domową. Od razu na wstępie: nie cierpię określenia "inteligentny dom". Po pierwsze w większości jest ono nadużywane (co to za "inteligencja", która sprowadza się do możliwości zapalenia światła w łazience przez telefon), po drugie mój dom nie ma być inteligentny: ma być przede wszystkim wygodny. Nie wiem jak dla was - ale dla mnie ważniejsze jest zdublowanie wyłącznika oświetlenia przy moim ulubionym fotelu gdzie zwykłem popijać kawusię i oglądać telewizję, niż kombinowanie z pilotami (które zawsze leżą poza zasięgiem ręki) czy smartfonami (gdzie trzeba włączyć aplikację i wybrać odpowiednią funkcję przedzierając się przez jakieś menu). Podobny system już miałem, ale był on jakoś mało rozbudowywalny, poza tym sterowanie nie było zbyt wygodne. Postanowiłem więc zrobić nowy od zera. Cały system składa się z następujących komponentów: Centralka - czyli moduł główny, z klawiaturą, wyświetlaczem, głośnikiem i innymi bajerami, sterująca bezpośrednio kotłem; Wyłączniki oświetlenia - każdy steruje jednym lub dwoma punktami, wyposażone w różnej maści wyłączniki, czujki i takie tam; Kontroler termometrów - najprostszy ze wszystkich modułów, jego zadaniem jest wyłącznie odczyt temperatury z trzech DS18B20 i przesyłanie wyników do centralki. Najpierw może o centralce. Centralka składa się z dwóch głównych układów: Raspberry Pi Zero 2 W jako główny "mózg" urządzenia i ESP-12E do komunikacji ze światem zewnętrznym (poprzez esp-now z pozostałymi modułami oraz poprzez panel sterujący z użytkownikiem). Ponieważ mój kocioł pozwala jedynie na sterowanie typu "włącz/wyłącz", elektrycznie sprawa jest stosunkowo prosta. Do sterowania służą dwa przekaźniki. Pozostawiłem stary sterownik Euroster 2000, ale w czasie pracy jest on odłączony. Do sterowania służą dwa przekaźniki: jeden odpowiada za odłączenie Eurostera gdy centralka jest gotowa do pracy - tzn. RPi się ładnie zabootował, wie która jest godzina, odczytał dane z pamięci EEPROM i FRAM i ma informacje z przynajmniej jednego termometru. Drugi to właściwy przekaźnik sterujący piecem. Układ wygląda mniej więcej tak: Jak widać, aby centralka mogła sterować kotłem, na pinie READY musi pojawić się jedynka. Rezystor 10k zapewnia, że tranzystor będzie zatkany również w przypadku, gdy pin będzie w stanie wysokiej impedancji lub RPi w ogóle nie będzie zasilany. Diody sygnalizują stan przekaźników: READY (zielona) to gotowość do pracy, FAIL (czerwona) to sygnalizacja przełączenia na Euroster, FIRE (niebieska) zapala się przy włączeniu kotła. Oprócz tego do RPi podłączone są: Konwerter I2S ze wzmacniaczem i głośnikiem Moduł zegara DS3231 z pamięcią EEPROM 4 kB Moduł FRAM 32 kB ESP8266 wraz z układem RESET/BOOT Natomiast ESP oprócz komunikacji z pozostałymi modułami obsługuje: wyświetlacz klawiaturę czujnik odległości czujnik temperatury i ciśnienia fotorezystor (jeszcze nie wiem po co, ale miałem wolne wejście analogowe) ESP8266 jest podłączony tak, żeby umożliwić zarówno współpracę układu z malinką, jak i zdalne programowanie: Ponieważ do programowania ESP8266 używam frameworku Arduino, teoretycznie można zainstalować Arduino IDE na malince (tak też zrobiłem na początku). Uznałem jednak, że nie jesto to zbyt wygodne. Dlatego też kompiluję program na lokalnym pececie z linuksem na pokładzie, a do malinki przesyłam tylko gotowy plik bin. I tu mała uwaga: nie podaję pełnego rozwiązania choćby z tego względu, że w rzeczywistości skrypty są bardziej skomplikowane, ale jest to związane ze specyfiją jinjretnego urządzenia i jego możliwości. Ale zasada działania jest mniej więcej taka, jak niżej. Do przesłania skompilowanego pliku używam swojego wrappera. Zewnętrzne polecenie to: ssh centralka.local /usr/local/bin/esprmt a na wejście podaję plik BIN. Oczywiście połączenie ssh musi być skonfigurowane tak, aby nie trzeba było wpisywać hasła (czyli za pomocą kluczy). Na samej malince jest to już bardziej skomplikowane. Najpierw trzeba było napisać kawałek programu, który zresetuje ESP i wprowadzi w tryb programowania. W tym celu stworzyłem plik /etc/default/esp zawierający opisy pinów: boot=24 prog=23 Program w pythonie wygląda tak: #!/usr/bin/env python #coding: utf-8 cfg={} for line in open('/etc/default/esp'): line=line.strip().split('=') if len(line) == 2: cfg[line[0].strip()] = line[1].strip() bootp=int(cfg['boot']) progp=int(cfg['prog']) import sys arg = sys.argv[1] if arg not in ('boot','prog'): raise Exception('Bad arg') import RPi.GPIO as GPIO, time GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(bootp, GPIO.OUT) GPIO.setup(progp, GPIO.OUT) if arg == 'boot': GPIO.output(progp, 0) GPIO.output(bootp, 1) time.sleep(0.1) GPIO.output(bootp, 0) else: GPIO.output(bootp, 1) time.sleep(0.1) GPIO.output(progp, 1) time.sleep(0.1) GPIO.output(bootp, 0) time.sleep(0.1) GPIO.output(progp, 0) time.sleep(0.1) Po zapisaniu do /usr/local/bin/espy i nadaniu praw do uruchomienia mogę wykonać reset ESP poleceniem: espy boot lub wprowadzić w tryb programowania: espy prog Teraz tylko wystarczy wrzucić program do ESP. Do tego służy skrypt /usr/local/bin/esprmt: #!/bin/bash cat >/tmp/upload.bin /usr/local/bin/espy prog /home/pi/.arduino15/packages/esp8266/tools/python3/3.7.2-post1/python3 -I \ /home/pi/.arduino15/packages/esp8266/hardware/esp8266/3.0.2/tools/upload.py \ --chip esp8266 --port /dev/ttyS0 --baud 230400 \ --before no_reset_no_sync --after soft_reset \ write_flash 0x0 /tmp/upload.bin No i trochę o możliwościach. Ponieważ prekursorem urządzenia był po prostu regulator temperatury (na RPI 1B), i tu kontrola temperatura pozostała głównym zadaniem. Piec może pracować w następujących cyklach: zwykły dzień (rano i wieczorem cieplej, w nocy i w typowych godzinach wyjścia temperatura spada, poza tym w dzień utrzymane 18°). Ze względu na specyfikę naszej pracy na każdy dzień tygodnia mogę ustalić oddzielny program. dzień świąteczny. Jest taki sam jak program "niedziela", w stosunku do zwykłego dnia przesunięta jest godzina poranna (trochę później się wstaje) i nie ma godzin wyjścia. jestem w domu - jak zwykły dzień, ale bez godzin wyjścia wychodzę od-do (godziny wyjścia ustalane indywidualnie) wyjazd (utrzymana stała temperatura nie mniej niż 13°) program indywidualny (tu mogę zaprogramować dokładnie kiedy chcę mieć jaką temperaturę). Cykl pieca na bieżący dzień mogę sobie ustawić z klawiatury wybierając po prostu właściwą pozycję z menu: Oczywiście mogę sobie ustawić dowolne cykle na przyszłość - do tego służy kalendarz. Wszystkie święta (stałe i ruchome) są uwzględniane automatycznie, jak na zdjęciu poniżej: Ważną funkcją jest również możliwość chwilowego włączenia/wyłączenia pieca. W tym trybie mogę ustawić na stałe stan zapalony/zgaszony. Po upływie 5 minut, jeśli nie wcisnąłem żadnego klawisza, urządzenie powraca do trybu automatycznego. Algorytm ustalania temperatury jest stosunkowo prosty, ale wymagał wprowadzenia empirycznych danych, uzyskanych na podstawie obserwacji z poprzedniego sezonu grzewczego. Po prostu: Jeśli temperatura jest niższa niż zadana minus histereza piec jest włączony (rozpoczynamy grzanie) Jeśli piec pracuje a temperatura nie przekracza wartości zadanej plus histereza, piec jest włączony (kontynuujemy grzanie) Jeśli z obliczeń wypada, że trzeba rozpocząć grzanie aby o określonej porze osiągnąć odpowiednią temperaturę piec jest włączony (rozpoczynamy grzanie). W przeciwnym razie piec jest wyłączony. Dochodzą tu dodatkowe czynniki - np. temperatura na zewnątrz wpływająca na czas konieczny do ogrzania mieszkania. Również mimo, iż program uruchamiany jest z crona co minutę, jeśli od ostatniego przełączenia pieca minęło mniej niż pięć minut piec nie będzie przełączany. Wszystkie parametry niedostępne z klawiatury mogę sobie ustawić przeglądarką w sieci domowej. Przykładowo ustawianie programu grzania na konkretny dzień wygląda tak: Oprócz kontroli temperatury centralka ma jeszcze kilka przydatnych funkcji. Wbudowany syntezator mowy (kedrigern) może oznajmić godzinę, temperaturę na zewnątrz czy wygłosić prognozę pogody na dwa dni. Czujnik odległości odpowiada za włączenie wyświetlacza tylko wtedy, gdy ktoś stoi przed centralką. Ponieważ część punktów oświetleniowych w mieszkaniu wyposażona jest w moduły ESP8266, może również zapalać/gasić światło (np. automatycznie zapalić lampkę w sypialni o właściwej porze). Zastosowałem tu czujnik ToF - może się to wydawać przesadą, ale cała centralka wisi w przejściu, i żaden czujnik odbiciowy nie zapewnił mi prawidłowego wykrycia (tzn. nie reagowania na ścianę naprzeciw przy jednoczesnym wykryciu czegoś w pewnej minimalnej odległości). Ale trochę więcej o tych modułach. Taki najprostszy moduł to sterowanie oświetleniem w tzw. "saloniku". Mam tu dwie linie oświetlenia górnego i boczny kinkiet. Z przyczyn technicznych moduły są dwa: jeden po stronie wersalki z przekaźnikiem sterującym kinkietem, drugi w miejscu oryginalnego wyłącznika oświetlenia. Ponieważ moduły komunikują się ze sobą (bez pośrednictwa centralki), każdy z nich może sterować dowolną z trzech linii. Każdy moduł posiada zasilacz, jeden lub dwa przekaźniki (użyłem tu subminiaturowych z dopuszczalnym prądem obciążenia 0.5A, mam nadzieję że przy mocy LED max. 14 W/linię i połączonych równolegle stykach to wystarczy) sterowanych przez BC-817 (takie miałem w szufladzie) oraz oczywiście ESP8266. Użyłem tu "gołych" ESP-12E. Trochę problemów miałem z wyłącznikami. Ponieważ są to przyciski chwilowe - aż chciałoby się użyć przycisków sterujących do rolet. Niestety - nigdzie nie znalazłem takich które mi odpowiadają (na trzy rolety) więc zrobiłem je sam ze zwykłych tact-switchy. Chyba nawet lepiej wyszło: wyłącznik ma grubość ok. centymetra - czyli mniej więcej tyle, ile wystaje ze ściany zwykły wyłącznik podtynkowy. A oto efekt: Jak widać, do wyłącznika doprowadzone są tylko dwa przewody - jest to po prostu typowa klawiatura rezystancyjna. Odpowiednio dobierając rezystory mógłbym teoretycznie osiągnąć niezależny odczyt wszystkich trzech kanałów, ale musiałbym za dużo kombinować. Pominąłem więc niektóre kombinacje (np. wciśnięcie wszystkich trzech) i udało mi się zrobić to na zwykłych rezystorkach (tych z szuflady). Wyłącznik ma następujące funkcje: pojedyncze wciśnięcie klawisza - zapalenie (na górze) lub zgaszenie (na dole) linii; jeśli zapalane jest oświetlenie sufitowe, automatycznie gaszony jest kinkiet na ścianie; podwójne wciśnięcie klawisza na górze - zapalenie linii i wygaszenie pozostałych; podwójne wciśnięcie dowolnego klawisza na dole - zgaszenie wszystkich linii; dłuższe wciśnięcie niebieskiego klawisza na górze - przełączenie lampki stojącej w sypialni (syntezator anonsuje stan zapalona/zgaszona); dłuższe wciśnięcie dowolnego klawisza na dole - zaanonsowanie godziny i temperatury na zewnątrz przez syntezator; dłuższe wciśnięcie lewego i prawego klawisza na dole - odczytanie pogody (przed godziną 16:00 na dziś i jutro, po 16:00 na jutro i pojutrze). Moduł oświetlenia przedpokoju ma trochę inne funkcje. Przede wszystkim wyposażony jest w czujkę PIR, automatycznie zapalając światło kiedy ktoś się w przedpokoju pojawia. Za pomocą wyłącznika można również ręcznie zapalić czy zgasić światło (przydaje się, bo czujka nie widzi całego pomieszczenia, niestety nie mogłem się pozbyć martwej strefy przy szafie) lub nawet całkowicie wyłaczyć czujkę. Sama obudowa do czujki jest chyba jedynym "uniwersalnym" rozwiązaniem, tak więc przy okazji zamieszczam pliki STL i SCAD: czujka.zip. I to chyba by było na tyle - nie chcę się rozpisywać, bo o tym można książkę napisać. W razie jakichś pytań jestem do dyspozycji.
  20. Cześć, dziś pokażę Wam, w jaki sposób złożyć do kupy moduł ESP8266 (w tym wypadku płytkę WeMos D1 mini) oraz matrycę LED na sterowniku MAX7219. Oprócz tego będziemy potrzebować paru przewodów połączeniowych, lutownicy z cyną, obudowy do zegarka oraz przewód microUSB do zasilenia naszego układu. Całość projektu jest dostępna w repozytorium na moim Githubie. Obudowa Jako obudowę użyłem sklejki o grubości 6 milimetrów, którą zamówiłem na aukcji Allegro razem z docięciem. Całość obudowy z dostawą kosztowała mnie mniej niż 40 złotych. Zaprojektowałem obudowę korzystając z programu Inkscape. Następnie została ona sklejona i pokryta lakierobejcą w kolorze jasnego dębu. Na front obudowy nakleiłem mleczną plexi o grubości 3 mm, którą dostałem w lokalnym sklepie za 5 złotych. Elektronika Tutaj będzie dużo łatwiej, gdyż wystarczy połączyć tylko odpowiednie piny. Połączyłem zgodnie z adnotacją w bibliotekach do obsługi MAX7219. Należy pamiętać, że WeMos D1 operuje na napięciu 3.3V na szczęście sterownik MAX7219 akceptuje je i nie będzie problemu aby układ ruszył. Na zdjęciu połączenie za pomocą przewodów z haczykami. Kod programu Przed kompilacją programu, należy dodać do Arduino IDE obsługę płytek opartych na ESP8266 (opisał to SOYER w tym temacie). Będziemy potrzebować trzech bibliotek, które nie są standardowo dołączone do Arduino IDE: MD_MAX72XX - dzięki której nasz procesor skomunikuje się ze sterownikiem MAX7219 MD_Parola - biblioteka rozszerzająca funkcje MD_MAX72XX NTPClient - dzięki niej zyskamy możliwość pobierania czasu z Internetu bez potrzeby używania modułu RTC. Po dodaniu bibliotek, kod wygląda następująco: #include <MD_Parola.h> #include <MD_MAX72xx.h> #include <SPI.h> #include <ESP8266WiFi.h> #include <NTPClient.h> #include <ESP8266WebServer.h> #include <WiFiUdp.h> #define HARDWARE_TYPE MD_MAX72XX::FC16_HW #define MAX_DEVICES 4 #define CLK_PIN D5 // or SCK #define DATA_PIN D7 // or MOSI #define CS_PIN D8 // or SS MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); WiFiUDP ntpUDP; const char *ssid = ""; const char *password = ""; NTPClient timeClient(ntpUDP, "0.pl.pool.ntp.org", 3600, 60000); WiFiServer server(80); uint8_t frameDelay = 25; textEffect_t scrollEffect = PA_SCROLL_LEFT; #define BUF_SIZE 512 char curMessage[BUF_SIZE]; char newMessage[BUF_SIZE]; bool time_interval = false; String data; unsigned int run_seconds = 0; String timeCheck(){ timeClient.update(); data = timeClient.getFormattedTime(); data.toCharArray(newMessage, BUF_SIZE); P.displayText(newMessage, PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT); return data; } String wifiCheck(){ WiFiClient client = server.available(); while(client.available()){ String req = client.readStringUntil('\r'); req = req.substring(5,req.length()-9); req.replace("%20", " "); client.flush(); String s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nReq: " + req + "</html>\n"; client.print(s); delay(1); client.stop(); switch(req[0]){ case 's': data = req.substring(2); P.displayScroll(curMessage, PA_LEFT, PA_SCROLL_LEFT, frameDelay); return data; break; case 'i': data = req.substring(2); P.setIntensity(data); break; } } } void setup() { P.begin(); P.displayClear(); P.displaySuspend(false); P.displayScroll(curMessage, PA_LEFT, PA_SCROLL_LEFT, frameDelay); P.setIntensity(0); curMessage[0] = newMessage[0] = '\0'; WiFi.begin(ssid, password); while ( WiFi.status() != WL_CONNECTED ) { delay ( 500 ); } timeClient.begin(); server.begin(); sprintf(curMessage, "%03d:%03d:%03d:%03d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]); } void loop() { if ( (millis()/1000 - run_seconds) > 30 ){ run_seconds = millis()/1000; time_interval = true; } if (time_interval){ data = timeCheck(); time_interval = false; } data = wifiCheck(); data.toCharArray(newMessage, BUF_SIZE); if (P.displayAnimate()) { strcpy(curMessage, newMessage); P.displayReset(); } } W zależności od zakupionego modułu trzeba będzie wybrać wersję hardware sterownika MAX7219. Definiujemy ją w linijce 10: #define HARDWARE_TYPE MD_MAX72XX::FC16_HW Obecnie dostępne typy sprzętowe to: FC16_HW, PAROLA_HW, GENERIC_HW, ICSTATION_HW. Musimy tutaj dobrać wartość eksperymentalnie. Niewłaściwy typ sprzętowy powoduje błąd w kolejnosci wyświetlania oraz w kolejności animacji poszczególnych pikseli matrycy. Dodatkowa opcja (własny tekst) W kodzie dociekliwi zobaczą, że jest możliwość wyświetlenia własnego tekstu. Po uruchomieniu programu na wyświetlaczu przewinie się adres IP, który WeMos uzyskał z naszego routera. Po otworzeniu strony z poziomu przeglądarki (lub wywołaniu polecenia curl) na stronę: http://<IP>/s=<TEXT> na wyświetlaczu będzie on przewijany od prawej do lewej strony przez maksymalnie 30 sekund. Dodatkowa opcja (zmiana jasności wyświetlacza) Można również zmienić jasność wyświetlacza, albo na stałe (w kodzie linijka: 86) P.setIntensity(<liczba z zakresu 0-15>); lub poprzez stronę www pod adresem: http://<IP>/i=<LICZBA Z ZAKRESU 0-15> gdzie 0 - to wartość minimalna, a 15 odpowiada za maksymalny poziom świecenia. Zegarek prezentuje się następująco: obudowa.pdf
  21. MOTYWACJA Jest to pierwszy projekt z wykorzystaniem ESP8266, chociaż jakiekolwiek arduino poradziłoby sobie z tym bez problemu Zamysłem projektu była obserwowanie poboru prądu w pewnym czasie na monitorze komputera. Bardzo podobną rzecz posiada Daniel Rakowiecki, dosyć dużo mu pomaga w czasie naprawy OPIS DZIAŁANIA Projekt ma za zadanie zmierzyć prąd pobierany przez urządzenie badane. Układ INA219 mierzy spadek napięcia na rezystorze bocznikującym 0,1Ω. Następnie wysyła dane po magistrali I2C do NODEMCU V3.0 a µC wysyła na wyświetlacz monochromatyczny OLED oraz po UART do komputera. Używając np. kreślarki z Arduino Ide można zaobserwować w miarę fajny wykresik A ESP wybrałem ponieważ jest tani (około 15 zł). Maksymalny "bezpieczny" prąd to 3,2A. A schemat ideowy wygląda tak: I troszkę zdjęć: ORAZ KOD PROGRAMU W programowaniu jestem strasznym laikiem, więc proszę o wyrozumiałość Inspirowałem się kilkoma stworzeniami podobnych konstrukcji na Youtubie. #include <Wire.h> #include <Adafruit_INA219.h> #include <SPI.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> Adafruit_INA219 ina219; #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); int current_mA = 0; float busvoltage = 0; float shuntvoltage = 0.0; float loadVoltage_V = 0.0; void setup(void) { ina219.begin(); Serial.begin(9600); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,10); display.println("LOADING"); display.display(); display.setTextSize(1); display.setCursor(0,50); display.println("POWERED BY ZIMOL03"); display.setCursor(0,0); display.println("v1.1"); display.display(); delay(300); for ( int width=0 ; width<115 ; width++) { display.fillRect(0, 30, width, 15, WHITE); display.display(); delay(150); width = width + 2; width++; } } void loop(void) { shuntvoltage = ina219.getShuntVoltage_mV(); current_mA = ina219.getCurrent_mA(); busvoltage = ina219.getBusVoltage_V(); loadVoltage_V = busvoltage + (shuntvoltage / 1000); Serial.println(current_mA); display.clearDisplay(); voltCurrent(); delay(25); } void voltCurrent() { display.setTextSize(3); display.setTextColor(WHITE); display.setCursor(0, 10); display.print(current_mA); display.print("mA"); display.setCursor(0, 40); display.print(loadVoltage_V); display.print("V"); display.display(); } A DOKŁADNOŚĆ? No tu już jest troszkę gorzej. W szkole przy użyciu w miarę dokładnego amperomierza analogowego pomierzyłem trochę prądów*: No jest dosyć duża rozbieżność, niestety. Pewnie można byłoby polepszyć dokładność; zmienić rezystor na taki, który ma TCR bardzo mały, dokładność lepszą, dorobić lepsze rozpraszanie ciepła itd. *Trochę mało czasu miałem, dlatego badanie odbywało się co 500mA PODSUMOWANIE Urządzonko może głownie przydać się jako dodatek do zasilacza laboratoryjnego, ale także w szkołach przy badaniu różnych układów np. na pracowni elektrycznej. Uczniowi na pewno będzie trochę łatwiej przy rysowaniu charakterystyk
  22. Po kursie Arduino chcesz nauczyć się czegoś jeszcze? Albo chcesz zrobić inteligenty dom? A może Arduino po prostu ci nie wystarcza - na to wszystko jest rozwiązanie! ESP8266 to wydajny i tani mikrokontroler. Znajdziemy go na wielu płytkach, od małego 01 na NodeMCU kończąc. Dzisiaj zobaczymy jakie są rodzaje płytek, co w sobie ukrywa kostka mikrokontrolera, oraz spróbujemy przygotować środowisko i wgrać Blinka. Niestety, tak nie robimy - tylko zewrzemy piny. Spis treści serii artykułów: 1. Omówienie, i przygotowanie środowiska 2. Zapoznanie z nowym środowiskiem, praca jako Arduino, prosty serwer WWW 3. Przyspieszony kurs na webmastera 4. Wykresy, zapis do SPIFFS, mini smart-dom 5. Odbiór danych z przeglądarki, stałe IP, łączenie modułów ESP Ten wpis brał udział konkursie na najlepszy artykuł o elektronice lub programowaniu. Sprawdź wyniki oraz listę wszystkich prac » Partnerem tej edycji konkursu (marzec 2020) był popularny producent obwodów drukowanych, firma PCBWay. Możliwości ESP8266 Uznałem, że najlepiej będzie zestawić NodeMCU v3 z Arduino UNO. Najważniejsze informacje podałem w tabeli. Pamiętamy, że ESP pracuje na napięciu 3.3v. Kiedy podłączałem do 5V (nie róbcie tego w domu) się tylko grzał, ale lepiej tego nie róbcie. ESP ma także pamięć FLASH w oddzielnej kostce - co pozwala na dużą jej pojemność (zależy to od wersji modułu). Co więcej, interfejsy możemy zdefiniować na (prawie) każdym pinie. ESP pracuje o wiele szybciej od Arduino - bo aż na 80MHz (z możliwością do 160!), i przede wszystkim ESP jest 32 bitowe. No, i ESP się lekko grzeje, ale to nic złego. Warianty ESP8266 Ten mały mikrokontroler możemy znaleźć na równie małych płytkach, lub znacznie większych i rozbudowanych modułach. Jednym z mniejszych, samodzielnych modułów jest ESP12. Posiada wiele wyprowadzeń, lecz nie jest (nawet z przejściówką) zbyt przyjazny dla płytki stykowej. Posiada natomiast aż 4MB pamięci FLASH (wersja ESP12F). Będzie to optymalny wybór dla rozwiązań wbudowanych (np. własnych płytek). ESP12 Jeżeli natomiast szukamy czegoś równie małego, ale bardziej przyjaznego dla nas, tutaj nadchodzi ESP01. Ten mały modulik ma niestety mniej pamięci (niebieska wersja 512kB, czarna 1MB), oraz tylko 8 wyprowadzonych pinów - lecz do konkretnego zastosowania, np. gniazdka z przekaźnikiem, wystarczy. ESP01 (niebieski) ESP03 i ESP07 to uboższe wersje ESP12, ale posiadają ceramiczną antenę - a ESP07 nawet złącze do zewnętrznej anteny. ESP07 Pozostałe moduły rzadko się spotyka, różnią się jedynie ilością wyprowadzeń, rozmiarem, i sposobem montażu. Przygotowanie ESP do programowania W zależności od tego, jaki moduł wybrałeś, będziesz musiał albo przylutować przewody do jego wyprowadzeń, lub podłączyć się przewodami do płytki stykowej. Dlatego na początek nauki, najlepiej zakupić NodeMCU (płytkę deweloperską z ESP12 na pokładzie), Wemos (troszkę mniejsze płytki z ESP12) - mają wszystko wbudowane. Jeżeli taką płytkę wybrałeś, możesz pominąć ten krok. Mając "surowe" ESP12 lub 01, musisz je odpowiednio podłączyć. Połączenie ESP01 z konwerterem USB ↔ UART. Rozpiska pinów dla ESP01. Do tego będziemy potrzebować dwóch przycisków tact switch, kondensatora elektrolitycznego (z zakresu 100-1000µF), dwóch rezystorów 10kΩ, przewodów, oraz oczywiście ESP i konwertera. Pokazałem to na przykładzie ESP01, ale każdy ESP też ma takie wyprowadzenia: pin CH_PD łączymy na stałe do napięcia zasilania przez rezystor 10kΩ pin RST podciągamy do VCC rezystorem 10kΩ, oraz podpinamy przycisk zwierający do masy pin GPIO0 podpinamy do przycisku zwierającego z masą między VCC a GND dajemy kondensator pin RX konwertera łączymy z pinem TX ESP, a pin TX konwertera z pinem RX ESP piny VCC i GND ESP łączymy z pinami VCC i GND konwertera napięcie na konwerterze ustawiamy na 3.3V! Na NodeMCU także znajdziemy dwa przyciski. Przycisk RST odpowiada ze reset mikrokontrolera - tak samo jak w Arduino. Ale co robi przycisk PRG? Otóż, jeżeli na pin GPIO0 podamy logiczne 0 podczas startu mikrokontrolera, wprowadzimy go w tryb programowania. Dzięki temu będziemy mogli wgrać do niego nasz kod. Jeżeli nie mamy zainstalowanych sterowników dla konwertera (np. CH340), powinniśmy je pobrać i zainstalować. Przygotowanie środowiska ESP możemy programować na dwa sposoby, w języku lua - oraz klasycznie, jak arduino, w c++. Opiszę wam sposób jak programować ESP jako Arduino - a do tego potrzebne będzie Arduino IDE. Jeżeli jeszcze takowego nie mamy, pobieramy najnowszą wersję stąd, po czym instalujemy. Dokładny proces instalacji został opisany na kursie Arduino - jeżeli mamy już zainstalowane środowisko, uruchamiamy je, a następnie przechodzimy do zakładki Plik → Preferencje. Powinno nam się otworzyć nowe okno. Szukamy okienka "Dodatkowe adresy URL do menedżera płytek", i wklejamy ten adres: https://arduino.esp8266.com/stable/package_esp8266com_index.json Całość powinna teraz wyglądać tak: Klikamy OK - następnie przechodzimy do zakładki Narzędzia → Płytka → Menedżer płytek Szukamy "esp8266", i klikamy Instaluj. Pobrane zostanie ok. 100MB danych. Od teraz możemy wybrać płytkę ESP jak zwykłą płytkę Arduino. ALE! Jeżeli już kiedyś programowałeś, w innym IDE, zapewne wiesz, że Arduino IDE jest troszkę przestarzałe. Brak autouzupełniania, podpowiedzi, rozbudowanego systemu plików, GIT, i innych funkcji. Jest na to sposób! Dlatego w tym poradniku także opiszę instalację i konfigurację Microsoft Visual Studio Code do pracy z Arduino. Dzięki temu będzie o wiele prościej i wygodniej pisać programy. Pobieramy zatem najnowsze VS Studio Code z tej strony. Jak widać jest ono dostępne na Windowsa, Linuxa, i MacOS. Po pobraniu i zainstalowaniu, powinniśmy zobaczyć taki widok: Jeżeli ciemny motyw nam nie odpowiada, możemy to zmienić naciskając koło zębate (lewy, dolny róg) i "Color Theme" - jasny motyw to Light+. Problemem dla niektórych może być język angielski - lecz w informatyce jest on niezbędny do funkcjonowania. Nie ma problemu aby wrócić do spolszczonego Arduino IDE. Jeżeli jednak chcesz zostać przy VS Code, musimy zainstalować rozszerzenie Platform.io. Dokładniej masz to opisane w tym forbotowym artykule. Po zainstalowaniu Platformio, klikamy magiczny przycisk F1 (musimy go zapamiętać!), i ukazuje nam się to okno: Znajdziemy tam wszystkie funkcje znane z Arduino IDE! Teraz możemy podpiąć nasz konwerter lub NodeMCU do komputera. Tworzymy nowy projekt, i szukamy po lewej pliku platformio.ini. Tam możemy wybrać inną płytkę niż ta, którą wybraliśmy podczas tworzenia projektu. Możemy także ustalić inne rzeczy - więcej w dokumentacji. Otwieramy podfolder src, i szukamy pliku main.cpp. Otworzyło nam się nowe okno - pierwsze co widzimy, to biblioteka Arduino. Dołączy ona wszystkie funkcje z starego IDE. Wklejamy poniższy kod. Największą różnicą ESP w stosunku do Arduino, jest tutaj zapalenie poprzez ustawienie LOW na pinie. #include <Arduino.h> void setup() { pinMode(LED_BUILTIN, OUTPUT); //pin drugi jako wyjście } void loop() { digitalWrite(LED_BUILTIN, LOW); //zapalamy diodę delay(1000); //czekamy sekundę digitalWrite(LED_BUILTIN, HIGH); //gasimy diodę delay(1000); //czekamy sekundę } Teraz przyszedł moment na wgranie szkicu. Klikamy przycisk PRG (gdzieniegdzie opisywany jako FLASH), trzymamy, a następnie RESET, i puszczamy. Dioda powinna raz mrugnąć - oznacza to, że ESP jest gotowe do zaprogramowania. Klikamy zatem znowu F1, a następnie "Platformio: Upload". Cierpliwie czekamy, aż program się wgra. Kiedy zobaczymy ten komunikat: Przyciskamy przycisk reset na naszej płytce - i dioda powinna zacząć mrugać. Na dole, po lewej, na niebieskim pasku są także małe ikonki pozwalające wgrać szkic. Gratulacje, udało Ci się zaprogramować ESP! Możesz je teraz wykorzystać jako moduł Wifi, samodzielne urządzenie, zastosowań jest tyle ile dla Arduino, a nawet i więcej. Udało Ci się także skonfigurować poprawnie VS Code do pracy z płytkami Arduino (i nie tylko!). W następnym odcinku zobaczymy co tak naprawdę oferuje przesiadka na VS Code, oraz spróbujemy połączyć się przez Wifi. Liźniemy nawet trochę HTMLa z CSSem, aby postawić stronę WWW! W przypadku jakichkolwiek problemów, nie bójcie się pisać komentarzy. Spis treści serii artykułów: 1. Omówienie, i przygotowanie środowiska 2. Zapoznanie z nowym środowiskiem, praca jako Arduino, prosty serwer WWW 3. Przyspieszony kurs na webmastera 4. Wykresy, zapis do SPIFFS, mini smart-dom 5. Odbiór danych z przeglądarki, stałe IP, łączenie modułów ESP
  23. Kojarzycie jakieś gotowce na ESP8266 (mam Wemos d1 mini konkretnie) by miało już webserver, formularze do konfiguracji (wifi, etc), możliwość wgrania nowego firmware OTA? To brzmi jak taki standardowy pakiet, ale szperałem za szablonami i znalazłem tylko takie coś https://gist.github.com/DeanCording/5308e474d909f7d70613722fd86b09bb ale to nie do końca to. Nie chciałem wyważać otwartych drzwi. Obecnie mam wgraną Tasmotę*, ale ona ma pewne minusy i chciałem zastąpić swoim softem, ale nie chce mi się klepać tych generycznych rzeczy.. obstawiam, że ktoś mógł już to zrobić * konkretnie to ESP robi za bramkę między licznikiem energii, a RPi i po MQTT raportuje impulsy.. jednak info o liczbie impulsów i czasie pomiędzy 2 ostatnimi umie wysyłać nie częściej niż raz na 10s, a chciałbym po każdym impulsie, bo obecny sposób i zapis tego w InfluxDB dość mocno utrudnia rysowanie sensownych danych w Grafanie.
  24. Opis konstrukcji Dość modny ostatnio temat, poruszający jakość powietrza, powstał w celu wykonania pomiarów wpływu kominka w domu na zapylenie. W ten oto sposób powstała stacja pogodowa z prezentacją pomiarów na LCD 2004 z I2C oraz możliwością udostępnienia danych dla Domoticz lub ThingSpeak. Sercem stacji jest układ ESP8266-12F na adapterze ESP Shild. Całość umieszczono na PCB zaprojektowanym w EAGLE. Płytka jest zaprojektowana w sposób umożliwiający szybką wymianę poszczególnych elementów. Może być ona wykorzystywana do programowania ESP z wykorzystaniem złącza PROG (po podpięciu się konwerterem USB-UART), jak również w innych projektach wykorzystujących I2C, wejście analogowe ESP, wejścia cyfrowe. Jako zasilanie wykorzystałem zasilacz 12V 1A, których mam kilkanaście. Dla potrzeb zasilania czujników potrzebujemy zasilania 5V o wydajności prądowej ok 1A. W tym celu wykorzystano przetwornicę impulsową step down - przetwornica DC-DC Mini 360 . Przetwornica, jak i inne elementy jest wymienna (na goldpinach). Takie rozwiązanie wymusiło stosowanie tego samego układu w innych projektach, gdzie miałem dostępne zasilania 24-30VDC). Projekt spodobał się znajomym, więc płytka została od razu wykonana w kilku egzemplarzach na frezarce mojego wykonania. Wygląd PCB od strony druku można zobaczyć na zdjęciach. Realizacja pomiarów: W założeniach miałem mierzyć tylko zawartość pyłów ale w szufladach zalegało jeszcze kilka innych czujników. Stąd też dodatkowe pomiary. Pyły: PM1; PM2,5; PM10 - czujnik PMS5003 Ciśnienie, temperatura, wilgotność - czujnik BME280 Wskaźnik CO2 - czujnik MQ135 Wyniki prezentowane są na LCD oraz przez WiFI korzystając z oprogramowania EasyEsp. Istnieje możliwość konfiguracji oprogramowania w celu przesyłania pomiarów do Domoticz lub ThingSpeak. Oprogramowanie W założeniach miałem napisać własny soft wykorzystując biblioteki dostępne dla Arduino IDE, ale z braku czasu poszedłem na łatwiznę i wykorzystałem EasyEsp. Soft wgrywamy za pomocą oprogramowania Esp8266Flasher - wykorzystujemy połączenie po USB - UART (złącze PROG na PCB). Konfiguracja Proces konfiguracji jest dokonywany z poziomu strony WWW oprogramowania ESPEasy i jest dość intuicyjny. Wszystkie parametry wpisujemy w zakładkach odpowiedzialnych za obsługę sieci, czujników iitp. Oczywiście proces konfiguracji opisany jest dokładnie na stronie projektu ESPEasy. Dla osób nie obeznanych w tej tematyce zamieszczam plik konfiguracyjny mojego projektu wraz z dokumentacją zdjęciową (Konfiguracja ESP). Podsumowanie Na chwilę obecną brak jest obudowy, ale układ powstał jako prototyp i każdy adresat układu ma ją wykonać we własnym zakresie. Sam zrobię to jak skończę inne projekty. Konstrukcja ma sporo wad: 1. Brak kalibracji czujników. 2. Pomiar MQ135 to tylko wskazanie przesunięte o 400 ppm (~poziom CO2 w atmosferze, nie uwzględniam wpływu temperatury i wilgotności). 3. Brak dzielnika napięcia na A0 (ESP ma pomiar 0-1V, MQ135 może dać do 5 V przy 5000 ppm), jednak zakładam, że nie będę miał stężenia ponad 1000ppm w domu. Później przetnę ścieżkę na PCB i dam dzielnik na analogu (co niestety zmniejszy dokładność pomiarów) lub zabezpieczę wejście analogowe diodą zenera. 4. Gotowe oprogramowanie z wieloma wadami, w planach zmiana na własny soft i wysyłanie informacji na Cayenne IOT. Zalety: 1. Prosta modułowa konstrukcja, uniwersalna płytka PCB stosowana przeze mnie w innych projektach. 2. Gotowy soft możliwy do wgrania i konfiguracji dla zupełnych laików. Jeśli ktoś jest zainteresowany dodatkowymi materiałami, to proszę o kontakt PW. W załączniku zamieszczam: 1. EAGLE - schemat w EAGLE 9.1.2 wraz z rysunkiem ścieżek. 2. ESP - oprogramowanie w wykorzystanej wersji. 3. ESP8266Flasher - soft do wgrania oprogramowania. Podczas testów pomiar pyłów miałem na zewnątrz przy mrozach ponad 300 szczytowo i pokrywało się to z lokalną stacją w Połańcu (odchyłka była w granicach 5%). Czujnik PM5003 pracuje u mnie w cyklu 60 sekund pomiary/ 30 minut uśpienie. Żywotność czujnika laserowego to 8000 h. Częstszych pomiarów nie potrzebuję do swoich potrzeb. Czas 60 sekund wystarcza do odpowiedniego wygrzania czujnika i ustabilizowania się pomiarów. Cała stacja pobiera zaraz po starcie ok. 3 W, a po nagrzaniu czujnika MQ135 pobór energii spada do ok. 1,8 W. ESP8266Flasher.zip EAGLE.zip ESP.zip Konfiguracja_ESPEasy.zip
  25. Witam. Znalazłem na YouTube stacje pogodową. W podpisie pod filmem jest adres do kodu źródłowego który należy otworzyć w Arduino IDE, wpisać/zmienić w 3 linijkach dane i działa. Ja jako zielony szczypiorek na wiosnę. -Spróbujmy Wczoraj przyszły zamówione komponenty, więc wierząc w swoje siły przysiadłem fałda pościągałem, po instalowałem i po spędzeniu w sumie 7h udało mi się dojść jakich bibliotek mi brakuje. Lecz przy kompilacji wywala mi błąd. Mam płytkę Nodemcu CH340G V3 i ekranik MSP 2401 lub MSP 2042 Według LcdWiki sterownik który jest podany w kodzie powinien pasować. A wywala błąd. Może ze złej biblioteki korzystam? Będę wdzięczny za jakąkolwiek pomoc/wskazówkę co jest nie tak. esp8266-weather-station-color.zip
×
×
  • Utwórz nowe...