Przeszukaj forum
Pokazywanie wyników dla tagów 'akwarium'.
Znaleziono 4 wyniki
-
Od kiedy tylko ludzie zaczęli interesować się akwarystyką od tego momentu postanowili na każdym kroku ułatwiać sobie pracę. Powstawały coraz to nowsze urządzenia – napowietrzacze, filtry, oświetlenia i tym podobne. Także w tej dziedzinie pojawiły się urządzenia w pełni zautomatyzowane (sterowane za pomocą różnego rodzaju przełączników oraz czujników). Przykładem takiego rozwiązania był chociażby sterownik akwariowy “Skalar”. Jako że powstał on w czasach gdy o diodach mało kto słyszał a sterowanie PWM już chyba nikt postanowiłem stworzyć własny sterownik akwariowy Bella. Jest to kompletny sterownik oparty na Arduino Nano v3. Sterownik akwariowy Bella – lista materiałów do budowy Obudowa uniwersalna Z1W Panel przedni MN-Tech Panel tylny MN-Tech Płytka prototypowa Bella Compact 1.1 moduł pod driver PWM firmy Meanwell (5 kanał) – opcjonalny arduino nano wraz z programem sterownika akwariowego Bella zegar ds1307 wyświetlacz LCD 20×4 znaki na szynie I2C termometr DS18b20 wodoodporny na kablu – 1 szt drivery firmy Meanwell serii LDD (dowolny model) (max 5 szt) przekaźnik 1 kanałowy – 2 szt przekaźnik 4 kanałowy – 1 szt kable połączeniowe FF długości 20cm do podłączenia modułów (30 szt – liczone z zapasem) gniazdo na goldpin 3 pinowe – 8 szt gniazdo na goldpin 4 pinowe – 2 szt (do wyświetlacza) gniazdo na goldpin 5 pinowe – 1 szt (do klawiatury) gniazdo na goldpin 6 pinowe – 2 szt (przekaźnik 4 kanałowy) zworka 2 pinowa (używana w przypadku braku sondy pH) przycisk chwilowy – 4 szt (jako klawiatura) włącznik kołyskowy okrągły – 2 szt gniazdo montażowe płaskie (GS-035) – 6 szt gniazdo zasilające na kabel ósemka – 1 szt gniazdo + wtyk 3 pinowy mini CB (termometr) gniazdo + wtyk 2 pinowy mini CB do podłączenia zasilania driverów 2x gniazdo + 2x wtyk 4 pinowy CB na listwę led (każde na 2 kanały oświetlenia) lub 1 gniazdo CB 8 pinowe na 4 kanały gniazdo montażowe do zasilania arduino 5,5/2,1 gniazdo bezpiecznikowe – 1 szt śruby czarne M3 długość 12mm – około 30szt nakrętki M3 – około 60 sztuk Oświetlenie Najważniejszym założeniem sterownika akwariowego Bella było oczywiście sterowanie oświetleniem. Sterownik obsługuje 5 kanałów PWM. Na samej płytce jest miejsce na zamontowanie 5 driverów z firmy Meanwell (dowolnych od LDD-300L do LDD-1500H). 4 z nich znajdują się na płytce natomiast piąty można zamontować osobno na dodatkowej płytce. Napięcie zasilania driverów jest w zakresie od 12 do 48V. Na płytce zostały wyciągnięte piny pwm oraz masy do podłączenia dodatkowych driverów. Moc każdego kanału można ustawić dowolnie w granicy 0-100% (wartości 0-255 na wyświetlaczu). Wszystkie kanały włączają się jednocześnie i gaszą o tej samej godzinie (wyjątkiem są kanały 1 i 2, które w przypadku ustawienia oświetlenia nocnego gaszą się dopiero o północy. W przypadku kiedy moc któregoś z kanałów jest zmniejszona jego ściemnianie / rozjaśnianie działa tak samo długo lecz jest proporcjonalnie wolniejsze (skoki są mniejsze). Długości i zakresy które można ustawić w tym dziale zostaną opisane w ostatnim akapicie “Zakresy”. Termostat Kolejnym punktem jest zastosowanie wodoodpornego termometru DS18b20 i możliwość podłączenia do niego dwóch niezależnych urządzeń (np grzałki oraz chłodnicy czy też wentylatora). Jak w poprzednim punkcie zakresy zostały opisane na końcu. W przypadku awarii / odpięcia termometru temperatura na wyświetlaczu znika (zostaje zastąpiona przez znaki –. Dodatkowo w ramach zabezpieczenia w takim przypadku gniazda zarówno grzania jak i chłodzenia zostają odłączone od zasilania. W przypadku tej funkcjonalności mamy dostępne 2 zmienne. Pierwsza to temperatura (oznaczająca temperaturę dolną) poniżej której uruchomiona zostanie grzałka. Druga to histereza. Główną wartością jest temperatura – jest to deklarowana wartość dolna poniżej której uruchomiony zostanie przekaźnik odpowiadający za grzałkę. Na wyświetlaczu zmieni się opis z G-OFF na G- ON. Temperatura będzie rosła do momentu przekroczenia 1x histerezy w górę. W przypadku jeśli temperatura przekroczy wartość zadaną + 2x histerezę uruchomi się chłodzenie i będzie działać do momentu w którym włączyło się grzanie (T+H). Stycznik 1 oraz stycznik 2 Stycznik 1 może zostać ustawiony w 2 trybach – pierwszym z nich jest to tryb pracy do sterowania elektrozaworem do CO2 (sterowanie uruchamia się automatycznie po podłączeniu sondy pH). Drugim trybem jest uruchamianie się 1x dziennie na dowolny okres (od 1 minuty do 23 godzin i 59 minut). W przypadku gdy nie ma podpiętej sondy pH do sterownika należy zrobić mostek pomiędzy pinem A7 a pinem GND – program automatycznie zmienia działanie stycznika na tryb pracy 1x dziennie. Stycznik 2 działa tylko jako uruchamiany 1x dziennie (jak wyżej). Stycznik 3 oraz stycznik 4 Działają one automatycznie w zależności od pór dnia. Stycznik numer 3 uruchamia się w momencie rozpoczęcia świtu i wyłącza w momencie gdy kończy się zmrok. Stycznik numer 4 uruchamia się tylko w czasie trwania dnia. Sonda pH Program obsługuje sondy pH opisane w tym artykule. Podpinana jest ona do pinu A7. Za jej pomocą sterowany jest stycznik numer 1 do którego można podpiąć jak pisałem powyżej elektrozawór dozujący CO2. W momencie w którym nie mamy podpiętej sondy pH należy zewrzeć piny A7 oraz GND. Dzięki temu stycznik nr 1 przełącza się na działanie 1x dziennie. Wartości które możemy zmieniać to dolna granica pH oraz histereza. Po przekroczeniu górnej granicy uruchomiony zostaje stycznik (do którego można podłączyć elektrozawór dozujący CO2) i działa on do momentu obniżenia pH poniżej zadanej wartości. Oświetlenie awaryjne Pin analogowy A6 został przygotowany jako uruchamianie oświetlenia awaryjnego. W przypadku gdy pin zwarty jest do masy (za pomocą rezystora 10kOhm program działa w normalnym trybie. Gdy włącznik zostanie uruchomiony i zwarty do pinu 5V oświetlenie zacznie świecić na około 50% mocy (na tyle mocno żeby było widać lecz aby nie oślepiać). W tryb pracy awaryjnej zostaną uruchomione kanały 1 oraz 2. Do uruchomienia oświetlenia awaryjnego służy przycisk na boku obudowy (z przodu) po stronie wyświetlacza. Wyłącznik oświetlenia Istnieje także możliwość wyłączenia całkowicie napięcia podawanego na diody. Do tego celu służy przełącznik znajdujący się z boku obudowy (od strony wyświetlacza z tyłu). Dzięki temu możemy w sytuacji w której jest potrzeba odpięcia oświetlenia od sterownika odłączyć całkowicie zasilanie driverów. Wyświetlacz LCD Sterownik akwariowy Bella oferuje dwa tryby działania. Pierwszym z nich jest tryb wyświetlania informacji znajdujących się na sterowniku. Poniżej znajduje się właśnie taki układ. Na żółto zostały oznaczone pola odpowiednio od góry: Grzałka, Wentylator, Stycznik 1 (jeśli używamy sondy pH zmienia się jego nazwa z S na C – można podpiąć do niego elektrozawór CO2) oraz Stycznik S2. Styczniki S3 oraz S4 (ich godziny działania) wyświetlane są w menu rotacyjnym na dole ekranu. Kolejnym trybem w jakim pracuje ten sterownik akwariowy jest tryb menu. Aby go uruchomić należy jednocześnie przytrzymać przyciski + oraz – (góra i dół). W tym momencie układ wyświetlacza zmieni się całkowicie na prezentowany poniżej: Funkcje menu (nazwa, dostępne wartości oraz ich skoki) W trybie ustawień zmieniają się dwa dolne wiersze na wyświetlaczu. Na górnym jest informacja o aktualnie zmienianej funkcji oraz jej wartości. W dolnym rzędzie pokazany jest dostępny zakres w jakim może być on zmieniany. Dokładna struktura menu zakresy oraz skoki są dostępne w tabeli poniżej. Po uzyskaniu maksymalnej wartości (dla przykładu PWM1 – 255) jeśli nadal będzie włączony przycisk + wartości będą liczone dalej od najniższej. Podobnie dzieje się w przypadku uzyskania najniższej wartości i trzymania przycisku -. W tym przypadku wartość zmieni się na największą. Więcej informacji znajdziecie w filmie:
-
Kolejny sterownik akwarystyczny, a może coś więcej :)
jacqob569 opublikował temat w Artykuły użytkowników
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 Ten artykuł bierze udział w naszym konkursie! 🔥 Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » 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("jkgrzelak1@gmail.com", 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 🙂 -
Kolega Robert (SP9-10126-KR) poprosił mnie o wykonanie sterownika do akwarium. Założeniem była "symulacja dnia" (płynne rozjaśnianie i ściemnianie oświetlenia LED o świcie i zmierzchu) oraz monitorowanie temperatury wody z alarmem (w razie awarii fabrycznej grzałki z termostatem do akwariów). Pracę urządzenia można doglądać za pomocą strony WWW. Parametry odświeżają się na żywo dzięki zastosowaniu technologii AJAX. Jeżeli temperatura nie znajdzie się w ustawionym przedziale zostanie włączony alarm. Czas synchronizowany jest automatycznie korzystając z serwera NTP. Ciekowostką jest możliwość konfiguracji nazywy SSID sieci WiFi i hasła do niej przez samego użytkownika poprzez stronę sterująca. Standardowo urządzenie łączy się z siecią WiFi. W przypadku, gdy zapamiętana konfiguracja jest niezgodna z parametrami sieci domowej, wystarczy nacisnąć i przytrzymać przycisk przed podłączeniem do prądu. ESP8266 uruchomi się w trybie Access Pointa, z którym będziemy mogli się połączyć i na stronie http://192.168.1.1 dokonamy poprawek. Po podłączeniu do sieci domowej WiFi możemy też nacisnąć wspomniany przycisk w trakcie pracy urządzenia, co pozwoli na wyświetlenie uzyskanego z DHCP IP. Ułatwi to wejście na stronę konfiguracyjną. Przy uruchomieniu następuje odczyt ustawień sieci #include <EEPROM.h> String ssid = "", pass = ""; [...] setup(){ if (ssid == "") { int i; for (i = 100; i < 170; i++) { if (EEPROM.read(i) == NULL) { break; } ssid += char(EEPROM.read(i)); } for (int ii = i + 1; ii < 250; ii++) { if (EEPROM.read(ii) == NULL) { break; } pass += char(EEPROM.read(ii)); } } WiFi.mode(WIFI_STA); WiFi.begin(ssid.c_str(), pass.c_str()); [...] server.on("/", handleRoot); server.on("/sensors", handleSensors); server.begin(); } Za odbieranie danych z formularza WWW i zapisywanie ich w pamięci odpowiada void handleRoot() { [...] if (server.hasArg("SSID") && server.arg("SSID") != "" && server.hasArg("wifipass") && server.arg("wifipass") != "") { for (i = 0; i < server.arg("SSID").length(); i++) { EEPROM.write(i + 100, server.arg("SSID")[i]); } i += 100; EEPROM.write(i, NULL); for (i = 0; i < server.arg("wifipass").length(); i++) { EEPROM.write(i + 101 + server.arg("SSID").length(), server.arg("wifipass")[i]); } EEPROM.write(server.arg("SSID").length() + server.arg("wifipass").length() + 101, NULL); EEPROM.commit(); ESP.restart(); } } Cały kod wygląda następująco: #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <EEPROM.h> #include <WiFiUdp.h> #include <Time.h> // http://www.pjrc.com/teensy/td_libs_Time.html #include <ESP8266mDNS.h> #include <OneWire.h> //https://github.com/adafruit/ESP8266-Arduino/tree/esp8266/libraries/OneWire #include <DallasTemperature.h> //https://github.com/milesburton/Arduino-Temperature-Control-Library #include <Wire.h> #include <LiquidCrystal_I2C.h> //https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library #define PWMpin 15 //(D8) #define WiFimodeButton 12 //D6 #define PumpPin 13 //(D7) #define TempAlarmPin 14 //(D5) #define ONE_WIRE_BUS 4 //D2 /*char ssid[] = ""; // your network SSID (name) char pass[] = ""; // your network password */ String ssid = "", pass = ""; const char* WiFiHostname = "aquarium"; unsigned char screenCount = 0, tmBegin, tmEnd, dusk, pumpBegin, pumpEnd, tempMin, tempMax; bool lastTimeLCD, tempAlarm, lastButtonStatus = HIGH; unsigned int localPort = 2390; // local port to listen for UDP packets int PWM = 0; float temp = 0; /* Don't hardwire the IP address or we won't get the benefits of the pool. Lookup the IP address for the host name instead */ //IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server IPAddress timeServerIP; // time.nist.gov NTP server address const char* ntpServerName = "time.nist.gov"; const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets // A UDP instance to let us send and receive packets over UDP WiFiUDP udp; ESP8266WebServer server(80); OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); LiquidCrystal_I2C lcd(0x27, 16, 2); tmElements_t tm; time_t getNtpTime(); unsigned long last_time_sync = 0, last_NTP_check = 0; void setup() { pinMode(PWMpin, OUTPUT); pinMode(WiFimodeButton, INPUT_PULLUP); pinMode(TempAlarmPin, OUTPUT); Serial.begin(9600); EEPROM.begin(512); Wire.begin(0, 2); //D2, D4 Wire.setClock(100000); sensors.begin(); lcd.begin(); lcd.backlight(); tmBegin = EEPROM.read(0); tmEnd = EEPROM.read(1); dusk = EEPROM.read(2); pumpBegin = EEPROM.read(3); pumpEnd = EEPROM.read(4); tempAlarm = EEPROM.read(5); tempMin = EEPROM.read(6); tempMax = EEPROM.read(7); // We start by connecting to a WiFi network // WiFi.mode(WIFI_AP_STA); WiFi.softAPdisconnect(); WiFi.disconnect(); if (ssid == "") { int i; for (i = 100; i < 170; i++) { if (EEPROM.read(i) == NULL) { break; } ssid += char(EEPROM.read(i)); } for (int ii = i + 1; ii < 250; ii++) { if (EEPROM.read(ii) == NULL) { break; } pass += char(EEPROM.read(ii)); } } if (digitalRead(WiFimodeButton) == LOW) { //access point part Serial.println("Creating Accesspoint"); IPAddress apIP(192, 168, 1, 1); WiFi.mode(WIFI_AP); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); WiFi.softAP("aquarium", "aquarium123"); Serial.print("IP address:\t"); Serial.println(WiFi.softAPIP()); } else { //station part Serial.print("connecting to..."); Serial.println(ssid); WiFi.hostname(WiFiHostname); WiFi.mode(WIFI_STA); WiFi.begin(ssid.c_str(), pass.c_str()); /* while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } */ Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } /* MDNS.begin("aquarium"); MDNS.addService("http", "tcp", 80);*/ Serial.println("Starting UDP"); udp.begin(localPort); Serial.print("Local port: "); Serial.println(udp.localPort()); server.on("/", handleRoot); server.on("/sensors", handleSensors); server.begin(); } void loop() { Serial.println(ssid); Serial.println(pass); Serial.println(WiFi.status()); if (millis() - last_time_sync > 10 * 24 * 60 * 60 * 1000 || last_time_sync == 0) { unsigned long unixt = unix_time(); if (unixt > 1512876158) { setTime(unixt); last_time_sync = millis(); } } Serial.print("Time library "); Serial.print(current_time(0)); breakTime(now(), tm); if (now() < 1512876158) { PWM = 0; } else if (tmBegin < tmEnd && tm.Hour >= tmBegin && tm.Hour < tmEnd || tmBegin > tmEnd && (tm.Hour >= tmBegin || tm.Hour < tmEnd)) { if (tm.Hour * 60 + tm.Minute < tmBegin * 60 + dusk || tm.Hour * 60 + tm.Minute < tmEnd * 60 - dusk) { PWM = 1023 - map(tmBegin * 60 * 60 + dusk * 60 - (tm.Hour * 60 * 60 + tm.Minute * 60 + tm.Second), 0, dusk * 60, 0, 1023); } if (tm.Hour * 60 + tm.Minute > tmEnd * 60 - dusk) { if (tmBegin < tmEnd) { PWM = 1023 - map(tm.Hour * 60 * 60 + tm.Minute * 60 + tm.Second - (tmEnd * 60 * 60 - dusk * 60), 0, dusk * 60, 0, 1023); } else { PWM = map(tmEnd * 60 * 60 - (tm.Hour * 60 * 60 + tm.Minute * 60 + tm.Second), 0, dusk * 60, 0, 1023); } } if (tm.Hour * 60 + tm.Minute >= tmBegin * 60 + dusk && tm.Hour * 60 + tm.Minute <= tmEnd * 60 - dusk || tmBegin > tmEnd && (tm.Hour * 60 + tm.Minute > tmBegin * 60 + dusk || tm.Hour * 60 + tm.Minute < tmEnd * 60 - dusk)) { PWM = 1023; } } /*else if (tmBegin < tmEnd && tm.Hour > tmBegin && tm.Hour < tmEnd || tmBegin > tmEnd && (tm.Hour > tmBegin || tm.Hour < tmEnd)) { PWM = 1023; } else if (tm.Hour == tmBegin) { PWM = map(tm.Minute, 0, 59, 0, 1023); } else if (tm.Hour == tmEnd) { PWM = map(59 - tm.Minute, 0, 59, 0, 1023); }*/ else { PWM = 0; } analogWrite(PWMpin, PWM); Serial.print(" "); Serial.println(PWM); server.handleClient(); if (now() % 2 != lastTimeLCD) { lastTimeLCD = now() % 2; sensors.requestTemperatures(); if (digitalRead(WiFimodeButton) == LOW && lastButtonStatus == HIGH && WiFi.status() == WL_CONNECTED) { screenCount++; } lastButtonStatus = digitalRead(WiFimodeButton); temp = sensors.getTempCByIndex(0); if (tempAlarm && (temp < (float)tempMin || temp > (float)tempMax)) { digitalWrite(TempAlarmPin, HIGH); } else { digitalWrite(TempAlarmPin, LOW); } if (tm.Hour >= pumpBegin && tm.Hour < pumpEnd) { digitalWrite(PumpPin, HIGH); } else { digitalWrite(PumpPin, LOW); } lcd.clear(); switch (screenCount % 2) { case 0: lcd.print(current_time(1)); lcd.setCursor(0, 1); lcd.print(temp, 1); lcd.print("C Day"); lcd.print(tmBegin); lcd.print("-"); lcd.print(tmEnd); break; case 1: lcd.print(WiFi.localIP().toString()); break; } } } void handleRoot() { int i; if (server.hasArg("begin") && server.arg("begin").toInt() >= 0 && server.arg("begin").toInt() <= 24 && server.hasArg("end") && server.arg("end").toInt() >= 0 && server.arg("end").toInt() <= 24 && server.hasArg("dusk") && server.arg("dusk").toInt() >= 0 && server.arg("dusk").toInt() <= 255) { tmBegin = server.arg("begin").toInt(); tmEnd = server.arg("end").toInt(); dusk = server.arg("dusk").toInt(); EEPROM.write(0, tmBegin); EEPROM.write(1, tmEnd); EEPROM.write(2, dusk); if (server.hasArg("pumpBegin") && server.arg("pumpBegin").toInt() >= 0 && server.arg("pumpBegin").toInt() <= 24 && server.hasArg("pumpEnd") && server.arg("pumpEnd").toInt() >= 0 && server.arg("pumpEnd").toInt() <= 24) { pumpBegin = server.arg("pumpBegin").toInt(); pumpEnd = server.arg("pumpEnd").toInt(); EEPROM.write(3, pumpBegin); EEPROM.write(4, pumpEnd); } // if (server.arg("tempMin").toInt() >= 0 && server.arg("tempMin").toInt() <= 100 && server.hasArg("tempMax") && server.arg("tempMax").toInt() >= 0 && server.arg("tempMax").toInt() <= 100 && server.arg("tempMax").toInt() > server.arg("tempMin").toInt()) if (server.arg("tempMin").toInt() >= 0 && server.arg("tempMin").toInt() <= 100 && server.arg("tempMax").toInt() >= 0 && server.arg("tempMax").toInt() <= 100 && server.arg("tempMax").toInt() > server.arg("tempMin").toInt()) { tempAlarm = server.arg("tempAlarm").toInt(); tempMin = server.arg("tempMin").toInt(); tempMax = server.arg("tempMax").toInt(); EEPROM.write(5, tempAlarm); EEPROM.write(6, tempMin); EEPROM.write(7, tempMax); } EEPROM.commit(); } if (server.hasArg("SSID") && server.arg("SSID") != "" && server.hasArg("wifipass") && server.arg("wifipass") != "") { for (i = 0; i < server.arg("SSID").length(); i++) { EEPROM.write(i + 100, server.arg("SSID")[i]); } i += 100; EEPROM.write(i, NULL); for (i = 0; i < server.arg("wifipass").length(); i++) { EEPROM.write(i + 101 + server.arg("SSID").length(), server.arg("wifipass")[i]); } //i += 3 + server.arg("wifipass").length(); EEPROM.write(server.arg("SSID").length() + server.arg("wifipass").length() + 101, NULL); EEPROM.commit(); ESP.restart(); } String content = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><title>Aquarium controller</title></head><body>"; /* for (i = 2; i < 100; i++) { if (EEPROM.read(i) == 5) { break; } content += char(EEPROM.read(i)); } content += ";"; for (int ii = i + 1; ii < 150; ii++) { if (EEPROM.read(ii) == 5) { break; } content += char(EEPROM.read(ii)); } */ content += "<DIV style=\"display:table; font-size: large; border-style: solid;\">Current time: <span id=\"time\"></span><BR>Temperature: <span id=\"temp\"></span>°C<BR>PWM: <span id=\"PWM\"></span>%<br>"; // content += "<div id=\"refresh\"> </div><script>var myVar = setInterval(myTimer, 1000); function myTimer() {var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById(\"refresh\").innerHTML = this.responseText;} }; xhttp.open(\"GET\", \"sensors\", true); xhttp.send();}</script>"; content += "\"Artificial day\" from " + String((int)tmBegin) + ":00 to " + String((int)tmEnd) + ":00. Dusk duration: " + String((int)dusk) + " minutes.</DIV>"; content += "<script>myTimer();var myVar = setInterval(myTimer, 1000);function myTimer() {var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var str = this.responseText; var values = str.split(\";\"); document.getElementById(\"time\").innerHTML = values[0]; document.getElementById(\"temp\").innerHTML = values[1]; document.getElementById(\"PWM\").innerHTML = values[2]; } }; xhttp.open(\"GET\", \"sensors\", true); xhttp.send();} var i = 0;</script>"; content += "<form action='/' method='POST'>Begin: <input type='number' pattern='[0-9]' maxlength='2' size='2' name='begin' value='" + String((int)tmBegin) + "'> UTC<br>"; content += "End: <input type='number' pattern='[0-9]' maxlength='2' size='2' name='end' value='" + String((int)tmEnd) + "'> UTC<br>"; content += "Dusk: <input type='number' pattern='[0-9]' maxlength='2' size='2' name='dusk' value='" + String((int)dusk) + "'> minutes<br>"; content += "Air pump operates between <input type='number' pattern='[0-9]' maxlength='2' size='2' name='pumpBegin' value='" + String((int)pumpBegin) + "'> and <input type='number' pattern='[0-9]' maxlength='2' size='2' name='pumpEnd' value='" + String((int)pumpEnd) + "'><br>"; content += "<input type=\"checkbox\" name=\"tempAlarm\" value=\"1\""; if (tempAlarm) { content += " checked"; } content += ">TempAlarm <input type='number' pattern='[0-9]' maxlength='2' size='2' name='tempMin' value='" + String((int)tempMin) + "'> - <input type='number' pattern='[0-9]' maxlength='2' size='2' name='tempMax' value='" + String((int)tempMax) + "'><br>"; content += "<a href=\"\" onclick=\"i = i+1; if(i % 2 == 0) {wifi.style.display = 'none';} else {wifi.style.display = 'block';} return false;\">Change WiFi settings</a>"; content += "<div id=\"wifi\" style=\"display:none;\">Home WiFi Network Name (SSID): <input type='text' name='SSID' value='" + String(ssid) + "'><br>"; content += "Password: <input type='password' name='wifipass'></div><br>"; content += "<input type='submit' value='Submit'></form> <br>"; content += "</body></html>"; server.send(200, "text/html", content); } void handleSensors() { String content = current_time(0) + ";" + String(temp) + ";" + String(100.0 * PWM / 1023.0); server.send(200, "text/html", content); } String current_time(bool shortstring) { breakTime(now(), tm); String timeStr; if (!shortstring) { if (tm.Year > 0) { tm.Year -= 30; } else { tm.Year = 0; } } timeStr = String(tm.Day) + "." + String(tm.Month); if (!shortstring) { timeStr += "." + String(tm.Year); } timeStr += " " + String(tm.Hour) + ":"; if (tm.Minute < 10) { timeStr += "0"; } timeStr += String(tm.Minute) + ":"; if (tm.Second < 10) { timeStr += "0"; } timeStr += String(tm.Second) + "UTC"; return timeStr; } // send an NTP request to the time server at the given address //https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/NTPClient/NTPClient.ino unsigned long sendNTPpacket(IPAddress& address) { //Serial.println("sending NTP packet..."); // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: udp.beginPacket(address, 123); //NTP requests are to port 123 udp.write(packetBuffer, NTP_PACKET_SIZE); udp.endPacket(); } time_t unix_time() { if (WiFi.status() == WL_CONNECTED && ((unsigned long)(millis() - last_NTP_check) > 15000 || last_time_sync == 0)) { last_NTP_check = millis(); //get a random server from the pool WiFi.hostByName(ntpServerName, timeServerIP); sendNTPpacket(timeServerIP); // send an NTP packet to a time server // wait to see if a reply is available delay(1000); int cb = udp.parsePacket(); if (!cb) { //Serial.println("no packet yet"); } else { //Serial.print("packet received, length="); //Serial.println(cb); // We've received a packet, read the data from it udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer //the timestamp starts at byte 40 of the received packet and is four bytes, // or two words, long. First, esxtract the two words: unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); // combine the four bytes (two words) into a long integer // this is NTP time (seconds since Jan 1 1900): unsigned long secsSince1900 = highWord << 16 | lowWord; //Serial.print("Seconds since Jan 1 1900 = " ); //Serial.println(secsSince1900); // now convert NTP time into everyday time: Serial.println("Unix time sync "); // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: const unsigned long seventyYears = 2208988800UL; // subtract seventy years: unsigned long epoch = secsSince1900 - seventyYears; // print Unix time: if (epoch > 1512876158) { return epoch + 2; } else { return 0; } } } } Składniki: ESP8266 wodoodporny termometr oparty na układzie DS18B20 wyświetlacz LCD 2x16 z konwerterem I2C LCM1602 przetwornica step-down LM2596 zapewniająca zasilanie 3.3V dla elektroniki, konwertując z 12V na wejściu N-kanałowy tranzystor MOSFET do sterowania PWM oświetleniem LED buzzer
-
Wielokanałowy sterownik akwarium jako element automatyki domowej
Karolus opublikował temat w Projekty - DIY
Witajcie Chciałbym przedstawić tu swój projekt Sterownika do akwarium pracującego w systemie automatyki domowej opartej na Domoticzu. Pomysł i historia projektu Ponad rok temu wpadliśmy z żoną na pomysł założenia akwarium, pomysł jak pomysł, ale im więcej czytałem tym więcej kombinowałem, a jako że jestem elektronikiem to i pomysłów przybywało mi z każdym dniem 🙂 Pierwsza próba budowy sterownika polegała na wykorzystaniu Arduino nano i stworzeniu samodzielnego układu pracującego lokalnie. Tyle że już w trakcie pisania kodu stwierdziłem że sterowanie sterowaniem, ale fajnie by było móc zrobić coś zdalnie, a przede wszystkim móc sprawdzić zdalnie co dzieje się w akwarium (temperatura, stan lamp itp). Tak zaczęła się moja przygoda z Domoticzem i szeroko pojętym IoT. Opis projektu Sam sterownik zbudowany jest na Arduino Pro mini, natomiast do komunikacji z serwerem wykorzystana jest platforma Mysensors zbudowana na modułach NRF24L01. Jak wcześniej pisałem sterownik pracuje w systemie mojej domowej automatyki opartej o serwer Domoticza, pracujący na RaspberryPi 3B. Na chwile obecną sterownik realizuje następujące funkcje: cztery kanały ON/OFF (przekaźniki 10A 250V), sterowanie: grzałka, filtr, falownik, chłodzenie (wentylatory w pokrywie). Z uwagi na bezpieczeństwo mieszkańców akwarium filtr podpięty jest pod styki NC przekaźnika, tzn, domyślnie jest on włączony, a możemy go wyłączyć. 16 kanałów PWM (z tranzystorami IRF520 jako elementy wykonawcze), sterowanie lampami LED, ściemnianie podświetlenia wyświetlacza, sterownie pracą falownika, rezerwa. funkcja "Karmienie" i "Serwis", załączane z lokalnej klawiatury, pierwsza umożliwia czasowe (5min) wyłączenie filtra, falownika i wentylatorów, druga umożliwia wyłączenie trybu automatyki i przejście w tryb serwisowy (przydatne przy pracach porządkowych w zbiorniku). pomiar temperatury w akwarium (czujniki DS18B20), układ zrobiony jest tak że może obsłużyć nieskończenie wiele termometrów, w praktyce w mniejszym zbiorniku mam 2 w większym 3 i to wystarcza. Dodatkowo można wybrać czy automatyka sterowania grzaniem/chłodzeniem korzysta z jednego z termometrów czy z wartości średniej wszystkich wskazań. zabezpieczenie przed przegrzaniem i wychłodzeniem wody (bezwarunkowe wyłączenie/włączenie grzania/chłodzenia w temperaturach skrajnych) pomiar temperatury zewnętrznej pomiar poziomu oświetlenia zewnętrznego Schemat blokowy sterownika przedstawiam na poniższym rysunku Jak widać większość układów pracuje na magistrali I2C, sprawia to że całość jest projektem rozwojowym, a wszelkie możliwe dodatkowe funkcje ograniczone są tylko wyobraźnią i.... pojemnością pamięci wykorzystanego Arduino 😉 Kanały PWM zrealizowane są na module PCA 9685, a jako elementy wykonawcze służą tranzystory IRF520 (oczywiście można zastosować inne MOSFETy), moduł z przekaźnikami podłączony jest do magistrali I2C poprzez expander PCF8574. Zarządzanie sterownikiem Podstawowe sterowanie pracą sterownika możliwe jest z lokalnej klawiatury, natomiast i informacje o pracy sterownika (temperatura, czas, poziom światła poszczególnych kanałów LED, stan urządzeń wykonawczych itp) wyświetlane są bezpośrednio na wyświetlaczu LCD 20x4. Pełne sterownie możliwe jest poprzez serwer Domoticza, za pośrednictwem strony www (poniżej przykładowy zrzut ekranu) i/lub aplikacji na androida. Teraz czas na kilka zdjęć elementów sterownika: Moduł arduino z przetwornica 3,3V, expanderem PCF 8574 i modułem NRF24L01 zmontowane są na płytce uniwersalnej, która osadzona jest na module przekaźników tworząc "kanapkę" Widok "kanapki" płytki uniwersalnej z arduino i modułu z przekaźnikami Moduł wykonawczy na tranzystorach IRF (sześć kanałów do sterowania lampami LED) Moduł przekaźników Wyświetlacz LCD Problemy napotkane przy realizacji projektu konieczne było wymuszenie niższej częstotliwości pracy platformy mysensors z powodu zegara jakim taktowane jest zastosowane arduino niestabilna praca modułów NRF, konieczne było dodanie elektrolitów (dałem 330u bo takie miałem :)) jak najbliżej pinów zasilających modułu (lutowałem bezpośrednio na tych pinach) okresowe zawieszanie i/lub przekłamanie odczytu temperatury z termometrów DS18B20, nie doszedłem przyczyny, wiec dodałem możliwość zdalnego resetu arduino (wiem że to tylko zaleczenie problemu a nie rozwiązanie, ale tymczasowo zdaje egzamin) zbyt małą pamięć programu zastosowanego modułu arduino, zastanawiam się czy nie przejść na ESP8266 z EasyESP konieczność zbudowania obudowy 😉