Happy Birds - karmnik IoT z systemem obserwacji ptaków

Happy Birds - karmnik IoT z systemem obserwacji ptaków

Obserwowanie ptaków to bardzo popularne hobby na całym świecie. Tak jak wiele innych dziedzin, także ono korzysta z postępów w technologii. Lornetki, aparaty, cyfrowe kamery – ornitolodzy amatorzy chętnie sięgają po takie wyposażenie. W ostatnim czasie do ich asortymentu doszły nowoczesne systemy IoT.

Autorowi prezentowanego projektu przyświecała idea fotografowania ptaków w przydomowych ogrodach i udostępnianie ich zdjęć w sieci. Artykuł szczegółowo opisuje czynności i komponenty potrzebne do zbudowania własnego karmnika dla ptaków i robienia ładnych zdjęć przebywających w nim gości. Zdjęcia robione za pomocą Happy Birds dostępne są natychmiast na smartfonie – można je udostępniać i rozmawiać z innymi entuzjastami ptaków za pośrednictwem prostej sieci społecznościowej Happy Bird.

Rysunek 1. Opis elementów konstrukcji karmnika

Dwa główne elementy tego projektu to karmnik dla ptaków i aplikacja. Obudowa karmnika dla ptaków (rysunek 1) jest wykonana w technologii druku 3D z 3 różnych materiałów. Szczegóły tej konstrukcji zostaną dokładniej omówione w dalszej części artykułu. System elektroniczny składa się z następujących części:

  • czujnik do wykrywania ptaków, który znajduje się w centralnym bloku,
  • przednia kamera skierowana w stronę obszarów karmienia,
  • elektronika sterująca systemem,
  • dwa wyjmowane dozowniki żywności umieszczone po bokach,
  • panele słoneczne na dachu, które służą do zasilania elektroniki.

Karmnik można zamocować na gałęzi drzewa, przy dachu lub po prostu postawić na podporze na rozsądnej wysokości (ze względu na zagrożenie, jakim są koty). Gdy ptak wyląduje na karmniku, czujnik wykrywa jego obecność i uruchamia kamerę w celu wykonania szybkiego zdjęcia gościa. Następnie zdjęcie jest wysyłane do aplikacji Happy Birds dzięki lokalnej sieci Wi-Fi i Internetowi. Nie trzeba być fizycznie obecnym, aby zrobić zdjęcie. Zdjęcia są przechowywane w bazie danych Firebase w chmurze Google. Można zapisać je także w swojej bibliotece zdjęć, udostępnić na Instagramie lub WhatsApp i udostępnić w specjalnie przygotowanej sieci społecznościowej Happy Birds i poprosić o pomoc, jeśli np. mamy problem z identyfikacją konkretnego gatunku.

Rysunek 2. Uproszczona zasada działania systemu

Aplikacja towarzysząca Happy Birds jest dostępna w sklepach Apple i Google (działa na smartfonach i tabletach) i jest w pełni darmowa.

Potrzebne elementy

System został zbudowany z następujących komponentów:

  • moduł ESP32-CAM z zewnętrzną anteną,
  • kamera z sensorem OV2640 z przewodem o długości 75 mm,
  • sensor odległości Sharp GP2 Y0D810 Z0 F,
  • moduł ładowarki baterii na układzie TP 4056,
  • pakiet ogniw litowo-jonowych o pojemności 4800 mAh i napięciu 3,7 V,
  • oporniki 1/4 W: 10 kΩ, 330 kΩ oraz 1 MΩ,
  • kondensatory: 2×100 nF, 1×22 μF (elektrolityczny),
  • dwa ogniwa fotowoltaiczne 6 V, 100 mA, o rozmiarze 90×60 mm (lub 6 V, 583 mA o rozmiarze 135×165 mm),
  • dwa złącza JST XH-3 A, dwa złącza JST XH-3Y,
  • złącze JST XH-2A, złącze JST XH-2Y,
  • po dwa męskie i żeńskie złącza Mini Micro JST 2 Pin z przewodem o długości 80 mm,
  • dwa 8-stykowe gniazda,
  • micro switche (przyciski) DIP 4 P 6×6,
  • czarny i czerwony przewód,
  • płytka drukowana (dokumentacja PCB znajduje się na stronie z projektem – link na końcu artykułu),
  • przełącznik kołyskowy,
  • dwie gumowe podkładki,
  • samogwintujące wkręty M2,
  • filamenty do drukarki 3D – drewnopodobny filament PLA, czarny filament z PETG lub PLA, przezroczysty filament z PLA.
Rysunek 3. Moduły potrzebne do realizacji systemu

Komponenty instalowane w karmniku

Technologia zastosowana w karmniku nie jest skomplikowana i bazuje głównie na module ESP32-CAM. Jest to kompaktowy i bogaty w funkcje moduł bazujący na popularnym SoM ESP32, który w ramach tego rozwiązania został przygotowany do integracji z kamerą. Główne elementy modułu, używane w systemie, obejmują:

  • mikrokontroler ESP32 – 32-bitowy procesor małej mocy z 4 Mb pamięci RAM, Wi-Fi oraz Bluetooth,
  • kamera OV2640 z obiektywem o kącie widzenia 160°, podłączona kablem o długości 75 mm do płytki ESP32-CAM,
  • zewnętrzna antena Wi-Fi, podłączona do ESP32-CAM, aby zwiększyć zasięg komunikacji,
  • wbudowana na płytce lampa błyskowa oraz czytnik kart micro SD nie są używane w tym projekcie.

Przez większość czasu ESP32 jest w trybie głębokiego uśpienia, aby minimalizować zużycie energii elektrycznej w systemie. System w karmnika używa następujących wyprowadzeń modułu:

  • GPIO 13: wybudzenie z czujnika zasięgu,
  • GPIO 14: monitorowanie napięcia akumulatora,
  • GPIO 3: przycisk do konfiguracji płytki,
  • Zasilanie dostarczane jest przez wyprowadzenie 5 V.

Oprócz modułu ESP32-CAM i kamery, w systemie zastosowano także inne elementy, takie jak:

  • Czujnik zbliżeniowy o zasięgu 10 cm firmy Sharp – gdy coś zostanie wykryte w odległości od 2 do 10 cm od sensora, wówczas wyprowadzenie wyjściowe przyjmuje wartość zera logicznego i budzi w ten sposób ESP32 z trybu głębokiego uśpienia. Wybudzony moduł wykonuje zdjęcie i je przetwarza. Autor próbował zastosować również inne czujniki na podczerwień, ale nie sprawdziły się tak dobrze – klasyczny sensor PIR wykrywa wszelkie zmiany nawet na dużej odległości, poruszające się chmury czy gałęzie drzew poruszane przez wiatr i nie nadaje się do tego zadania. Czujnik Sharp wykrywa tylko ruch w odległości 2...10 cm przed sensorem. Jedyną jego wadą jest stały pobór prądu na poziomie 5 mA;
  • Zasilanie systemu zapewnia akumulator litowo-jonowy o pojemności 4800 mAh, który jest ładowany przez panele słoneczne. Czasami musi być doładowany np. przez USB, jednak znacznie wydłuża czas pracy systemu. Zastosowany moduł ładowarki bazującej na TP4056 zabezpiecza akumulator przed przeładowaniem i nadmiernym rozładowanie ogniw;
  • Włącznik zasilania, który odcina system od baterii;
  • Przycisk chwilowy, który jest używany do konfiguracji modułu za pomocą smartfona i specjalnie przygotowanej aplikacji.

Energia była jednym z najtrudniejszych problemów przy projektowaniu tego systemu, ponieważ płytka ESP32-CAM wymaga napięcia wejściowego nie niższego niż 3,7 V: oznacza to, że napięcie akumulatora nie może spaść poniżej 70...75% jego maksymalnej wartości.

Dlatego też konieczne jest doładowywanie ogniwa, na przykład z paneli PV. W artykule, opisującym ten proces, autor proponuje dwie opcje rozmiaru paneli słonecznych i dachu, który je trzyma:

  • Opcja 1 – panele o wymiarach 90×60 mm, które dostarczają po 100 mA każdy. Dla autora (mieszkającego w okolicach Paryża), wiosną i latem dostarczały one pod dostatkiem energii i były w stanie doładowywać na bieżąco ogniwo, ale zimą i jesienią słońca było za mało, aby utrzymać układ w ciągłej gotowości.
  • Opcja 2 – panele 135×165 mm, które dostarczają po 583 mA każdy. To wydaje się dostateczną opcją dla niemalże dowolnego klimatu.

Budowa układu

Płytka drukowana

Można zbudować własną płytkę elektroniczną np. na płytce uniwersalnej lub użyć dołączonego do projektu pliku Gerber, który zawiera pełny opis układu i który można wyprodukować za około 2 dolary. Płytka zaprojektowana została za pomocą EasyEDA, bardzo wszechstronnego narzędzia z bezpośrednimi linkami do producentów płytek drukowanych.

Fotografia 1. Wygląd płytki łączącej komponenty systemu

Należy zwrócić uwagę, że płytka ma elementy lutowane po obu stronach:

  • strona TOP ma wszystkie elementy pasywne (rezystory, kondensatory), przycisk, złącze zasilania włącznik, złącze ładowarki (JST XH-2A) i złącze czujnika (JST-3A),
  • strona BOTTOM ma dwa 8-stykowe żeńskie gniazda na goldpiny, w których zostanie umieszczony moduł ESP32-CAM.
Rysunek 4. Schemat ideowy systemu

Zasilanie i ładowanie

Moduł ładowarki i zasilania powinien być podłączony do reszty systemu. Autor rekomenduje zastosowanie czarnych i czerwonych przewodów, aby nie było niejasności w połączeniu i ryzyka zwarcia baterii (co wiąże się z ryzykiem pożaru lub wybuchu ogniwa).

Do podłączenia zastosowano dwie pary złączy micro JST PH 2.0 do podłączenia wejść plus i minus modułu do paneli słonecznych. Złącza te można kupić wraz z przewodami, więc wystarczy przylutować przewody męskie do paneli słonecznych i żeńskie do wejść plus i minus modułu. Należy upewnić się, czy czerwony (plus) i czarny (minus) podłączono do odpowiednich miejsc na PCB modułu ładowarki.

Następnie do modułu dołączyć można baterię. Wystarczy przylutować dwa przewody pomiędzy wyprowadzeniami B+ i B– na module i dołączyć je do uchwytu baterii (znaki + i – na uchwycie baterii mogą być mało widoczne). Finalnie należy dwa przewody, podłączone do złącza JST XH-2Y, podłączyć do złącza przeznaczonego dla baterii na głównej płytce elektronicznej systemu. Czerwone i czarne przewody ze złączami JST PH 2.0 należy zlutować także z ogniwami fotowoltaicznymi. Pozwoli to podłączyć je do płytki modułu ładowarki.

Sensory

Do podłączenia sensora odległości potrzebne są zaledwie trzy przewody. Dobrze jest zastosować trzy różne kolory, najlepiej zachowując czarny i czerwony dla masy i zasilania. Z jednej strony przewodu instalowane jest żeńskie złącze Dupont, a z drugiej strony przewód zakończony jest złączem JST XH-3Y. Łącząc ze sobą przewody i złącza, należy zwrócić uwagę na kolejność sygnałów po obu stronach – ich kolejność jest różna.

Moduł ESP32-CAM

W pierwszej kolejności do modułu należy podłączyć antenę zewnętrzną. Ta operacja może być nieco trudna, ale korzystając z poniższego poradnika, można to zrobić z łatwością. Zasadniczo należy zmienić pozycję malutkiego rezystora wybierającego antenę z pozycji wybierającej antenę na płytce na pozycję łączącą z gniazdem zewnętrznej anteny.

Fotografia 2. Modyfikacja w obwodzie anteny modułu

Można to zrobić w 5 krokach:

  1. Należy nałożyć trochę topnika na rezystor, gdy lutownica się nagrzewa. Oczywiście biorąc pod uwagę wielkość rezystora, dobrze jest mieć dużą lampę powiększającą;
  2. Można teraz położyć lutownicę na rezystorze, a gdy spoiwo się stopi, należy usunąć rezystor grotem lutownicy lub pęsetą;
  3. Ponownie umieszczamy trochę topnika teraz na padach odpowiadających konfiguracji anteny zewnętrznej i lutujemy, dodając odrobinę cyny;
  4. Odcinamy krótki kawałek cienkiego drutu i umieszczamy go między wyżej wymienionymi punktami za pomocą pęsety, jednocześnie umieszczając tam lutownicę. Spoiwo powinno się stopić i utrzymać kawałek drutu na miejscu. Płytka zmodyfikowana w ten sposób została pokazana na fotografii 2;
  5. Finalnie, można przyciąć drut – modyfikacja jest gotowa.

Ostatecznie można kontynuować pracę z anteną wewnętrzną, jednak zasięg może nie być wystarczający do najbardziej ekstremalnych implementacji karmnika. Po podłączeniu zewnętrznej anteny do modułu należy ostrożnie dołączyć kamerę OV2640 za pomocą płaskiej, specjalnie przygotowanej taśmy.

Uwaga dotycząca złączy

Poszczególne złącza JST nie są tak naprawdę obowiązkowe, można je zastąpić dowolnymi innymi złączami, które łatwiej znaleźć, już z zarobionymi przewodami, bez konieczności ich zaciskania. Można nawet przylutować przewody bezpośrednio do płytek bez żadnego złącza, ale wtedy traci się możliwość demontażu układu.

Autor rekomenduje dodatkowe izolowanie włącznika i kabli z baterii z zastosowaniem koszulek termokurczliwych, które uchronią przewody przed wszelkimi zwarciami czy kontaktami z innymi elementami. Zwarcie na baterii LiPo może wywołać pożar lub jej wybuch.

Oprogramowanie karmnika IoT

Do opracowania oprogramowania w języku C zastosowano środowisko Arduino IDE. Musi być ono skonfigurowane do programowania modułu ESP32-CAM od firmy AI-Thinker. Dodatkowo potrzebny będzie kabel z układem FTDI – konwerter USB-UART, do programowania. Po zaopatrzeniu się w te elementy można przygotować środowisko dla oprogramowania Bird Feeder.

Na komputerze należy utworzyć folder o nazwie Bird Feeder i skopiować do niego wszystkie pliki dołączone do projektu (link na końcu artykułu). Główny pik szkicu to plik BirdFeeder.ino (listing 1).

Listing 1.

<pre>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;Arduino.h&#34;</font>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;FS.h&#34;</font> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#434f54">&#47;&#47; Karta SD</font>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;SD_MMC.h&#34;</font> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#434f54">&#47;&#47; Karta SD</font>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;time.h&#34;</font>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;Battery.h&#34;</font>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;Camera.h&#34;</font>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;Firebase.h&#34;</font>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;Registers.h&#34;</font>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;Wifi.h&#34;</font>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;Eeprom.h&#34;</font>
<font color="#5e6d03">#include</font> <font color="#005c5f">&#34;Setup.h&#34;</font>
<font color="#5e6d03">#include</font> <font color="#434f54">&lt;</font><font color="#000000">HTTPClient</font><font color="#434f54">.</font><font color="#000000">h</font><font color="#434f54">&gt;</font>
<font color="#5e6d03">#include</font> <font color="#434f54">&lt;</font><font color="#000000">ESPAsyncWebServer</font><font color="#434f54">.</font><font color="#000000">h</font><font color="#434f54">&gt;</font>

<font color="#00979c">String</font> <font color="#d35400">version</font> <font color="#434f54">=</font> <font color="#005c5f">&#34;1.4.7&#34;</font><font color="#000000">;</font>

<font color="#434f54">&#47;&#47; Definicja wykorzystanych pinów</font>
<font color="#5e6d03">#define</font> <font color="#000000">PIN_WAKEUP</font> &nbsp;<font color="#000000">GPIO_NUM_13</font> &nbsp;&nbsp;<font color="#434f54">&#47;&#47; Pin do wybudzania (sensor)</font>
<font color="#5e6d03">#define</font> <font color="#000000">PIN_MODE</font> &nbsp;&nbsp;&nbsp;<font color="#000000">GPIO_NUM_3</font> &nbsp;&nbsp;&nbsp;<font color="#434f54">&#47;&#47; Pin przełącznika do trybu konfiguracyjnego</font>

<font color="#434f54">&#47;&#47; Dane dla serwera czasu</font>
<font color="#00979c">const</font> <font color="#00979c">char</font><font color="#434f54">*</font> <font color="#000000">ntpServer</font> <font color="#434f54">=</font> <font color="#005c5f">&#34;pool.ntp.org&#34;</font><font color="#000000">;</font>
<font color="#00979c">const</font> <font color="#00979c">long</font> &nbsp;<font color="#000000">gmtOffset_sec</font> <font color="#434f54">=</font> <font color="#000000">0</font><font color="#000000">;</font>
<font color="#00979c">const</font> <font color="#00979c">int</font> &nbsp;&nbsp;<font color="#000000">daylightOffset_sec</font> <font color="#434f54">=</font> <font color="#000000">3600</font><font color="#000000">;</font>

<font color="#434f54">&#47;&#47; Dane dla bazy Firebase database</font>
<font color="#00979c">String</font> <font color="#000000">imageFile</font> <font color="#434f54">=</font> <font color="#005c5f">&#34;&#34;</font><font color="#000000">;</font>
<font color="#00979c">String</font> <font color="#000000">imageURL</font> <font color="#434f54">=</font> <font color="#005c5f">&#34;&#34;</font><font color="#000000">;</font>
<font color="#00979c">String</font> <font color="#000000">DateTime</font> <font color="#434f54">=</font> <font color="#005c5f">&#34;&#34;</font><font color="#000000">;</font>

<font color="#434f54">&#47;&#47; Bufor obrazu z kamery</font>
<font color="#000000">camera_fb_t</font> <font color="#434f54">*</font><font color="#000000">imageBuffer</font> <font color="#434f54">=</font> <font color="#00979c">NULL</font><font color="#000000">;</font>

<font color="#00979c">void</font> <font color="#5e6d03">setup</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">{</font>
&nbsp;<font color="#000000">Registers_disableBrownoutDetector</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">begin</font><font color="#000000">(</font><font color="#000000">115200</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;BirdFeeder software version &#34;</font> <font color="#434f54">+</font> <font color="#d35400">version</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#000000">Registers_saveRegB</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Trzeba wykonać przed odczytem wartości analogowej</font>
&nbsp;<font color="#000000">SD_MMC</font><font color="#434f54">.</font><font color="#d35400">begin</font><font color="#000000">(</font><font color="#005c5f">&#34;&#47;sdcard&#34;</font><font color="#434f54">,</font> <font color="#00979c">true</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Wyłączenie karty SD</font>
&nbsp;<font color="#000000">WRITE_PERI_REG</font><font color="#000000">(</font><font color="#000000">SENS_SAR_READ_CTRL2_REG</font><font color="#434f54">,</font> <font color="#000000">reg_b</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#00979c">float</font> <font color="#000000">batteryValue</font> <font color="#434f54">=</font> <font color="#000000">Battery_getValue</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Odczyt napięcia baterii</font>
&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Battery value = &#34;</font> <font color="#434f54">+</font><font color="#00979c">String</font><font color="#000000">(</font><font color="#000000">batteryValue</font><font color="#000000">)</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#434f54">&#47;&#47; Wyłączenie diody Flash</font>
&nbsp;<font color="#00979c">int</font> <font color="#000000">canal</font> <font color="#434f54">=</font> <font color="#000000">7</font><font color="#000000">;</font>
&nbsp;<font color="#000000">ledcSetup</font><font color="#000000">(</font><font color="#000000">canal</font><font color="#434f54">,</font> <font color="#000000">5000</font><font color="#434f54">,</font> <font color="#000000">8</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#000000">ledcAttachPin</font><font color="#000000">(</font><font color="#000000">GPIO_NUM_4</font><font color="#434f54">,</font> <font color="#000000">canal</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#000000">ledcWrite</font><font color="#000000">(</font><font color="#000000">canal</font><font color="#434f54">,</font> <font color="#000000">0</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#000000">Eeprom_init</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Inicjalizacja pamięci EEPROM</font>
&nbsp;<font color="#434f54">&#47;&#47; Wykrycie trybu, w jakim uruchamiany jest układ</font>
&nbsp;<font color="#d35400">pinMode</font><font color="#000000">(</font><font color="#000000">PIN_MODE</font><font color="#434f54">,</font> <font color="#00979c">INPUT</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#00979c">boolean</font> <font color="#000000">Mode</font> <font color="#434f54">=</font> <font color="#d35400">digitalRead</font><font color="#000000">(</font><font color="#000000">PIN_MODE</font><font color="#000000">)</font> <font color="#000000">;</font>
&nbsp;<font color="#5e6d03">if</font> <font color="#000000">(</font><font color="#000000">Mode</font> <font color="#434f54">==</font> <font color="#00979c">false</font><font color="#000000">)</font> <font color="#000000">{</font> <font color="#434f54">&#47;&#47; Pin dołączony do masy przyciskiem</font>
&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Entering setup mode&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">Setup_getCredentials</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> &nbsp;&nbsp;
&nbsp;<font color="#000000">}</font> <font color="#5e6d03">else</font> <font color="#000000">{</font> <font color="#434f54">&#47;&#47; Tryb normalny</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">Camera_init</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">imageBuffer</font> <font color="#434f54">=</font> <font color="#000000">Camera_shoot</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<font color="#5e6d03">if</font> <font color="#000000">(</font><font color="#434f54">!</font><font color="#000000">imageBuffer</font><font color="#000000">)</font> <font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Camera capture failed&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#d35400">delay</font><font color="#000000">(</font><font color="#000000">500</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">gotoSleep</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">}</font> <font color="#5e6d03">else</font> <font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Camera capture successful&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">}</font> &nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<font color="#000000">Registers_restoreRegB</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Odzyskanie rejestru B, aby uruchomić Wi-Fi</font>
&nbsp;&nbsp;&nbsp;<font color="#00979c">uint8_t</font> <font color="#000000">wifiConnectionStatus</font> <font color="#434f54">=</font> <font color="#000000">Wifi_start</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Inicjalizacja Wi-Fi</font>
&nbsp;&nbsp;&nbsp;<font color="#5e6d03">if</font> <font color="#000000">(</font><font color="#000000">wifiConnectionStatus</font> <font color="#434f54">!=</font> <font color="#000000">WL_CONNECTED</font><font color="#000000">)</font> <font color="#000000">gotoSleep</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<font color="#00979c">int</font> <font color="#000000">wifiStrength</font> <font color="#434f54">=</font> <font color="#000000">Wifi_getStrength</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> &nbsp;
&nbsp;&nbsp;&nbsp;<font color="#434f54">&#47;&#47; Inicjalizacja i pozyskanie czasu, au przygotować unikatową nazwę pliku</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">DateTime</font> <font color="#434f54">=</font> <font color="#000000">getLocalTime</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;UTC time = &#34;</font> <font color="#434f54">+</font> <font color="#000000">DateTime</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">imageFile</font> <font color="#434f54">=</font> <font color="#000000">filePath</font> <font color="#434f54">+</font> <font color="#005c5f">&#34;bird-&#34;</font> <font color="#434f54">+</font> <font color="#000000">DateTime</font> <font color="#434f54">+</font> <font color="#005c5f">&#34;.jpeg&#34;</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">Firebase_init</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Dołączenie do backendu bazy danych Firebase</font>
&nbsp;&nbsp;&nbsp;<font color="#5e6d03">if</font> <font color="#000000">(</font><font color="#000000">Firebase_isReady</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">)</font> <font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Firebase ready&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">}</font> <font color="#5e6d03">else</font> <font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Firebase not ready, timeout reached, going to sleep&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">gotoSleep</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> &nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<font color="#000000">}</font>
&nbsp;&nbsp;&nbsp;<font color="#00979c">int</font> <font color="#000000">quota</font> <font color="#434f54">=</font> <font color="#000000">Firebase_getStorageQuota</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Sprawdzenie czy jest dostatecznie dużo miejsca</font>
&nbsp;&nbsp;&nbsp;<font color="#5e6d03">if</font> <font color="#000000">(</font><font color="#000000">quota</font> <font color="#434f54">&gt;</font> <font color="#000000">0</font><font color="#000000">)</font> <font color="#000000">{</font> <font color="#434f54">&#47;&#47; Zezwolenie na upload</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">quota</font><font color="#434f54">--</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">print</font><font color="#000000">(</font><font color="#005c5f">&#34;Upload photo... &#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#5e6d03">if</font> <font color="#000000">(</font><font color="#000000">Firebase_uploadImage</font><font color="#000000">(</font><font color="#000000">imageBuffer</font><font color="#434f54">,</font> <font color="#000000">DateTime</font><font color="#000000">)</font><font color="#000000">)</font> <font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">imageURL</font> <font color="#434f54">=</font> <font color="#000000">Firebase_getImageURL</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">}</font> <font color="#5e6d03">else</font> <font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">gotoSleep</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">}</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#434f54">&#47;&#47; Aktualizacja danych w bazie Firebase</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#5e6d03">if</font> <font color="#000000">(</font><font color="#000000">Firebase_isReady</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">)</font> <font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Firebase ready&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">}</font> <font color="#5e6d03">else</font> <font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Firebase not ready, timeout reached, going to sleep&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">gotoSleep</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> &nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">}</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#434f54">&#47;&#47; Przygotowanie PhotoNode z jsonem i zdjęciem</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">FirebaseJson</font> <font color="#000000">jsonPhoto</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">jsonPhoto</font><font color="#434f54">.</font><font color="#000000">add</font><font color="#000000">(</font><font color="#005c5f">&#34;ImageFile&#34;</font><font color="#434f54">,</font> <font color="#000000">imageFile</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">jsonPhoto</font><font color="#434f54">.</font><font color="#000000">add</font><font color="#000000">(</font><font color="#005c5f">&#34;ImageURL&#34;</font><font color="#434f54">,</font> <font color="#000000">imageURL</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">jsonPhoto</font><font color="#434f54">.</font><font color="#000000">add</font><font color="#000000">(</font><font color="#005c5f">&#34;Viewed&#34;</font><font color="#434f54">,</font> <font color="#00979c">false</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">Firebase_createPhotoNode</font><font color="#000000">(</font><font color="#434f54">&amp;</font><font color="#000000">jsonPhoto</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#434f54">&#47;&#47; Przygotowanie bazy danych Firebase</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#5e6d03">if</font> <font color="#000000">(</font><font color="#000000">Firebase_isReady</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">)</font> <font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Firebase ready&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">}</font> <font color="#5e6d03">else</font> <font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Firebase not ready, timeout reached, going to sleep&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">gotoSleep</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> &nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">}</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#434f54">&#47;&#47; Aktualizacja danych &nbsp;BirdFeeder</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">FirebaseJson</font> <font color="#000000">jsonBirdFeeder</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">jsonBirdFeeder</font><font color="#434f54">.</font><font color="#000000">add</font><font color="#000000">(</font><font color="#005c5f">&#34;Battery&#34;</font><font color="#434f54">,</font> <font color="#00979c">String</font><font color="#000000">(</font><font color="#000000">batteryValue</font><font color="#000000">)</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">jsonBirdFeeder</font><font color="#434f54">.</font><font color="#000000">add</font><font color="#000000">(</font><font color="#005c5f">&#34;Wifi&#34;</font><font color="#434f54">,</font> <font color="#00979c">String</font><font color="#000000">(</font><font color="#000000">wifiStrength</font><font color="#000000">)</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">jsonBirdFeeder</font><font color="#434f54">.</font><font color="#000000">add</font><font color="#000000">(</font><font color="#005c5f">&#34;Quota&#34;</font><font color="#434f54">,</font> <font color="#000000">quota</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">jsonBirdFeeder</font><font color="#434f54">.</font><font color="#000000">add</font><font color="#000000">(</font><font color="#005c5f">&#34;Version&#34;</font><font color="#434f54">,</font> <font color="#d35400">version</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000000">Firebase_updateBirdFeederNode</font><font color="#000000">(</font><font color="#434f54">&amp;</font><font color="#000000">jsonBirdFeeder</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Aktualizacja bazy danych</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">}</font>
&nbsp;&nbsp;&nbsp;<font color="#000000">gotoSleep</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Uśpienie po wykonaniu wszystkich kroków </font>
&nbsp;<font color="#000000">}</font>
<font color="#000000">}</font>

<font color="#00979c">void</font> <font color="#5e6d03">loop</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">{</font>
&nbsp;
<font color="#000000">}</font>

<font color="#00979c">void</font> <font color="#000000">gotoSleep</font><font color="#000000">(</font><font color="#000000">)</font> <font color="#000000">{</font><font color="#434f54">&#47;&#47; Głębokie uśpienie ESP32 &nbsp;</font>
&nbsp;<font color="#000000">Wifi_stop</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Zatrzymanie Wi-Fi</font>
&nbsp;<font color="#000000">esp_sleep_enable_ext0_wakeup</font><font color="#000000">(</font><font color="#000000">PIN_WAKEUP</font><font color="#434f54">,</font> <font color="#000000">0</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Włączenie linii wybudzania</font>
&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Going to sleep now&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#000000">gpio_hold_en</font><font color="#000000">(</font><font color="#000000">GPIO_NUM_4</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Zapisanie stanu GPIO4</font>
&nbsp;<font color="#d35400">delay</font><font color="#000000">(</font><font color="#000000">1000</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#000000">esp_deep_sleep_start</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font> <font color="#434f54">&#47;&#47; Uśpienie układu</font>
&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;This will never be printed&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
<font color="#000000">}</font>

<font color="#00979c">String</font> <font color="#000000">getLocalTime</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">{</font> <font color="#434f54">&#47;&#47; Pozyskuje czas z serwera NTP</font>
&nbsp;<font color="#00979c">struct</font> <font color="#000000">tm</font> <font color="#000000">timeinfo</font><font color="#000000">;</font>
&nbsp;<font color="#000000">configTime</font><font color="#000000">(</font><font color="#000000">gmtOffset_sec</font><font color="#434f54">,</font> <font color="#000000">daylightOffset_sec</font><font color="#434f54">,</font> <font color="#000000">ntpServer</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#5e6d03">if</font><font color="#000000">(</font><font color="#434f54">!</font><font color="#000000">getLocalTime</font><font color="#000000">(</font><font color="#434f54">&amp;</font><font color="#000000">timeinfo</font><font color="#000000">)</font><font color="#000000">)</font><font color="#000000">{</font>
&nbsp;&nbsp;&nbsp;<b><font color="#d35400">Serial</font></b><font color="#434f54">.</font><font color="#d35400">println</font><font color="#000000">(</font><font color="#005c5f">&#34;Failed to obtain time&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;&nbsp;&nbsp;<font color="#5e6d03">return</font> <font color="#005c5f">&#34;No time&#34;</font><font color="#000000">;</font>
&nbsp;<font color="#000000">}</font>
&nbsp;<font color="#00979c">char</font> <font color="#000000">dateTime</font><font color="#000000">[</font><font color="#000000">25</font><font color="#000000">]</font><font color="#000000">;</font>
&nbsp;<font color="#d35400">strftime</font><font color="#000000">(</font><font color="#000000">dateTime</font><font color="#434f54">,</font> <font color="#00979c">sizeof</font><font color="#000000">(</font><font color="#000000">dateTime</font><font color="#000000">)</font><font color="#434f54">,</font> <font color="#005c5f">&#34;%Y-%m-%dT%H:%M:%SZ&#34;</font><font color="#434f54">,</font> <font color="#434f54">&amp;</font><font color="#000000">timeinfo</font><font color="#000000">)</font><font color="#000000">;</font>
&nbsp;<font color="#5e6d03">return</font> <font color="#00979c">String</font><font color="#000000">(</font><font color="#000000">dateTime</font><font color="#000000">)</font><font color="#000000">;</font>
<font color="#000000">}</font>

</pre>s

Na tym etapie nie jest wymagana zmiana parametrów konfiguracyjnych w skrypcie, zostanie to wykonane później za pośrednictwem aplikacji. Należy teraz skompilować i załadować plik binarny do modułu ESP32-CAM.

Kod nie jest zorientowany obiektowo, ale ma strukturę modułową. Głównym modułem jest BirdFeeder.ino, zawiera funkcje setup() i loop(). Sekwencja setup() konfiguruje poszczególne komponenty w systemie:

  1. ESP32 budzi się z trybu wyłączenia zasilania lub ze stanu uśpienia przez sensor zbliżeniowy. W tym drugim przypadku rejestr B musi zostać przywrócony, ponieważ był używany przez Wi-Fi i pozostaje niezmieniony podczas uśpienia.
  2. Karta SD nie jest używana i musi zostać wyłączona w celu ponownego użycia powiązanych wyprowadzeń GPIO.
  3. Teraz można użyć wejścia analogowego do odczytania poziomu naładowania baterii. Pomiar nie jest zbyt dokładny, ale wystarczający do oceny poziomu naładowania akumulatora.
  4. Lampa błyskowa LED jest wyłączona i nie jest używana.
  5. Z pamięci EEPROM są odczytywane zapamiętane ustawienia.
  6. Odczytywany jest stan przycisku, jeśli jest wciśnięty, system wchodzi w tryb ustawień, jeśli nie, kontynuuje normalnie pracę.
  7. W trybie ustawień ESP32 tworzy punkt dostępowy Wi-Fi (ssid: BirdFeeder-Access-Point, hasło:123456789) i czeka na parametry z aplikacji do momentu zresetowania płytki.
  8. Jeśli nie jest w trybie ustawień, wykonuje początkowe zdjęcie.
  9. Następnie uruchamia się Wi-Fi i można kontynuować przesyłanie zdjęć do bazy danych Firebase w czasie rzeczywistym. Przesyłany jest również status Wi-Fi i stan baterii. Zdjęcie jest niemal natychmiast dostępne na smartfonie.
  10. Po zakończeniu ESP32 wraca do stanu uśpienia, czekając na ponowne wyzwolenie przez czujnik.

Pozostałe pliki pełnią funkcję bibliotek pomocniczych, gdzie zaimplementowano poszczególne funkcjonalności:

  • Battery.ino, Battery.h: służy do odczytu stanu baterii. Nie jest to rozwiązanie nazbyt precyzyjne, ale wystarczy w zupełności do oceny jakości baterii i stopnia jej naładowania.
  • Camera.ino, Camera.h: ustawienia aparatu i robienia zdjęć.
  • Eeprom.ino, Eeprom.h: zapamiętuje parametry przesyłane przez aplikację Happy Birds, które muszą być przechowywane w pamięci stałej. Ten moduł obsługuje służącą do tego kość EEPROM.
  • Firebase.ino, firebase.h: funkcje dostępu do bazy danych i pamięci masowej Google Firebase.
  • Registers.ino, Registers.h: wyłącza wykrywanie spadku napięcia i zarządza rejestrem B, aby móc prawidłowo korzystać z Wi-Fi.
  • Setup.ino, Setup.h: moduł uruchamiany, gdy karmnik jest w trybie ustawień. W tym trybie ESP32 tworzy punkt dostępowy i czeka na odebranie parametrów z aplikacji.
  • Wifi.ino, Wifi.h: zarządza połączeniami Wi-Fi modułu, mierzy siłę sygnału i dba o to, aby Wi-Fi uruchamiane było tylko wtedy, gdy jest potrzebne, gdyż pobiera dużo energii.

Autor odradza wprowadzanie zmian do kodu z dwóch powodów. Głównym powodem jest kompatybilność z aplikacją, ale z drugiej strony ważny jest poziom optymalizacji programu. ESP32-CAM to fajna, ale nietypowa płytka, w której wyprowadzenia GPIO bywają dosyć trudne do prawidłowego użycia, ponieważ są dzielone między kilka funkcji. Kolejność wykonywania linii w programie ma duże znaczenie.

Karmnik może przesłać do 200 zdjęć, które są trwale przechowywane w chmurze Google. Dzięki aplikacji można je zapisywać w swoim lokalnym albumie, udostępniać za pośrednictwem sieci społecznościowych, w tym specjalnie przygotowanej sieci Happy Birds.

Po osiągnięciu limitu konieczne jest zwolnienie miejsca na dysku poprzez usunięcie niektórych zdjęć z poziomu aplikacji. Ograniczenie przestrzeni wynika z faktu, że jest to darmowa aplikacja. Autor zapowiada, że jeśli ten projekt okaże się sukcesem, przeskaluje całość, co najpewniej przełoży się na zwiększenie dostępnej pojemności, jak i rozbudowę sieci społecznościowej związanej z systemem.

Budowa karmnika i integracja elementów elektronicznych

Bird Feeder został zaprojektowany w programie Fusion 360. Model składa się z kilku elementów, które trzeba wydrukować osobno. Autor użył trzech różnych rodzajów filamentu do druku, ale oczywiście finalna decyzja należy do osoby wykonującej urządzenie.

Zastosowano filament z drobinkami drewna (SUNLU WOOD 1,75 mm). Dzięki temu karmnik wygląda niemalże jak wykonany z drewna. Elementy wydrukowane za pomocą tego filamentu to: główna część, wspornik kamery, tylna klapa, prowadnice i wspornik. Z czarnego filamentu (SUNLU PLA+ 1,75 mm) wykonano dach i ozdobny emblemat na karmnik. Ostatnie elementy – prawy i lewy karmnik – wykonano ze wzmocnionego filamentu z PETG (ERYONE PETG Clear 1,75 mm). Wydrukowane elementy karmnika pokazano na fotografii 3.

Fotografia 3. Wydrukowane w technice 3D elementy karmnika

Nic oczywiście nie stoi na przeszkodzie, aby zbudować taką konstrukcję z drewna. Wymiary elementów dostępne są w projekcie (link na końcu artykułu), co powinno pomóc w wykonaniu bardziej „klasycznego” karmnika.

Fotografia 4. Wstępnie złożone elementy karmnika

Wszystkie elementy trzeba ze sobą skleić. Wspornik kamery wkleja się z przodu karmnika, w pasujący do niego otwór z przodu. Dach umieszcza się oczywiście na górze, a ozdobnego ptaszka przykleić można z przodu, centralnie na karmniku (fotografia 4). Następnie na tylne drzwi nakleić można prowadnice, a w środku umieścić elektronikę (fotografia 5). Po odczekaniu ok. 24 godzin, aż klej wyschnie, można spryskać poszczególne części lakierem (dla ochrony zewnętrznej). Przed nałożeniem lakieru warto wypróbować go na elementach z danego materiału w niewidocznej ich części, aby upewnić się, czy nie dochodzi do niepożądanej reakcji chemicznej pomiędzy PLA/PETG a lakierem, przez co plastik może wyblaknąć, zmięknąć czy w inny sposób stracić swoje własności.

Fotografia 5. Karmnik podczas montażu

Następnie można przykleić panele słoneczne do dachu. Przewody powinny przechodzić przez dach. Kolejnym krokiem jest zamocowanie uchwytu do baterii za pomocą wkrętów samogwintujących M2. Finalnie można umieścić moduł ESP32-CAM na płytce drukowanej. Po zakończeniu montażu należy sprawdzić wszystkie połączenia. Należy także uszczelnić przejścia przewodów, wyprowadzenie kamery i inne miejsca, przez które ewentualna wilgoć mogłaby dostać się do środka urządzenia.

Wystawienie paneli słonecznych na działanie słońca lub światła powinno zapalić czerwoną diodę modułu ładowarki. Dołączenie micro-USB do zewnętrznego zasilania powinno również zapalić tę samą czerwoną diodę LED. Gdy bateria jest pełna, dioda LED zmienia kolor na niebieski. Po włączeniu systemu przyciskiem zasilania należy odczekać chwilę – ok. 1...2 minuty i położyć rękę przed czujnikiem, tuż nad obszarem karmienia – czerwona dioda LED powinna zaświecić się na czujniku, wewnątrz karmnika. Jeśli tak się stało, to znaczy, że urządzenie działa poprawnie.

Aplikacja mobilna

Aplikacja Happy Birds jest dostępna na urządzenia z systemem iOS w App Store oraz na urządzenia z systemem Android w Google Play. Jest w pełni darmowa. Na tym etapie należy ją pobrać i zainstalować na smartfonie lub tablecie, z którego korzystać będziemy oraz postępować zgodnie z kolejnymi instrukcjami. W zależności od ustawień aplikacja używa języka angielskiego lub francuskiego i działa w trybie jasnym i ciemnym. Można zmienić aktualny język lub tryb, przechodząc do ustawień telefonu (nie do ustawień aplikacji). Aby utworzyć konto, należy kliknąć przycisk Register (na dole pierwszej strony) i wypełnić formularz swoimi danymi wraz z e-mailem i hasłem. Jeśli rejestracja się powiedzie, przejdziemy na stronę ustawień.

Konfiguracja karmnika z aplikacją jest bardzo prosta. Dokładna instrukcja dostępna jest w postaci pliku pdf na stronie z projektem. Przed rozpoczęciem procesu konfiguracji należy naładować akumulator lub kontynuować z podłączoną ładowarką. Należy wejść w menu ustawień i kliknąć na przycisk Bird Feeder + phone Setup. System wchodzi teraz w tryb ustawień karmnika. Należy postępować zgodnie z instrukcjami wyświetlanymi na ekranie:

  1. Należy wyłączyć karmnik i odkręcić tylną ścianę.
  2. Teraz możemy nacisnąć przycisk ustawień i włączyć karmnik.
  3. Potem przechodzimy do ustawień Wi-Fi w telefonie i łączymy się z siecią BirdFeeder-Access-Point (hasło to 123456789).
  4. W aplikacji należy wprowadzić identyfikator SSID i hasło sieci Wi-Fi, do której podłączyć ma się karmnik podczas normalnej pracy. Można teraz zatwierdzić ustawienia i zapisać je w systemie.

Po krótkiej chwili (może to zająć kilka sekund) pojawi się komunikat potwierdzający pomyślną konfigurację. Można teraz wyłączyć karmnik i włączyć ponownie w normalnym trybie. Po tym należy podłączyć telefon do tego samego Wi-Fi i uruchomić aplikację.

Na tym etapie karmnik dla ptaków działa. Po nie więcej niż 1 minucie jest on już gotowy do testowania – wystarczy przysunąć rękę do sensora na odległość mniejszą niż 10 cm, a karmnik powinien wykonać zdjęcie, które zobaczymy w aplikacji. Jeśli tak jest, można zamknąć tylne drzwi śrubami i umieścić urządzenie w ogrodzie lub na balkonie.

Użytkowanie

Aplikacja powstała przy użyciu Fluttera, otwartej platformy Google do tworzenia aplikacji wieloplatformowych z jednego kodu źródłowego. To całkiem wygodne i łatwe rozwiązanie. Autorowi nauczenie się podstaw zajęło kilka tygodni, sama aplikacja powstawała jednak znacznie dłużej. Jest ona w tym momencie na etapie tzw. MVP (Minimum Viable Product), co oznacza, że jest to jej pierwsza wersja z zestawem podstawowych krytycznych funkcji:

  • Odbiór i przechowywanie zdjęć ptaków nadesłanych przez karmnik;
  • Zdjęcia mogą być również wykonywane przez aplikację (nie ma potrzeby stosowania karmnika);
  • Biblioteka zdjęć ptaków do pomocy w ich identyfikacji;
  • Udostępnianie za pomocą WhatsApp i Instagrama;
  • Pamięć lokalna w albumie;
  • Udostępnianie w sieci Happy Birds, w tym w strefie czatu;
  • Odkrycie wszystkich karmników dla ptaków, udostępniających zdjęcia i zlokalizowanych na mapie geograficznej świata (uwaga: dokładność lokalizacji obniżona do 3 km w celu zachowania anonimowości);
  • Dostępne tryby jasny i ciemny;
  • Dostępny w języku angielskim i francuskim.

Podsumowanie

Autor konstrukcji, jak i redakcja EP mają nadzieję, że ten ciekawy projekt spotka się z zainteresowaniem. Z łatwością można zbudować własny karmnik dla ptaków i dołączyć do sieci Happy Birds. Jeśli rozwój sieci się powiedzie, autor planuje dodanie większej liczby funkcji w aplikacji. Zachęca też użytkowników do przesyłania mu swoich opinii, dotyczących znalezionych błędów, sugestii nowych funkcji i innych.

Nikodem Czechowski, EP

Źródło https://bit.ly/3Hd8g9a

Artykuł ukazał się w
Elektronika Praktyczna
luty 2022
DO POBRANIA
Materiały dodatkowe
Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik listopad 2024

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio listopad - grudzień 2024

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka, Podzespoły, Aplikacje październik 2024

Automatyka, Podzespoły, Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna listopad 2024

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich listopad 2024

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów