Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'Raspberry Pi'.

  • Szukaj wg tagów

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

Typ zawartości


Kategorie forum

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

Kategorie

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

Szukaj wyników w...

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


Data utworzenia

  • Rozpocznij

    Koniec


Ostatnia aktualizacja

  • Rozpocznij

    Koniec


Filtruj po ilości...

Data dołączenia

  • Rozpocznij

    Koniec


Grupa


Imię


Strona


TempX

  1. Dawno już miałem ochotę skombinować sobie stację pogodową i powiesić na ścianie jakiś estetyczny ekran pokazujący jak to tam jest za oknem. Myślałem żeby coś takiego kupić, ale w końcu zbudowałem sam - a w każdym razie ekran, bo prezentowane dane pochodzą z serwisu pogodowego, a nie z mierników. Jest to mój pierwszy projekt elektroniczny. Od razu wyjaśnię, że ikony pogody oraz biedronka i grzybek to rysunki mojej pięcioletniej córki 🙂 Hardware Wszystko oparte jest na Raspberry Pi Zero W, ekran wybrałem Waveshare trójkolorowy 7.5", z modułem HAT dla Raspberry Pi. Natomiast żona dała mi wyraźnie do zrozumienia, że jeśli to ma wisieć na ścianie w salonie czy w korytarzu, to ma być ładne, więc zbudowałem z listewek drewnianych ramkę (wiem że jest trochę krzywo na łączeniach, ja naprawdę się starałem), a następnie stwierdziłem, że jeśli nałożę tego HATa na Pi, to całość jest o ładnych kilka milimetrów za gruba żeby się zmieścić pod ekranem... więc pozostało mi łączenie przewodami pojedynczych nóżek. Moduł HAT ma też połączenie uniwersalne, i dołączony był przewód ośmiożyłowy. Ekran ma dość krótką taśmę, w zestawie była też przedłużka ale postanowiłem podłączyć moduł do tej taśmy bezpośrednio z myślą że przecież tam w środku jest nadal masa miejsca na moje Pi. No cóż, okazało się to nie takie proste, i ostatecznie Pi jest tak trochę po skosie, utrzymywany na miejscu przez swój kabel zasilający oraz drut łączący go z wiązką przewodów. Tego się nie da opisać, to trzeba zobaczyć, więc załączam zdjęcia. Na szczęście wszystko to jest całkowicie schowane, bo ramka tyłem przylega do ściany. Grubość ramki to 2cm, przestrzeń na elektronikę ma około 1.5cm. (Jeżeli ktoś się zastanawia czemu wszystkie piny Pi są ubabrane cyną, no cóż, to smutna historia. Najpierw własnoręcznie wlutowałem tam złącze czterdziestopinowe (i byłem z siebie bardzo zadowolony, bo nigdy wcześniej czegoś takiego nie lutowałem, a do tego użyłem lutownicy transformatorowej i nic się od tego nie uszkodziło), a dopiero później zbudowałem ramkę, która okazała się za mała, więc je całe wylutowałem wyrywając po jednej nóżce. Nadal transformatorem, i nadal nic się nie uszkodziło, więc właściwie to nadal jestem z siebie zadowolony.) W ostatecznej wersji szczelina na dole, którą wsuwany był ekran, zaklejona jest plasteliną. Plastelina jest też użyta do uszczelnienia ekranu, tak żeby nie miał luzu. Software Oprogramowanie napisane jest całkowicie w języku Ruby, i składa się z następujących części: Dane pobierane są co około 10 minut: Dane o pogodzie pobierane są z openweathermap.com. Darmowy dostęp do API pozwala pobrać prognozę godzinną na 48h oraz dzienną na parę dni, no i sporo parametrów aktualnej pogody, które prawie wszystkie wyświetlam. Dane do kalendarza pobierane są przez protokół ICAL z Kalendarza Google, opis wydarzeń wskazuje na typ, różne typy są różnie oznaczone w kalendarzu (kreskowane linie nad datami albo kropki, czarne lub czerwone). Dodatkowo pobieram informacje o świętach z calendarific. Te dane są cache'owane bo świąt narodowych raczej nie dodają co 10 minut i nie ma sensu tak męczyć tego API. Pobrane dane są porównane z poprzednimi danymi. Jeżeli nie zmienił się żaden istotny parametr to nie ma sensu nic przerysowywać, zwłaszcza że ekran nie ma częściowego przerysowania, tylko cały migocze jak szalony przez kilkanaście sekund. Natomiast jeśli różni się na przykład aktualna ikona pogody czy temperatura, albo to, czy ma za dwie godziny padać, to przerysowuję od razu. Przerysowuję też co najmniej raz na godzinę, żeby zaktualizować wykres. Następnie z danych składam (z użyciem własnej bardzo prostej biblioteki) definicję SVG przedstawiającego cały ekran. Potem używam imagemagick żeby odczytać piksele z tego obrazu. Piksele mapuję na dostępne w ekranie kolory (biały, czarny, szary (tylko jeden) i czerwony), i wysyłam do własnej biblioteki, która montuje bufor w formacie akceptowanym przez ekran. Wysyłam zawartość do ekranu przez SPI. Korzystam przy tym z biblioteki Mike'a McCauley'a, której używam z Rubiego przez FFI. Oprogramowanie ma sporą ilość konfigurowalnych parametrów, takich jak liczba godzin do pokazania na wykresie, gęstość siatki, liczba tygodni w kalendarzu, dane do "galerii" po prawej na dole. Wszystko to, razem z takimi danymi jak na przykład klucze API albo lokalizacja dla której ma być sprawdzana pogoda, siedzi w pliku konfiguracyjnym lokalnie na Pi, albo przez HTTP (co pozwala łatwo zmieniać konfigurację bez logowania się do Pi). Używam gita, edytuję kod na innej maszynie niż ten Pi w ekranie, testuję kod uruchamiając go z parametrami, które powodują wygenerowanie PNG zamiast wysyłania obrazu na fizyczny ekran. Gdy zrobię zmianę, pushuję ją, a potem jedną komendą ustawiam flagę update-code w serwisie na zewnętrznym serwerze (nie będę tu opisywał ze szczegółami). Gdy serwis działający na ekranie następnym razem będzie odświeżać dane i zobaczy flagę update-code, to najpierw zrobi git pull i się zrestartuje. Dzięki temu nie potrzebuję łączyć się bezpośrednio z Pi w ekranie żeby wgrać mu aktualizację kodu. Potencjalny dalszy rozwój Projekt jest zakończony, wisi na ścianie już 4 miesiące i jestem z niego bardzo zadowolony. W międzyczasie dodawałem tylko różne drobne usprawnienia (na przykład dodawałem godziny wschodu i zachodu słońca, a ledwie wczoraj naprawiałem małego buga, który się ujawnił przy zmianie czasu). Natomiast jest możliwe parę większych usprawnień, które może kiedyś zrobię, a może nie. Pobieranie prognozy pogody z innego serwisu, na przykład yr.no. Czasami wydawało mi się, że jest bardziej trafna, chociaż bywało też odwrotnie. Natomiast zawiera mniej parametrów, więc raczej łączyłbym prognozę z dwóch źródeł. No i podaje temperaturę bez części ułamkowych, i musiałbym zaimplementować wygładzanie wykresu. Pobieranie informacji o aktualnej pogodzie z jakichś fizycznych czujników. Dołączenie informacji o aktualnych parametrach wewnątrz pomieszczenia, na przykład temperaturze i wilgotności. Do tego prawdopodobnie zbudowałbym osobne urządzenie oparte na Pi, które takie dane mierzy i wystawia w jakiś sposób, a ramka by je tylko pobierała.
  2. Cześć, właśnie rozpocząłem zakupiony u Was kurs Raspberry Pi i niestety system się nie bootuje, errory na zdjęciach. Proszę o radę, co w takiej sytuacji zrobić. Pozdrawiam, Stefan
  3. Przychodzę z pewnym problemem z którym borykam się już od jakiegoś czasu, związanym z mobilnym robotem. Ma to być robot wyposażony w wiele czujników, w tym: 4 czujniki odległości HC-SR04P, listwa z czujnikami odbiciowymi QTR-8A, IMU ICM-20948, 4 przyciski ("ala czujniki dotyku"), LIDAR, kamera, enkodery kwadraturowe. Do odbierania danych z czujników oraz do sterowania silnikami chcę wykorzystać Arduino Mega lub giga r1(niby jest szybyszy ale nie wykorzystuje się jego potencjału jak ię tworzy kod na arduino ide). Dane z Arduino będą przekazywane poprzez kabel usb do Raspberry Pi 4, na którym będzie ROS, oraz do którego podłączone będą LIDAR i kamera. Mam problem głównie z doborem silników do tego robota z powodu konieczności wyposażenia każdego silnika w enkoder. Wszystkie silniki, na jakie natrafiłem, były wyposażone w enkodery inkrementalne (co wymaga 2 pinów do obsługi jednego enkodera). Z tego, co sprawdziłem, aby obsłużyć enkodery, potrzebne są przerwania a arduino potrzebuję czasu aby wszystkie obsłużyć. Poniżej znajduje się kod, który napisałem, aby pomóc sobie przy doborze silników . Orientacyjna masa robota będzie wynosić około 6 kg(dla 4 kół), a koła, które chcę wykorzystać, mają średnicę 10 cm. Ilość kół wpłynie na moment potrzebny do silnika oraz ilość enkoderów. Ma być to robot autonomiczny a więc jeśli zastosuję 4 silniki, zamierzam użyć kół mecanum; jeśli 2, to zwykłe koła i jedno z przodu swobodne; jeśli 3, to koła omnikierunkowe. Dla 4 zwykłych kół nie znalazłem macierzy. #include <stdio.h> int main() { // Deklaracja zmiennych double V, M, N, X=1.5 /* odwrotnosc sprawnosci */, D, g=9.81, u=0.9 /* wspolczynik tarcia */, a=1 /* przyspieszenie liniowe */, t, O; // Wprowadzanie wartości zmiennych printf("Podaj wartość M: "); // masa scanf("%lf", &M); printf("Podaj wartość N: "); // Ilosc kol scanf("%lf", &N); printf("Podaj wartość D :"); // Srednica kola scanf("%lf", &D); printf("Podaj wartość V: "); // Predkosc liniowa scanf("%lf", &V); // Obliczenia zgodnie z podanymi wzorami t = (X * (((u * M * g) + (M * a)) * D) / (2*N)) * 10.19716; // Wymagany moment(z przelicznkiem z Nm na kgfcm) O = X*(2 * V / D) * 9.5493; // Predkosc obrotowa(z przelicznikiem z rad/s na rpm) // Wyświetlenie wyników printf("Wartość t wynosi: %lf\n", t); printf("Wartość O wynosi: %lf\n", O); return 0; } W kodzie nie uwzględniłem kąta równi pochyłej bo robot ma się poruszać po płaskiej powierzchni.Mam także wątpliwości co do współczynnika tarcia, - czy mam wziąć pod uwagę tarcie toczene, czy statyczne.Jeżeli macie także jakieś porady do innych rzeczy związanych z tym robotem będe wdzięczny za podpowiedź i pomoc.
  4. Dzień dobry, Szybkie pytanie czy karta TP-Link UE300 (RealTek RTL 8153) będzie współpracować z Raspberry Pi 4B? Chciałbym użyć tej karty jako interfejsu WAN przy konfiguracji pfSense.
  5. Cześć wszystkim, Mam problem z identyfikacją wersji GPIO na moim Raspberry Pi. Próbowałem kilku poleceń w terminalu, takich jak "gpio -v" i "gpio readall", ale otrzymuję komunikat o błędzie "-bash: gpio: nie znaleziono polecenia". Czy ktoś mógłby mi pomóc? Byłbym bardzo wdzięczny za wszelką pomoc. Dziękuję
  6. import cv2 import pytesseract import time from pytesseract import Output def extract_license_plate(image_path): image = cv2.imread(image_path) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (5, 5), 0) edges = cv2.Canny(blur, 100, 200) contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) contours = sorted(contours, key=cv2.contourArea, reverse=True)[:10] plate_text = None for contour in contours: peri = cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, 0.018 * peri, True) if len(approx) == 4: x, y, w, h = cv2.boundingRect(approx) roi = image[y:y + h, x:x + w] plate_text = pytesseract.image_to_string(roi, config='--psm 8 --oem 3 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') return ''.join(filter(str.isalnum, plate_text)) return "no text detected" cap=cv2.VideoCapture(0) try: while True: ret, frame = cap.read() if ret: cv2.imwrite('/home/marek/tablica.png',frame) print("Image saved") text = extract_license_plate('/home/marek/tablica.png') else: print("fail") if text is not None: print(f"Extracted Text: {text}") time.sleep(5) except Exception as e: print(f"An error occurred: {e}") finally: cap.release() print("Camera released.") Pracuję nad projektem systemu służącego do automatycznego otwierania bramy, w momencie rozpoznania przez kamerę z Raspberry Pi, numerów tablic rejestracyjnych zapisanych w pliku tekstowym. Jestem dość zielony w jakiekolwiek programy związane z systemami wizyjnymi i mocno posiłkowałem się ChatemGPT 4, wrzuciłem do chatu zdjęcia przypominające docelowy obraz z kamery i kazałem mu pod to napisać program. Program działa prawie idealnie kiedy zczytuje tablice z gotowych zdjęć, jednak gdy zdjęcia pochodzą z kamery i pokazuje przed nią dokładnie to samo zdjęcie tylko wydrukowane, program nie rozpoznaje żadnych znaków. Pytanie gdzie może leżeć problem, czy wpływ na to ma zbyt niska jakość obrazu z kamerki (używam kamery ZeroCam z przejściówka do Raspberry PI 3A+) , potencjalnie zły kąt zdjęcia przed kamerka czy może jakiś błąd w kodzie? Testuje działanie na załączonym obrazku Z góry dziękuje za pomoc, ewentualny namiar do kogoś kto mógłby pomóc :))
  7. mam taki problem. bo w filmach instalacji oprogramowania multimedialnego kodi na youtube widać, że filmy mają taką fajną nakładkę tutaj przypnę link jak to powinno wyglądać. z góry dzięki link do zdjęcia
  8. Projekt z gatunku "pilnych" - czyli trzeba rzucić wszystko i się tym zająć... Założenia: ma to odtwarzać empeszcze przez BT albo kontrolnie przez wbudowany głośniczek ma być łatwy w obsłudze (baaaaardzo łatwy) kilka trybów pracy, m.inn. spektakl (czyli po każdym numerze robi stop i przeskakuje do następnego) odczyt albo wszystkich plików mp3 (przesłuchanie) albo tylko konkretnych (nazwy rozpoczynające się od ciągu cyfr i podkreślnika lub myślnika) odczyt muzyki z pendrive (montowany read-only automatycznie) i ogólnie jeszcze innych parę ważnych rzeczy które wyjdą w praniu No i najważniejsze: zrobiony z tego co leży w szufladzie. Na razie wyszło mi coś takiego: RPi Zero W do grania, gadania z ustrojstwami po BT i czytania pendrive ESP32 do obsługi klawiatury, wyświetlacza i ogólnie całej logiki Komunikacja obu przez port serial Co już mam: Spatchowaną bibliotekę mpg123 do Pythona (dodane funkcje seek i tell) udane pożenienie ze sobą i2s, pulseaudio i bluetooth (walka z regulacją głośności dla i2s to temat na powieść) automontowanie pendrive oraz powiadomienie głównego programu na RPi że takowy został zamontowany prowizoryczny programik odtwarzacza mp3 (na mpg123 i pyao) jeszcze bardziej prowizoryczny do obsługi BT (w sumie wrapper do bluetoothctl - nic bardziej sensownego nie znalazłem). szkieletowy program na ESP32 (rdzeń 0 zajmuje się na razie wyłącznie wyświetlaczem, rdzeń 1 to cała reszta, zobaczymy co z tego wyjdzie) zmontowane wszystko (razem z modułem DS3231, eeprom się przyda bo karta jest montowana ro w overlay) na płytce uniwersalnej, Zobaczymy co z tego wyjdzie - konstrukcja zapowiada się ciekawie. Teraz kompletuję przyciski do klawiatury (chyba mi kilku zabraknie, ale może da się z czegoś wylutować). Jakby ktoś miał jakieś pomysły to na tym etapie bardzo chętnie się zapoznam 🙂
  9. Czy kiedyś będzie kurs FORBOT z mikrokontrolerem Raspberry Pi Pico (lub wersja W)? Przykładowe części: - wyświetlacz LCD - enkoder obrotowy - różne czujniki - diody LED - moduł RFID - matryca przycisków 4x4 - moduł przekaźnika - itp. Podobało mi się jak dobry był kurs elektroniki, a teraz dobrze by było wejść w mikrokontrolery. Pi Pico też jest tańsze niż np. Arduino Uno co pozwalało by na dodaniu więcej części do zestawu. Przepraszam, jeżeli ten temat nie pasuję do tej kategorii.
  10. Dzień dobry, W dniu wczorajszym eksperymentowałem z funkcją Serial.readString() w raspberry Pi pico wh (przy urzyciu arduino IDE) gdy podłączam się do malinki przez port COM przy uczuciu PUTTY. Pojawił się tekst który miał się pokazać w wypadku wpisania: "Test". void setup() { // put your setup code here, to run once: } void loop() { // put your main code here, to run repeatedly: Serial.printf("wpisz test") String text = Serial.readString(); if (text == "test") { Serial.printf("Wpisano test"); } else { Serial.printf("nie wpisano test"); } } Z góry dziękuje za pomoc, Olek
  11. Dzień dobry, mam problem z ustawieniem wejść cyfrowych na raspberry Pi4b, chciałbym skorzystać z funkcji WiringPiISR aby odczytywać tylko zbocze narastające. 1. Sensor przepływu cieczy wysyła sygnał cyfrowy na pin "1", 2. Odczytuje kolejne zbocza narastające na danym pinie i zliczam je w zmiennej. Tutaj rozumiem, że ta funkcja WiringPiISR wymaga podania:pinu, rodzaju zbocza, oraz co ma być wykonane 3. Po zliczeniu np. 100 sygnałów ma się wykonać funkcja wyłączająca pompkę Problem napotykam w samej składni, ponieważ nie jestem doświadczonym programista, i niewiele wiem o qt, Etap na którym ugrzązłem: W konstruktorze: WiringPiISR (pin_1,INT_EDGE_RISING,isrInput); Oraz zadeklarowałem void(*isrInput)(void); Tylko nie wiem co to tak naprawdę oznacza. Chciałbym jedynie aby dany pin odczytywał zbocze narastające Przepraszam za brak kodu, bo nie mam aktualnie możliwości wstawienia, oczywiście mogę podesłać plik .h oraz .cpp aczkolwiek jest to goła aplikacja w qt creator jedynie z kodem inicjalizujacym bibliotekę wiringPi oraz dane piny. Dziękuję z góry za każdą wskazówkę jak dany problem rozwiązać
  12. Dzień dobry, mam problem z ustawieniem wejść cyfrowych na raspberry Pi4b, chciałbym skorzystać z funkcji WiringPiISR aby odczytywać tylko zbocze narastające. 1. Sensor przepływu cieczy wysyła sygnał cyfrowy na pin "1", 2. Odczytuje kolejne zbocza narastające na danym pinie i zliczam je w zmiennej. Tutaj rozumiem, że ta funkcja WiringPiISR wymaga podania:pinu, rodzaju zbocza, oraz co ma być wykonane 3. Po zliczeniu np. 100 sygnałów ma się wykonać funkcja wyłączająca pompkę Problem napotykam w samej składni, ponieważ nie jestem doświadczonym programista, i niewiele wiem o qt, Etap na którym ugrzązłem: W konstruktorze: WiringPiISR (pin_1,INT_EDGE_RISING,isrInput); Oraz zadeklarowałem void(*isrInput)(void); Tylko nie wiem co to tak naprawdę oznacza. Chciałbym jedynie aby dany pin odczytywał zbocze narastające Przepraszam za brak kodu, bo nie mam aktualnie możliwości wstawienia, oczywiście mogę podesłać plik .h oraz .cpp aczkolwiek jest to goła aplikacja w qt creator jedynie z kodem inicjalizujacym bibliotekę wiringPi oraz dane piny. Dziękuję z góry za każdą wskazówkę jak dany problem rozwiązać
  13. Siema. Mam problem z kamerką w raspberry pi 3, ponieważ występuje błąd przy importowaniu biblioteki 'picamera'. Kod python: from picamera import PiCamera from time import sleep camera = PiCamera() camera.start_preview() sleep(5) camera.stop_preview() Kod błędu: Traceback (most recent call last): File "/home/pi/Desktop/camera.py", line 1, in <module> from picamera import PiCamera File "/usr/local/lib/python3.9/dist-packages/picamera/__init__.py", line 72, in <module> from picamera.exc import ( File "/usr/local/lib/python3.9/dist-packages/picamera/exc.py", line 41, in <module> import picamera.mmal as mmal File "/usr/local/lib/python3.9/dist-packages/picamera/mmal.py", line 49, in <module> _lib = ct.CDLL('libmmal.so') File "/usr/lib/python3.9/ctypes/__init__.py", line 374, in __init__ self._handle = _dlopen(self._name, mode) OSError: libmmal.so: cannot open shared object file: No such file or directory
  14. Dzień dobry, Chciałbym się zapytać czy istnieje na urządzeniu Raspberry Pi 3B+ polski lektor(męski) ?- Python, biblioteka: pyttsx3. Szukam już po internecie od dłuższego czasu i na nic nie mogę trafić. Id polskiego głosu jaki pokazuje mi program brzmi "polish" jednak po wybraniu go brzmi on dość mało "polsko". Chciałbym się dowiedzieć czy ten głos po prostu taki jest, czy ja coś robię nie tak. Jeśli istnieje jakiś inny sposób dodania polskiego głosy do maliny to prosiłbym o podpowiedź gdzie i jak mogę go znaleźć. (Sorki za jakoś zdjęcia, ale zależy mi na czasie)
  15. Dzień dobry Nie będę zupełnie oryginalny i zrobiłem dwie stacje meteo. Chciałem poduczyć się trochę arduino i szukałem pomysłu na projekt przeglądając Botland znalazłem czujniki pyłu PM 2,5 i PM 10. Przez to, że temat smogu jest na czasie uznałem to za dobry pomysł by zweryfikować czy miejscowość w której mieszkam (wieś) jest od niego wolna. Zacząłem od kursów na Forbocie (vel wyświetlacz lcd) by załapać podstawy arduino i elektroniki. Były bardzo pomocne. Następnie chciałem przetestować niektóre czujniki i tak sprawdziłem Temperatura DS18B20 MCP9808 SHT 15 SHT 31 Wilgotność DHT 11 DHT 22 SHT 15 SHT 31 Przy wyborze też między innymi kierowałem się dokładnością pomiaru najlepiej ok 5% oraz możliwością pracy przy ujemnych temperaturach im mniej tym lepiej gdyż mrozy tu mogą sięgać -20C. Przy testowaniu tylko miałem problem z DHT11 i DHT22 - mianowicie nie podawał mi poprawnych danych (miałem obok kupny wilgotnościomierz i dane zupełnie nie pasowały do siebie ale opisałem to w innym poście na forum). Wybrałem MCP9808 gdyż uznałem, na czujnik temperatury gdyż wg moich obserwacji najlepiej się sprawdzał w różnych warunkach i wahaniach temperatury. Osobno tak samo wybrałem czujniki wilgotności tylko do tego celu tj. SHT31. Barometr widziałem, że polecany jest BMP180 więc na nim zostałem. Z programowaniem nie było problemu ze względu na biblioteki. Czujniki pyłu były małym wyzwaniem. Brak bibliotek. I śmigają na UART Trzeba operować na przykładowych kodach i rozumieć jak lecą bity. Ponadto trzeb zwracać uwagę na to że różne modele mogą mieć różny formę ramki danych przesyłanych. Przetestowałem 3 czujniki pyłu PMS5003 PMS5003 z detekcją formaldehydu Gravity Każdy z nich miał inaczej ramkę ukształtowaną. Ponadto czujniki zasilane są napięciem 5V a linia danych 3,3V Do tego należy dokupić konwerter poziomów logicznych by zadziałało to sprawnie. Do tego zestawu dokupiłem stacje meteo z wiatromierzem. Następnie chciałem aby stacja mogła być na zewnątrz i komunikować się z odbiornikiem w środku przez nRF24L01+. Warto brać moduł z zewnętrzną anteną oraz adapterem poprawia działanie modułu. Mając już na płytce w miarę temat ogarnięty chciałem najpierw testowo zrobić mobilny czujnik pyłu aby zrobić pierwsze przymiarki do lutowania i montażu. Lutowałem na płytkach prototypowych z cienkimi kablami. Nie był to dobry pomysł ale na daną chwilę dało się. Zamiast przylutowywać moduły na stałe lutowałem sloty do nich. Wolałem trochę poświęcić jakość wykonania na rzecz sytuacji w której mógłbym się pomylić. Nie mam rozlutotwnicy a standardowa lutownica i odsysacz słabo mi się sprawdzały. Specyfikacja mobilnego czujnika pyłu (główne moduły) Arduino Pro Mini 328 - 5V/16MHz SHT 15 (wilgotność i temperatura) PMS5003 (czujnik pyłu) Konwerter USB-UART FTDI FT232RL - gniazdo miniUSB (programator ew zasilanie poza bateryjne) Wyświetlacz LCD 4x20 znaków zielony Efekt był zadowalający wiec uznałem, że warto będzie spróbować wysyłać dane na stronę www. Do tego zadania zaprzęgłem malinkę z modułem nRF24L01+. Idea była taka aby: ->pomiar stacji ->wysłanie danych drogą radiową ->odebranie przez Ras Pi ->wysłanie danych na zewnętrzny bazę danych MySQL ->pobranie danych z bazy i wyświetlenie jej na stronie bazującej na wordpresie Główny problem był z liczbami zmiennoprzecinkowymi. Gdyż malinka odbiera surowe bity i nie chce ich prze konwertować na liczbę zmiennoprzecinkową. W każdym bądź razie wszelkie próby konwersji i operacji na bitach skończyły się komunikatem ze Python nie obsługuje przesunięć bitowych dla liczb zmiennoprzecinkowych. Do zastosowań domowych wystarczy mi pomiar do drugiego miejsca po przecinku (temperatura) więc po prostu każdą zmienna która miała część ułamkową mnożę na Arduino razy 100 i jak odbiorę na malince dzielę przez 100. O ile odbiór między arduino to linijka kodu to tu przy odbiorze suchej transmisji (już przy użyciu biblioteki ...... ) trzeba było bity ręcznie składać bo Arduino wysyła jedną zmienną w dwóch bajtach trzeba było używać operatorów bitowych by te bajty złączyć w jeden. Przed montażem należało wyznaczyć miejsce dla stacji. Z tego względu do stacji mobilnej dorzuciłem adapter NRF na który mogłem po prostu zamiennie testować wersje z zewnętrzną i wewnętrzną anteną. Syngał nadawał do malinki kolejne cyfry a obok miałem telefon z teamviwerem na którym patrzałem się co na konsoli wyświetliło. Jeśli był cykl 1 2 3 .... 11 12, tzn., że w miejscu w którym stałem jak nadawano cyfry 4 5 6 7 8 9 10 sygnał nie doszedł i nie należało go brać pod uwagę do montażu docelowej stacji meteo. Wiatromierz montowałem na chwycie antenowym. Średnica rur od stacji była mniejsza więc wkładając ją uzupełniłem całość pianką montażową by nie latało. Rezultat był taki, że moduł z wbudowaną anteną za oknem 3m tracił zasięg drugi miał sygnał spokojnie do ok 10m (nawet przez ściany). Po teście wysłania danych i odebrania jednej danej na wordpresie przytępiono do montażu stacji. Jej finalna konfiguracja to Arduino Pro Mini 328 - 5V/16MHz Konwerter USB-UART FTDI FT232RL - gniazdo miniUSB (programator i zasilanie) nRF24L01+ Stacja meteo dfrobot PMS5003 (czujnik pyłu) Gravity (czujnik pyłu) MCP9808 (temperatura) SHT 31 (wigotność) BMP180 (ciśnienie) Dorzuciłem dwa czujniki pyły by porównać ich działanie. Jako końcowy element została mi kwestia wyświetlenia danych na stronie www. Dzięki wtyczce do pisania skryptów php w wordpresie udało napisać moduł pobierania danych z bazy i jego wyświetlania. Wykresy były większym problemem. Darmowe wtyczki do wordpressa nie chcą pobierać danych z sql i są ciężko edytowalne preferują prace na .csv i najlepiej jakby je ręcznie przeładowywać. Finalnie użyłem do tego celu Google Charts (nota bene na których wiele darmowych wtyczek do wordpresa bazuje). Z racji, że nie jestem web-developerem było to moje pierwsze spotkanie z php oraz javascriptem na którym Google Charsty operują. Zmuszenie tego pracy odbyło się metodą prób i błędów ale efekt jest zadowalający. Mam wykresy z 24h, tygodnia i miesiąca. Problemy Kodowanie int w arduino i malince Ze względu za na sposób kodowania liczny int w pythonie ujemne wartości temperatury np -5C na arduino pokazywały na malince wartość 65531 C. Trzeba było dopisać fragment który usuwał ten błąd. Komunikacja L01+ Moduły te są bardzo wrażliwe na zmiany napięcia. Można do nich dokupić adapter do wpięcia który głównie ma stabilizator napięcia. Nawet nie zawsze on pomaga jeśli do niego pójdzie nie wystarczające napięcie. Widać to np. jak się rusza kable to migocze LED lub jest "ciut" jaśniejszy. Wtedy anteny nie mogą się dogadać. Potrafi się zdarzyć takie kuriozum, że w takiej sytuacji antena straci własny adres. Jeśli anteny nie są w stanie się dogadać należy bezwzględnie sprawdzić pewność zasilania. Błąd wysłania danych SQL Czasami kod w malince się zawiesi w oczekiwaniu na wysłanie danych do serwera SQL. Trudno mi powiedzieć jak dobrze temu zaradzić. Występuję to przy dłuższej pracy (tydzień, miesiąc) aczkolwiek jedyne co mi przychodzi do głowy to ustawić na Raspianie auto-restart systemu co 24h. Wycinanie dziur w plastikowej obudowie Próbowałem wycinać szlifierką modelarską ale zawsze plastik się topił. Po szperaniu w necie rozwiązaniem są albo nożyki do plastiku albo wycinarka włosowa do plastiku ale nie udało mi się tego przetestować. Niestety wycięcia wyglądają mało estetycznie ale trudno. Google Charts Przesyłanie danych z PHP do JavaScript tak aby Google bylo problemem. Wykresy nie łykną dowolnego formatu danych i nie wyświetlą błędy jeśli uznają, że format im nie pasuje. Co mógłbym zrobić inaczej? Gdybym miał robić update to zamiast komunikacji radiowej do malinki wydaje mi się bardziej rozsądne pójście w wyposażenie arduino w WIFI i wysyłanie bezpośrednio na serwer. Nie uczyniłem tak ze względu na to pisanie kodu komunikacji Arduino WIFI nie jest to pare linijek. Plus też kod do wysyłania do SQL który znalazłem też pewnie by zawierał trochę kodu. Więcej dłubania ale efekt powinien być bardziej zadowalający. Plany na przyszłość Jedyne co chce na dniach zrobić to poduczyć się Eagla i przenieść całość na płytkę drukowaną i zamówić ją np. JLCPCB. Wtedy wypnę moduły z tamtych płytek i przypnę je do zamówionej. Reasumując Stacja działa już ok 6 miesięcy. Bez problemowo. Jak widać na wykresie z ostatniego miesiąca (tj. 26.12.2018 - 25.12.2019), że normy dla smogu w mojej wsi były w paru dniach przekroczone (a mieszkam w centrum pomorskiego bez kopalni, ciężkiego przemysłu wokół). Gdyby dobrze opisać każdy etap pracy można by z tego zrobić osobny kurs 🙂 Poniżej kody źródłowe Arduino Ras PI Wordpress pobieranie suchych danych Wordpress pobieranie danych i osadzenie ich na Google Charts //Autor Bartosz Jakusz. Można wykorzystywać komercyjnie. Proszę tylko podać autora kodu #include <math.h> #include <Wire.h> #include <Arduino.h> #include <Adafruit_BMP085.h> #include <SoftwareSerial.h> #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #include "Adafruit_SHT31.h" #include "Adafruit_MCP9808.h"7 #define dataSize 16 #define PMS5003BufferSize 40 #define dustGravityBufferSize 32 Adafruit_BMP085 bmp; Adafruit_MCP9808 tempsensor = Adafruit_MCP9808(); Adafruit_SHT31 sht31 = Adafruit_SHT31(); // software serial #2: RX = digital pin 8, TX = digital pin 9 SoftwareSerial Serial1(4, 5); SoftwareSerial Serial3(8, 9); SoftwareSerial Serial2(3, 2); RF24 radio(10, 14); // CE, CSN const byte address[6] = "00001"; char col; unsigned int dustSensor25 = 0,dustSensor10 = 0; unsigned int PMS = 0,PM10 = 0, formalin = 0,CR1 = 0,CR2 = 0; float PMSTemp = 0, PMSHigro = 0; bool readErr = false, readErr25 = false; int dustSensorReadError = 0; unsigned int higherBit = 0, LowerBit = 0; unsigned char buffer_RTT[40]={}; //Serial buffer; Received Data char meteoDatabuffer[35]; int measurments [dataSize]; void readPMS(); void readPM25(); void readDustSensorUARTData(SoftwareSerial S, unsigned char *bufferData, int bufferSize); unsigned int mergeBitsToInt(unsigned char *bufferData, int startBit, int stopBit); void clearBuffer(unsigned char *bufferData); void calculateControlSum(unsigned int &CR1Var, unsigned int &CR2Var, unsigned char *bufferData, int bufferSize); void encageDustSensorReadError(); void printUARTRecievedData(); void readMeteoStation(); int transCharToInt(char *_buffer,int _start,int _stop); //char to int) void sendData(); void printSendedData(); void printDataBySensor(); void setup() { Serial.begin(115000); Serial.println("start"); Serial1.begin(9600); Serial1.setTimeout(1500); delay(2000); Serial2.begin(9600); Serial2.setTimeout(1500); delay(1000); Serial3.begin(9600); Serial3.setTimeout(1500); radio.begin(); radio.setPALevel(RF24_PA_MAX); radio.setDataRate(RF24_1MBPS); radio.setChannel(0x76); radio.openWritingPipe(0xF0F0F0F0E1LL); radio.enableDynamicPayloads(); radio.stopListening(); if (!bmp.begin()) { while (1) {} } if (!tempsensor.begin()) { while (1); } if (! sht31.begin(0x44)) { // Set to 0x45 for alternate i2c addr while (1) delay(1); } Serial.println("Zakończony Init"); delay(2000); } void loop() { readPMS(); readPM25(); readMeteoStation(); printDataBySensor(); if(readErr == false && readErr25 == false) { //Serial.println("Wysyłam dane"); sendData(); //Serial.print("Wysłano dane: "); } delay(1000); } void readPMS() { Serial.println("Czujnik pm5003"); readDustSensorUARTData(Serial1, buffer_RTT, PMS5003BufferSize); calculateControlSum(CR1, CR2, buffer_RTT, PMS5003BufferSize); if(CR1 == CR2 && CR1 != 0) //Check { PMS = mergeBitsToInt(buffer_RTT, 12, 13); PM10 = mergeBitsToInt(buffer_RTT, 14, 15); formalin = mergeBitsToInt(buffer_RTT, 28, 29); PMSTemp = mergeBitsToInt(buffer_RTT, 30, 31) / 10; PMSHigro = mergeBitsToInt(buffer_RTT, 32, 33) / 10; readErr = false; } else { PMS = 4321; PM10 = 4321; formalin = 4321; encageDustSensorReadError(); } clearBuffer(buffer_RTT); } void readPM25() { Serial.println("Czujnik p, 25"); readDustSensorUARTData(Serial3, buffer_RTT, dustGravityBufferSize); calculateControlSum(CR1, CR2, buffer_RTT, dustGravityBufferSize); if(CR1 == CR2 && CR1 != 0) //Check { dustSensor25 = mergeBitsToInt(buffer_RTT, 6, 7); dustSensor10 = mergeBitsToInt(buffer_RTT, 8, 9); readErr25 = false; } else { dustSensor25 = 4321; dustSensor10 = 4321; encageDustSensorReadError(); } clearBuffer(buffer_RTT); } void readDustSensorUARTData(SoftwareSerial S, unsigned char *bufferData, int bufferSize) { S.listen(); while(!S.available()); while(S.available()>0) //Data check: weather there is any Data in Serial1 { col =S.read(); delay(2); if (col == 0x42) { bufferData[0]=(char)col; col =S.read(); delay(2); if (col == 0x4d) { bufferData[1]=(char)col; for(int i = 2; i < bufferSize; i++) { col =S.read(); bufferData[i]=(char)col; delay(2); } break; } } S.flush(); } } unsigned int mergeBitsToInt(unsigned char *bufferData, int startBit, int stopBit) { higherBit = buffer_RTT[startBit]; //Read PM2.5 High 8-bit LowerBit = buffer_RTT[stopBit]; //Read PM2.5 Low 8-bit return (higherBit << 8) + LowerBit; } void clearBuffer(unsigned char *bufferData) { for(int i = 0; i < 40; i++) { bufferData[i] = 0; } delay(100); } void calculateControlSum(unsigned int &CR1Var, unsigned int &CR2Var, unsigned char *bufferData, int bufferSize) { CR1Var = 0; CR2Var = 0; CR1Var =(buffer_RTT[bufferSize - 2]<<8) + buffer_RTT[bufferSize - 1]; for(int i = 0; i < bufferSize - 2; i++) { CR2Var += bufferData[i]; } } void encageDustSensorReadError() { dustSensorReadError = dustSensorReadError + 1; readErr = true; readErr25 = true; Serial.print("Błąd odczytu: "); Serial.println(dustSensorReadError); //printUARTRecievedData(); } void readMeteoStation() { Serial.println("Stacja meteo nasłuchuje"); Serial2.listen(); while(!Serial2.available()); while(Serial2.available()>0) //Data check: weather there is any Data in Serial1 { Serial.println("Start meteo"); for (int index = 0;index < 35;index ++) { if(Serial2.available()) { meteoDatabuffer[index] = Serial2.read(); if (meteoDatabuffer[0] != 'c') { //Serial.println("Transmisja w toku meteo"); index = -1; } } else { index --; } } } Serial.println("Koniec"); } int transCharToInt(char *_buffer,int _start,int _stop) { //char to int) int result = 0; int num = _stop - _start + 1; int tempArr[num]; for (int indx = _start; indx <= _stop; indx++) { tempArr[indx - _start] = _buffer[indx] - '0'; result = 10 * result + tempArr[indx - _start]; } return result; } void sendData(){ for(int z = 0; z < dataSize; z++) { measurments[z] = -1000; } //Meteo station measurments[0] = (int)(transCharToInt(meteoDatabuffer,1,3)); //wind directoin measurments[1] = (int)(transCharToInt(meteoDatabuffer,28,32) / 10.00); //pressure //measurments[2] = (int)(((transCharToInt(meteoDatabuffer,13,15) - 32.00) * 5.00 / 9.00) * 100); //temp *C //measurments[3] = (int)((transCharToInt(meteoDatabuffer,25,26)) * 100); //higro %RH measurments[2] = (int)((transCharToInt(meteoDatabuffer,5,7) / 0.44704) * 100); //avg wind speed 1 min (m/s) measurments[3] = (int)((transCharToInt(meteoDatabuffer,9,11) / 0.44704) * 100); //max wind speed 5min (m/s) measurments[4] = (int)((transCharToInt(meteoDatabuffer, 17, 19) * 25.40 * 0.01) * 100); // rain 1 hour (mm) measurments[5] = (int)((transCharToInt(meteoDatabuffer, 21, 23) * 25.40 * 0.01) * 100); // rain 24 hour (mm) //MCP9008 measurments[6] = (int)(tempsensor.readTempC() * 100); //SHT31 measurments[7] = (int)(sht31.readHumidity() * 100); measurments[8] = (int) (sht31.readTemperature()* 100); //PMS5003 //measurments[11] = PMSTemp * 100; //measurments[12] = PMSHigro * 100; measurments[9] = PMS; measurments[10] = PM10; measurments[11] = formalin; //PM2,5 Gravity measurments[12] = dustSensor25; measurments[13] = dustSensor10; //BMP180 measurments[14] = bmp.readPressure() / 100; //measurments[19] = (int) (bmp.readTemperature() * 100); measurments[15] = 666; int testMe = 0; for(int k = 0; k < 15; k++) { testMe += measurments[k]; } Serial.print("PRZYKLADOWA CHECK SUMA: "); Serial.println(testMe); radio.write(&measurments, sizeof(measurments)); printSendedData(); } void printSendedData() { Serial.println("Wysłano następujące dane:"); for (int i = 0; i < dataSize; i++) { Serial.print(measurments[i]); Serial.print(" "); } Serial.println(); } void printUARTRecievedData() { byte b; for(int i=0;i<32;i++) { b = (byte)buffer_RTT[i]; //buffer_RTT[i] = 0; Serial.print(b, HEX); Serial.print(" "); } Serial.print("CR1: "); Serial.print(CR1); Serial.print(" = "); Serial.println(CR2); } void printDataBySensor() { Serial.println(); Serial.println("MCP9808: "); Serial.print("Temperatura: "); float c = tempsensor.readTempC(); Serial.print(c); Serial.println("*C"); Serial.println(); Serial.println("SHT31: "); Serial.print("Wilgotność: "); Serial.print(sht31.readHumidity()); Serial.println("%RH"); Serial.print("Temperatura: "); Serial.print(sht31.readTemperature()); Serial.println("*C"); Serial.println(); Serial.println("BMP180: "); Serial.print("Temperatura: "); float bmpTemp = bmp.readTemperature(); Serial.print(bmpTemp); Serial.println("*C"); Serial.print("Ciśnienie: "); Serial.print(bmp.readPressure() / 100); Serial.println("hPa"); Serial.print("Teoretyczna wysokość: "); Serial.print(bmp.readAltitude(101100)); Serial.println("m.n.p.m"); Serial.println(); Serial.println("PMS5003:"); Serial.print("PM 2,5: "); Serial.print(PMS); Serial.println(" /25 ug/m3 "); Serial.print("PM 10 : "); Serial.print(PM10); Serial.println(" /50 ug/m3 "); Serial.print("Formaldehyd : "); Serial.print(formalin); Serial.println(" ug/m3 "); Serial.print("Temperatura: "); Serial.print(PMSTemp); Serial.println("*C"); Serial.print("Wilgotność: "); Serial.print(PMSHigro); Serial.println("%RH"); Serial.println(); Serial.println("PM 2,5 GRAVITY:"); Serial.print("PM 2,5: "); Serial.print(dustSensor25); Serial.println(" /25 ug/m3 "); Serial.print("PM 10 : "); Serial.print(dustSensor10); Serial.println(" /50 ug/m3 "); Serial.println(); Serial.println("STACJA METEO: "); Serial.print("Temperatura: "); c = (transCharToInt(meteoDatabuffer,13,15) - 32.00) * 5.00 / 9.00; Serial.print(c); Serial.println("*C"); Serial.print("Wilgotnosc: "); float metHigro = transCharToInt(meteoDatabuffer,25,26); Serial.print(metHigro); Serial.println("%RH"); Serial.print("Cisnienie: "); float pp = transCharToInt(meteoDatabuffer,28,32) / 10.00; Serial.print(pp); Serial.println("hPa"); Serial.print("Predkośc wiatru: "); float w = transCharToInt(meteoDatabuffer,5,7) / 0.44704; Serial.print(w); Serial.println("m/s"); Serial.print(" "); w = w * 3.6; Serial.print(w); Serial.println("km/h"); Serial.print("Max Predkośc wiatru: "); float m = transCharToInt(meteoDatabuffer,9,11) / 0.44704; Serial.print(m); Serial.println("m/s (ostatnie 5 minut)"); Serial.print(" "); m = m * 3.6; Serial.print(m); Serial.println("km/h (ostatnie 5 minut)"); Serial.print("Kierunek wiatru: "); Serial.print(transCharToInt(meteoDatabuffer,1,3)); Serial.println("C"); Serial.print("Opad (1h): "); float rain1 = transCharToInt(meteoDatabuffer, 17, 19) * 25.40 * 0.01; Serial.print(rain1); Serial.println("mm"); Serial.print("Opad (24h): "); float rain24 = transCharToInt(meteoDatabuffer, 21, 23) * 25.40 * 0.01; Serial.print(rain24); Serial.println("mm"); Serial.println(); } #Autor Bartosz Jakusz. Można replikować nawet w celach komercyjnych. Tylko umieścić autora kodu. import RPi.GPIO as GPIO from lib_nrf24 import NRF24 import time import spidev import struct import os import mysql.connector from mysql.connector import errorcode from datetime import date, datetime, timedelta GPIO.setmode(GPIO.BCM) pipes = [[0xE8, 0xE8, 0xF0, 0xF0, 0xE1], [0xF0, 0xF0, 0xF0, 0xF0, 0xE1]] radio = NRF24(GPIO, spidev.SpiDev()) radio.begin(0, 17) radio.setPayloadSize(32) radio.setChannel(0x76) radio.setDataRate(NRF24.BR_1MBPS) radio.setPALevel(NRF24.PA_MAX) radio.setAutoAck(True) radio.enableDynamicPayloads() radio.enableAckPayload() i = 0 j = 0 sucRate = 0 errRate = 0 totalRec = 0 try: while True: radio.openReadingPipe(1, pipes[1]) #radio.printDetails() radio.startListening() print(" ") print(datetime.now()) radio.print_address_register("RX_ADDR_P0-1", NRF24.RX_ADDR_P0, 2) radio.print_address_register("TX_ADDR", NRF24.TX_ADDR) j = 0 while not radio.available(0): j = j + 1 receivedMessage = [] radio.read(receivedMessage, radio.getDynamicPayloadSize()) radio.stopListening() print("Received: {}".format(receivedMessage)) string = "" floatBytes = [] indx = 0 while indx < 4: floatBytes.append(0) indx = indx + 1 measurments = [] indx = 0 mIndx = 0 tmpStr= "" tmpIndx = 0 crc = 0 if not receivedMessage: print("pusto wywalam sie") else: for n in receivedMessage: #print(tmpIndx) tmpIndx = tmpIndx + 1 floatBytes.insert(indx, n) if(indx >= 1): b1 = floatBytes[0] b2 = floatBytes[1] host = 0 host = b2 host = host << 8 host = host | b1 if(mIndx == 6): print("trt {}".format(host)) if(host > 65536 / 2): host = (65536 - host) * (-1) print("trti {}".format(host)) tmpVar = host / 100 print("trtf {}".format(tmpVar)) elif(mIndx >= 2 and mIndx < 5 or mIndx >= 7 and mIndx <= 8): tmpVar = host / 100 else: tmpVar = host indx = 0 measurments.append(tmpVar) mIndx = mIndx + 1 else: indx = indx + 1 totalRec = totalRec + 1 print(" ") if(host == 666): sucRate = sucRate + 1 print("Odebrano z sukcesem {}/{} transmisji".format(sucRate, totalRec)) try: print("Zaczynamy łacznie z baza") cnx = mysql.connector.connect(host='00.00.00.00',database='db') cursor = cnx.cursor() print("Połączono z baza") #zapytania SQL add_probeTime = ("INSERT INTO MeasrumentDateTime" "(time, date)" "VALUES (%s, %s)") add_MCP8006 = ("INSERT INTO MCP8006 " "(temperature, MeasrumentDateTime_ID) " "VALUES (%(temperature)s, %(timeID)s)") add_SHT31 = ("INSERT INTO SHT31 " "(humidity, temperature, MeasrumentDateTime_ID) " "VALUES (%(humidity)s ,%(temperature)s, %(timeID)s)") add_BMP180 = ("INSERT INTO BMP180" "(pressure, MeasrumentDateTime_ID) " "VALUES (%(pressure)s, %(timeID)s)") add_PMS5003 = ("INSERT INTO PMS5003" "(pm25, pm10, formaldehyd, MeasrumentDateTime_ID) " "VALUES (%(pm25)s, %(pm10)s, %(formaldehyd)s,%(timeID)s)") add_PMGravity = ("INSERT INTO PM25" "(PM25, PM10, MeasrumentDateTime_ID) " "VALUES (%(pm25)s, %(pm10)s, %(timeID)s)") add_meteoSation = ("INSERT INTO MeteoStation" "(windDirection, avgWindSpeedPerMinute, avgWindSpeedPerFiveMinutes, rainfallOneHour, rainfal24Hours, pressure, MeasrumentDateTime_ID) " "VALUES (%(windDir)s, %(avgWind)s, %(maxWind)s, %(rain1h)s, %(rain24h)s, %(pressure)s, %(timeID)s)") #Dodaj obecna godzine currentTimeAndDate = datetime.now() formatted_time = currentTimeAndDate.strftime('%H:%M:%S') currentDate = datetime.now().date() toSendSampleDate = (formatted_time, currentDate) cursor.execute(add_probeTime, toSendSampleDate) sampleTimeID = cursor.lastrowid #Wyslij do bazy MySql dane czujnik temp MCP8006Data = { 'timeID' : sampleTimeID, 'temperature' : measurments[6], } cursor.execute(add_MCP8006, MCP8006Data) #Wyslij do bazy MySql dane higrometr SHT 31 SHT31Data = { 'timeID' : sampleTimeID, 'humidity' : measurments[7], 'temperature' : measurments[8], } cursor.execute(add_SHT31, SHT31Data) #Wyslij do bazy MySql dane barometr BMP180 BMP180Data = { 'timeID' : sampleTimeID, 'pressure' : measurments[14], } cursor.execute(add_BMP180, BMP180Data) #Wyslij do bazy MySql dane czujnik pylu PMS5003 PMS5003Data = { 'timeID' : sampleTimeID, 'pm25' : measurments[9], 'pm10' : measurments[10], 'formaldehyd' : measurments[11], } cursor.execute(add_PMS5003, PMS5003Data) #Wyslij do bazy MySql dane czujnik pylu PM 2,5 Gravity PMGravityData = { 'timeID' : sampleTimeID, 'pm25' : measurments[12], 'pm10' : measurments[13], } cursor.execute(add_PMGravity, PMGravityData) #Wyslij do bazy MySql dane stacja meteo metStationData = { 'timeID' : sampleTimeID, 'windDir' : measurments[0], 'avgWind' : measurments[2], 'maxWind' : measurments[3], 'rain1h' : measurments[4], 'rain24h' : measurments[5], 'pressure' : measurments[1], } cursor.execute(add_meteoSation, metStationData) print("Wysylanie danych na serwer") cnx.commit() cursor.close() print("Wyslano do bazy pomyslnie dane") except mysql.connector.Error as err: if err.errno == errorcode.ER_ACCESS_DENIED_ERROR: print("Something is wrong with your user name or password") elif err.errno == errorcode.ER_BAD_DB_ERROR: print("Database does not exist") else: print(err) else: cnx.close() time.sleep(14) else: print("BLAD TRANSMISJI: nie zgadza sie suma kontrolna") #errRate = errRate + 1 #print("Żle odebrano {}/{} transmisji".format(errRate, totalRec)) print(" ") print("------------------------------------------") #radio.closeReadingPipe(pipes[1]) #radio.stopListening() time.sleep(1) except KeyboardInterrupt: print(i) except: # this catches ALL other exceptions including errors. # You won't get any error messages for debugging # so only use it once your code is working print ("Other error or exception occurred!") finally: GPIO.cleanup() # this ensures a clean exit echo"Autor Bartosz Jakusz. Można wykorzystywać komercyjnie. Wsakazać tylko autora kodu. "; $mydb = new wpdb('usr_db','db','localhost'); $currentTime = $mydb->get_results("SELECT time, date FROM MeasrumentDateTime ORDER BY ID DESC LIMIT 1"); $tempData = $mydb->get_results("SELECT temperature FROM MCP8006, MeasrumentDateTime WHERE MeasrumentDateTime_ID = MeasrumentDateTime.ID ORDER BY MeasrumentDateTime.ID DESC LIMIT 1"); $higroData = $mydb->get_results("SELECT humidity FROM SHT31, MeasrumentDateTime WHERE MeasrumentDateTime_ID = MeasrumentDateTime.ID ORDER BY MeasrumentDateTime.ID DESC LIMIT 1"); $pressureData = $mydb->get_results("SELECT pressure FROM BMP180, MeasrumentDateTime WHERE MeasrumentDateTime_ID = MeasrumentDateTime.ID ORDER BY MeasrumentDateTime.ID DESC LIMIT 1"); $pms5003Data = $mydb->get_results("SELECT pm25, pm10, formaldehyd FROM PMS5003, MeasrumentDateTime WHERE MeasrumentDateTime_ID = MeasrumentDateTime.ID ORDER BY MeasrumentDateTime.ID DESC LIMIT 1"); $gravityData = $mydb->get_results("SELECT PM25, PM10 FROM PM25, MeasrumentDateTime WHERE MeasrumentDateTime_ID = MeasrumentDateTime.ID ORDER BY MeasrumentDateTime.ID DESC LIMIT 1"); $metStationData = $mydb->get_results("SELECT windDirection, avgWindSpeedPerMinute, avgWindSpeedPerFiveMinutes, rainfallOneHour, rainfal24Hours FROM MeteoStation, MeasrumentDateTime WHERE MeasrumentDateTime_ID = MeasrumentDateTime.ID ORDER BY MeasrumentDateTime.ID DESC LIMIT 1"); $avgkmPerHour = $metStationData[0]->avgWindSpeedPerMinute * 3.6; $maxkmPerHour = $metStationData[0]->avgWindSpeedPerFiveMinutes * 3.6; echo "<table>"; echo"<col width=50%>"; echo"<tr>"; echo"<th>Ostatni pomiar z: </th>"; echo"<th></th>"; echo"</tr>"; echo"<tr>"; echo"<th>".$currentTime[0]->time." </th>"; echo"<th>".$currentTime[0]->date." </th>"; echo"</tr>"; echo"</table>"; echo "<br>"; echo "<br>"; $dir = $metStationData[0]->windDirection; $dirName = ""; if ($dir < 45) { $dirName = "Północny"; } elseif ($dir >= 45 or $dir < 90 ) { $dirName = "Północno-Wschodni"; } elseif ($dir >= 90 or $dir < 135 ) { $dirName = "Wschodni"; } elseif ($dir >= 135 or $dir < 180 ) { $dirName = "Południowo-Wschodni"; } elseif ($dir >= 180 or $dir < 225 ) { $dirName = "Południowy"; } elseif ($dir >= 225 or $dir < 270 ) { $dirName = "Południowo-Zachodni"; } elseif ($dir >= 270 or $dir < 315 ) { $dirName = "Zachodni"; } else { $dirName = "Północno-Zachodni"; } echo "<table>"; echo"<col width=50%>"; echo"<tr>"; echo"<th>Temperatura: </th>"; echo"<th>".$tempData[0]->temperature."*C </th>"; echo"</tr>"; echo"<tr>"; echo"<th>Wilgotność: </th>"; echo"<th>".$higroData[0]->humidity."%RH </th>"; echo"</tr>"; echo"<tr>"; echo"<th>Ciśnienie: </th>"; echo"<th>".$pressureData[0]->pressure."hPa </th>"; echo"</tr>"; echo"</table>"; echo "<br>"; echo "<br>"; echo "<table>"; echo"<col width=50%>"; echo"<tr>"; echo"<th>Czystość powietrza: </th>"; echo"<th></th>"; echo"</tr>"; echo"<tr>"; echo"<th>PM 2,5 - norma 25ug/m3: </th>"; echo"<th></th>"; echo"</tr>"; echo"<tr>"; echo"<th>PMS5003: </th>"; echo"<th>".$pms5003Data[0]->pm25."ug/m3</th>"; echo"</tr>"; echo"<tr>"; echo"<th>Gravity: </th>"; echo"<th>".$gravityData[0]->PM25."ug/m3</th>"; echo"</tr>"; echo"</tr>"; echo"<tr>"; echo"<th><br></th>"; echo"<th><br></th>"; echo"</tr>"; echo"</tr>"; echo"<tr>"; echo"<th>PM 10 - norma 50ug/m3</th>"; echo"<th></th>"; echo"</tr>"; echo"<tr>"; echo"<th>PMS5003: </th>"; echo"<th>".$pms5003Data[0]->pm10."ug/m3 </th>"; echo"</tr>"; echo"<tr>"; echo"<th>Gravity: </th>"; echo"<th>".$gravityData[0]->PM10."ug/m3 </th>"; echo"</tr>"; echo"<tr>"; echo"<th><br></th>"; echo"<th><br></th>"; echo"</tr>"; echo"</tr>"; echo"<tr>"; echo"<th>Formaldehyd</th>"; echo"<th></th>"; echo"</tr>"; echo"<tr>"; echo"<th>PMS5003: </th>"; echo"<th>".$pms5003Data[0]->formaldehyd."ug/m3 </th>"; echo"</tr>"; echo"</table>"; echo "<br>"; echo "<br>"; echo "<table>"; echo"<col width=50%>"; echo"<tr>"; echo"<th>Stacja Meteo: </th>"; echo"<th></th>"; echo"</tr>"; echo"<tr>"; echo"<th>Kierunek Wiatru: </th>"; echo"<th>".$dirName."</th>"; echo"</tr>"; echo"<tr>"; echo"<th><br></th>"; echo"<th><br></th>"; echo"</tr>"; echo"<tr>"; echo"<th>Średnia prędkość wiatru (ostatnia minuta): </th>"; echo"<th></th>"; echo"</tr>"; echo"<tr>"; echo"<th> </th>"; echo"<th>".$metStationData[0]->avgWindSpeedPerMinute."m/s</th>"; echo"</tr>"; echo"<tr>"; echo"<th> </th>"; echo"<th>".$avgkmPerHour."km/h</th>"; echo"</tr>"; echo"</tr>"; echo"<tr>"; echo"<th><br></th>"; echo"<th><br></th>"; echo"</tr>"; echo"</tr>"; echo"<tr>"; echo"<th>Max prędkość wiatru (ostatnie 5 min)</th>"; echo"<th></th>"; echo"</tr>"; echo"<tr>"; echo"<th> </th>"; echo"<th>".$metStationData[0]->avgWindSpeedPerFiveMinutes."m/s</th>"; echo"</tr>"; echo"<tr>"; echo"<th> </th>"; echo"<th>".$maxkmPerHour."km/h</th>"; echo"</tr>"; echo"<tr>"; echo"<th><br></th>"; echo"<th><br></th>"; echo"</tr>"; echo"</tr>"; echo"<tr>"; echo"<th>Opad atmosferyczny</th>"; echo"<th></th>"; echo"</tr>"; echo"<tr>"; echo"<th>Ostatnia godzina: </th>"; echo"<th>".$metStationData[0]->rainfallOneHour."mm</th>"; echo"</tr>"; echo"<tr>"; echo"<th>Ostatnia doba: </th>"; echo"<th>".$metStationData[0]->rainfal24Hours."mm </th>"; echo"</tr>"; echo"</table>"; echo"Autor Bartosz Jakusz. Można wykorzystywać komercyjnie. Wsakazać tylko autora kodu. "; $mydb = new wpdb('usr_db','db','localhost'); $pm25DataPMS5003 = $mydb->get_results("SELECT time, date, round(avg(pm25), 0) as pm25PMSMonth FROM PMS5003, MeasrumentDateTime WHERE TIMESTAMP(date, time) > NOW() - INTERVAL 1 MONTH and MeasrumentDateTime_ID = MeasrumentDateTime.ID group by DAY(TIMESTAMP(date, time)) ORDER BY MeasrumentDateTime.ID"); $pm25DataGravity = $mydb->get_results("SELECT time, date, round(avg(PM25.PM25),0) as pm25GravMonth FROM PM25, MeasrumentDateTime WHERE TIMESTAMP(date, time) > NOW() - INTERVAL 1 MONTH and MeasrumentDateTime_ID = MeasrumentDateTime.ID group by DAY(TIMESTAMP(date, time)) ORDER BY MeasrumentDateTime.ID"); $tmpPM25 = []; $i = 0; $someRandData = 25; foreach ($pm25DataGravity as $obj) : array_push($tmpPM25, array(substr((string)$obj->date,5)." ".substr((string)$obj->time,0,-9), (float)$obj->pm25GravMonth, (float)$pm25DataPMS5003[$i]->pm25PMSMonth, (float)$someRandData )); $i++; endforeach; echo"<div id='pm25ChartMonth'></div>"; ?> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script type="text/javascript"> var pm25 = <?php echo json_encode($tmpPM25); ?>; google.charts.load('current', {packages: ['corechart', 'line']}); google.charts.setOnLoadCallback(drawBasic); function drawBasic() { var data = new google.visualization.DataTable(); data.addColumn('string', 'Last Hour'); data.addColumn('number', 'Gravity'); data.addColumn('number', 'PMS5003'); data.addColumn('number', 'Norma'); data.addRows(pm25); var options = { 'title':'Zaniczyszczenie PM 2,5 ostatni miesiąc', 'curveType': 'function', hAxis: { title: 'Godzina' }, vAxis: { title: 'um/m3' }, height: 500 }; var chart = new google.visualization.LineChart(document.getElementById('pm25ChartMonth')); chart.draw(data, options); } </script>
  16. MacintoshPi to mój mały projekt pozwalający na uruchomienie pełnoekranowych systemów Apple: Mac OS 7, Mac OS 8 i Mac OS 9 z dźwiękiem, aktywnym połączeniem internetowym i emulacją modemu pod Raspberry Pi OS Lite. Wszystko bez menadżera X.org, a wyłącznie za pomocą multimedialnej biblioteki SDL2 i z poziomu CLI – Raspberry Pi OS Lite. Dzięki temu emulatory wykorzystują pełną moc Raspberry Pi, są stabilniejsze i użyteczne w połączeniu z dodatkowym retro-oprogramowaniem. Instalacja wymaga uruchomienia jednego skryptu na czystym systemie Raspberry Pi OS Lite i poczekania około dwóch godzin na kompilację i instalację pakietów. Dodatkowo, dzięki zawartemu w projekcie dokumentowi, w dual-boot możliwe jest umieszczenie najszybszego (bare-metal) emulatora Commodore 64/128/PET BMC64 budując w ten sposób ciekawy retro pakiet na jednej karcie SD. Cały projekt MacintoshPi działa z urządzeniami Raspberry Pi Zero 2W, 2, 3, 3B, 3B+ (na razie nie działa z wersją 4). Poniżej film przedstawiający możliwości projektu MacintoshPi: Istnieje system Mac OS 7 dla maliny, ale do tej pory system Mac OS 9 w pełnoekranowym trybie (bez zbędnego menadżera okien - dla Raspberry Pi OS Lite) nie był dostępny. To chyba pierwsza taka implementacja pozwalająca na korzystanie z Mac OS 9 i SDL2 w trybie pełnoekranowym, z dźwiękiem i połączeniem internetowym – nawet na małym Raspberry Pi 2 W (a działa również na Raspbery Pi 2). Zajmuję się tematem emulacji Mac OS kilka lat (bo bardzo lubię retro systemy Apple'a) i temat jest dość złożony (gdyby zaczynać wszystko od początku): problemy z konfiguracją obrazów systemów pod obsługę Internetu, z właściwymi konfigami do emulatorów, z właściwymi opcjami do kompilacji emulatorów oraz (oddzielnie) SDL2 (bo oczywiście SDL z paczki nie zadziała w emulacji), z właściwą kompilacją NetDrivera, z wersjami bibliotek (bo muszą być legacy), problemy z dźwiękiem itp. itd. Oczywiście mówię o problemach, które napotkają początkujący, po czym zniechęcą się i rzucą te emulatory w kąt. Ponieważ rozwiązałem te wszystkie problemy na swoim Raspberry-Macintoshu, to postanowiłem udostępnić to rozwiązanie dla Wszystkich, tak żeby wystarczyło odpalić jeden skrypt i otrzymać wszystkie trzy systemy w jednym pakiecie bez żadnego wysiłku - i tym właśnie jest ten mały projekt. Składniki projektu W skład projektu wchodzą poniższe skrypty bash'owe auto-kompilujące i instalujące dla Raspberry Pi: Emulator Macintosh 68K Basilisk II obsługujący systemy Mac OS 7 (System 7.5.5) i Mac OS 8, Emulator PowerPC SheepShaver obsługujący system Mac OS 9, Emulator Commodore 64/128/Pet VICE, Wirtualny Modem wykorzystujący projekty tty0tty i tcpser, działający z powyższymi dwoma emulatorami dla produktów Apple, Commodore oraz z samym Raspberry Pi OS i pozwalający na łączenie się dowolnym oryginalnym retro-oprogramowaniem terminalowym z dzisiejszymi telnetowymi BBSami, Emulator CD-ROM, DVD-ROM CDEmu, pozwalający na montowanie pod Linuxem obrazów CD (iso, toast, cue/bin, mds/mdf itp.) – działa z emulatorami BasiliskII i SheepShaver oraz z Raspberry Pi OS. Emulatory są automatycznie skonfigurowane do obsługi tego wirtualnego napędu CD-ROM. Spójny launcher uruchamiający te wszystkie systemy w różnych rozdzielczościach (po restarcie) i w różnych konfiguracjach, SyncTERM – program do łączenia się z BBSami z poziomu Raspberry Pi OS kompilowany w połączeniu z biblioteką SDL, Informacje jak uruchomić w dual-boot Raspberry Pi OS z najszybszym emulatorem Commodore dla Raspberry Pi BMC64 – (bare metal/low latency emulator). O moim Macintoshu opartym na Raspberry Pi Moją wersję MacintoshPi napędza Raspberry Pi 3B+. Obudowę Macintosh Classic II zakupiłem na eBay-u – była kompletnie żółta, ale do stanu zgodnego z fabrycznym przywróciłem ją korzystając z wody utlenionej 18% i odpowiedniego naświetlania. Ekran LCD IPS 10,1'' 1024x600px HDMI Waveshare 11870 (zakupiony w Botlandzie) jest obrócony o 180°, aby okablowanie nie przeszkadzało górnej, wąskiej ramce Macintosha. Przestrzeń między płaskim ekranem, a pozostałościami kształtu CRT uzupełniłem wydrukiem 3D, który został zaprojektowany na potrzeby projektu twórców kanału YouTube 2GuysTek. Ekran Waveshare jest nieco zbyt szeroki, ale odpowiednie operowanie plikiem config.txt pozwala software'owo obrócić ekran i ustalić dokładną pozycję wyświetlanego obrazu, dla każdego z systemów lub programów oddzielnie (po każdorazowym restarcie). Ekran jest też nieco zbyt niski, dlatego puste przestrzenie wypełniłem czarnym bristolem i praktycznie nie widać tych elementów (wyglądają jak czarne tło ramki otaczającej ekran) – zalecam jednak wykorzystanie innego monitora, którego rozmiar będzie nieco większy, a dopiero software'owo zmniejszyć rozmiar i ustalić właściwą pozycję wyświetlanego obrazu. Klawiatura i mysz to zestaw Logiteh MK295 Silent Wireless Combo – są tylko nieznacznie zbliżone stylem do dostarczanych w tamtych latach peryferii, ale są też bezprzewodowe i korzystają z pojedynczego dongla bluetooth. Dodałem dwa głośniki podłączone do wejścia audio analog/jack Raspberry Pi 3B+ i do rozgałęźnika. Wszystkie te elementy są zintegrowałem wewnątrz obudowy komputera Apple Macintosh Classic II. Więcej informacji o MacintoshPi można znaleźć na GitHubie projektu: https://github.com/jaromaz/MacintoshPi lub po polsku na mojej stronie prywatnej: https://jm.iq.pl Wzmianka o tym projekcie pojawiła się w serwisie Hackster.io - również na ich Twitterze, chociaż nie wysyłałem im żadnych informacji 🙂 : https://www.hackster.io/news/jaromaz-s-macintoshpi-turns-a-raspberry-pi-into-a-fast-feature-packed-classic-apple-mac-emulator-be88c17f66a8
  17. Witajcie. Mam pytanie. Czy wejście żeńskie tych przewodów (https://botland.com.pl/przewody-polaczeniowe/6174-przewody-polaczeniowe-zensko-meskie-10cm-40szt-5904422304423.html) pasuje do wyjść GPIO RPi 4B (4GB RAM)? Będę wdzięczny za odpowiedź. 🙂
  18. Cześć, mam pomyśł na nowy kurs. Co powiecie na kurs RPi Pico? Myślę, że są osoby, które są zainteresowane tym tematem. 🙂
  19. Cześć, Zaprezentuje swojego robota albo raczej prototyp, który powstał głównie do stworzenia i testowania oprogramowania do zarządzania robotem. Natomiast drugim celem było zebranie danych dla sieci neuronowej. Zadaniem robota było jeżdżenie miedzy liniami i zebranie danych z kamery i sterownika silników. I ostatnim celem było wykorzystanie nauczonej sieci prowadzania robota między liniami. Elektronika i zasilanie: Raspberry Pi 4B WiFi 4GB RAM kamera HD OV5647 5Mpx sterownik silników TB6612FNG siniki i przekładnia (344,2:1) to jakaś stara Tamiya power bank 20000mAh,2A MAX chyba Części mechaniczne i konstrukcyjne: gąsienice i koła wydrukowane (pobrane ze strony: www.printables.com) mocowanie do kamery drukowane Oprogramowanie: https://github.com/noxgle/pt2 Problemy: Sterownik silników jest zasilany bezpośrednio z Raspberry co powdowiało że przy pełnym wypełnieniu pwm "brakowało prądu" i następował restart. Zmniejszenie wypełnienia pwm do 50% pomogło do momentu większego rozładowanie baterii. Robotem podczas zbierania danych sterowała aplikacja uruchomiona na laptopie po sieci WiFI. Dane z kamery były obrabiane były wykorzystaniem biblioteki OpenCV. Sterowanie robotem z nauczoną siecią też odbywało się z wykorzystaniem laptopa. Do nauki została wykorzystana biblioteka fast.ai
  20. Przeglądając listę możliwych integracji Home Assistant w większości jest tam to czego potrzebujemy. Sam byłem w szoku, gdy aplikacja podpowiedziała mi integrację z tunerem audio, o którym bym nawet nie pomyślał że się do tego nadaje. Problem pojawia się, gdy wymyślimy sobie własne DIY, które robi coś unikatowego i chcemy to podłączyć pod automatykę domowa. Jedną z metod jest użycie Template Switch (czyli takiego wirtualnego przełącznika) i powiązanie go funkcją lambda z np. komponentem magistrali UART i komunikowanie się z naszym DIY. Problem w tym, że będziemy musieli poświęcić cały układ WiFi na pomost pomiędzy DIY, a centralką HA. W tym artykule postaram się nakreślić, jak zacząć pisać własne komponenty do ESPHome. Przygotowanie Niby jest do tego instrukcja (custom sensor i custom generic component), ale mimo wszystko po przeczytaniu tego co tam zamieszczono, przejrzeniu przykładów, zapytaniu na oficjalnym kanale na Discordzie, odpowiedź znalazłem dopiero na szarym końcu internetu. Wyjdźmy od tego jak tworzymy aplikacje (wsad np. do ESP8266 tu ESP-01S). Mając postawiony HA i zainstalowany dodatek ESPHome, dodajemy nowy sprzęt: Wybieramy nazwę: Rodzaj płytki: Pomijamy wgrywanie, bo trzeba zmienić coś w konfiguracji. Wybieramy więc edit: Mamy tu kilka domyślnych ustawień: esphome: name: spectrum-display esp8266: board: esp01_1m # Enable logging logger: # Enable Home Assistant API api: ota: password: "xxx" wifi: ssid: !secret wifi_ssid password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "xxx" password: "xxx" captive_portal: Nazwa - potrzebna do mDNS jako hostname. Niestety po aktualizacji coś słabo działa. Na stronie ESPHome sugeruje się używanie statycznego IP, które ma też przyspieszać łączenie: Dlatego dodajemy fragment dotyczący stałego IP: wifi: ssid: !secret wifi_ssid password: !secret wifi_password manual_ip: static_ip: 192.168.0.102 gateway: 192.168.0.1 subnet: 255.255.255.0 # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "xxx" password: "xxx" Co w efekcie zobaczymy w logu: Hasło do WiFi mamy ustawione w ESPHome (secrets). Zapisujemy, wgrywamy podłączając urządzenie przez USB lub wgrywając używając OTA, oczywiście jeżeli znamy hasło i adres urządzenia. W moim przypadku jest ta druga opcja: Gotowe komponenty Komponent to pewna funkcjonalność (nawet bardzo rozbudowana). Np. może to być przełącznik światła dołączonego do konkretnego wyprowadzenia (patrz przykład). W przypadku bardziej rozbudowanych komponentów, np. obsłudze protokołu komunikacji, obsługa zawęża się do wskazania urządzenia (np. DS18B20 1-wire) i podania wyprowadzenia. Takich komponentów możemy dodawać wiele i będą działać niezależnie od siebie. To tak jakbyśmy uruchomili wiele współbieżnych procesów. W praktyce wygląda to bardziej jak kod Arduino, w którym przy pomocy funkcji millis() wykonujemy "współbieżnie" wiele funkcji. Podstawy za nami. Teraz konfigurację można wzbogacić o jakieś podstawowe komponenty. Przykładowo kod do sterowania diodą na płytce pod pinem 2: # Enable Home Assistant API api: light: - platform: binary name: "Onboard LED" output: onboard_led_out output: - id: onboard_led_out platform: gpio pin: GPIO2 Id posłuży nam do dodania tzw. encji czyli kontrolki na panelu widocznym w Home Assistant. Wgrywamy i przechodzimy do głównej strony i edytujemy widok panelu: Od razu widzimy encję: Modyfikujemy wedle uznania: i gotowe! Możemy poklikać w nowo dodany przycisk. Najpewniej logika przycisku będzie odwrócona ze względu na sposób podłączenia LED na płytce ESP-01S. Możemy zmienić to w konfiguracji: output: - id: onboard_led_out platform: gpio pin: GPIO2 inverted: true I działa 🙂 Własny komponent Napisanie własnego komponentu nie jest aż tak trudne. Autorzy uznali, że będą wzorować się na koncepcji kodu Arduino , w którym można wyróżnić bloki setup() i loop(). Problem w tym, że nie wiadomo gdzie zapisać te pliki. Na stronie z poradnikiem tworzenia własnych komponentów jest co prawda informacja: Ale niewiele to wnosi, bo nigdzie nie jest napisane gdzie jest ten katalog... Z pomocą przychodzi test przykładowej konfiguracji, w której chcę dodać jakiś plik: Czyli jest to ścieżka: /config/esphome/ Sprawdźmy jak wygląda zawartość tego katalogu, tylko zanim do teog przejdziemy, potrzeba nam SSH - bo w systemie HA nie ma domyślnie takich udogodnień. W repozytorium znajdujemy SSH, ustawiamy hasło i możemy się zalogować. Użytkownik root, hasło własne, port 22. Używam polecenie ls z dopiskiem -a, aby wyświetlić ukryte pliki i katalogi: la -a /config/esphome/ W tym miejscu można umieścić własne pliki bibliotek - tuż obok widzimy plik yaml konfiguracji. Ja swoje pliki umieszczam w katalogu custom_components tak by nie tworzyć bałaganu: Wewnątrz katalogu tworzę plik fps_meter.h: touch fps_meter.h Do pracy korzystam z WinSCP i VSC - zapis pliku w VSC od razu zdalnie go aktualizuje: Dla testu wpisuję kod służący do wyznaczania częstotliwości odświeżeni: #include "esphome.h" class FPSCounter : public Component, public Sensor { private: const unsigned long INTERVAL = 5000; unsigned long counter; unsigned long last_millis; float fps; public: float get_setup_priority() const override { return esphome::setup_priority::LATE; } void setup() override { last_millis = millis(); int last_button_state = HIGH; } void loop() override { if(millis() - last_millis > INTERVAL) { fps = (1000.0 * counter) / (millis() - last_millis); ESP_LOGD("FPS_COUNTER", "%lu sec passed, FPS: %f", INTERVAL, fps); publish_state(fps); counter = 0; last_millis = millis(); } ++counter; } }; I zapisuję. Do tego jak działa ten kod jeszcze wrócimy, ale na razie istotne jest, że w pętli wykonywane jest sprawdzenie częstości odświeżania i okresowo informacja wysyła jest do HA. Komponent jest czujnikiem (dziedziczy po klasie Sensor) ponieważ zwraca pewne informacje do centralki. Teraz trzeba użyć naszą klasę. Przechodzimy do konfiguracji i najpierw dodajemy plik biblioteki: esphome: name: spectrum-display includes: - custom_components/fps_meter.h oraz tworzymy komponent: api: sensor: - platform: custom lambda: |- auto fps_meter = new FPSCounter(); App.register_component(fps_meter); return {fps_meter}; sensors: name: "FPS counter" light: - platform: binary name: "Onboard LED" output: onboard_led_out output: - id: onboard_led_out platform: gpio pin: GPIO2 inverted: true Dodajemy typ sensor (to po czym dziedziczy nasz komponent), a w funkcji lambda korzystamy ze zdefiniowanej klasy FPSCounter. Dodajemy też nazwę, która może nam się przydać. Wgrywamy nowy kod i w logu zobaczymy, że coś zostało wyznaczone - mamy częstotliwość odświeżania około 60Hz: Możemy też znaleźć nasz "czujnik" wśród encji: i nasze obie kontrolki działają, powiedzmy "równolegle" 🙂 Kod programu Wracając na chwilę do kodu programu: #include "esphome.h" class FPSCounter : public Component, public Sensor { public: float get_setup_priority() const override { return esphome::setup_priority::LATE; } void setup() override { } void loop() override { }; widzimy, że szablon jest dość prosty. Tworzymy klasę, która dziedziczy po komponencie i sensorze, aby móc wysyłać informacje (dostępna staje się wtedy metoda publish_state(). Jak wspomniałem setup i loop to metody, które możemy uzupełnić kodem wykonywanym odpowiednio przy starcie i cyklicznie. get_setup_priority() służy ustaleniu jaki priorytet ma nasza klasa. Możliwych narzędzi jest naprawdę wiele, zainteresowanych odsyłam do lektury dokumentacji i analizy przykładów. Kod klasy możemy rozbić na osobne pliki .cpp i .h. Warto jeszcze wspomnieć o czymś, co mnie bardzo mocno zmyliło - o bibliotekach/narzędziach deweloperskich ESPHome. W kodzie widzimy, że dodajemy plik esphome.h, ale nie jest to biblioteka: W pliku tym mamy podlinkowane biblioteki, które będziemy używać, a sam plik wygenerowany zostanie automatycznie... zostanie, ponieważ nasza biblioteka jest w katalogu /config/esphome, ale przed kompilacją jest kopiowana do katalogu projektu: Czyli dla powtórzenia: nasze pliki bibliotek trzymamy w głównym katalogu np. /config/esphome/custom_components/ w pliku .yaml dodajemy ścieżkę: custom_components/plik_biblioteki.h w kodzie pliku plik_biblioteki.h dodajemy esphome.h jakby był tuż obok, bo przed kompilacją zostanie tam przekopiowany tworząc kod pamietamy żeby podlinkować zawartość katalogu src Kuszące może być edytowanie zawartości katalogu src jednak tu uwaga w pliku README.txt: THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY ESPHome automatically populates the build directory, and any changes to this directory will be removed the next time esphome is run. For modifying esphome's core files, please use a development esphome install, the custom_components folder or the external_components feature. Oznacza to, że ten katalog jest wygenerowany automatycznie na bazie pliku .yaml i nie powinniśmy w nim nic mieszać.
  21. Witajcie, Podczas korzystania z biblioteki PyTorch napotkałem na komunikat "Illegal instruction". Błąd powstaje podczas wykorzystania "torchaudio.transforms.Resample()", a dokładnie w liniach kodu: transform = torchaudio.transforms.Resample(orig_freq=sample_rate, new_freq=new_sample_rate) tensor = transform(tensor) Podana klasa ma za zadanie zmienić sample_rate nagrania dźwiękowego. Czy spotkał się ktoś z was, z czymś takim ? Pozdrawiam ! 😄
  22. Cześć, Napotkałem pewien problem podczas próby instalacji biblioteki PyTorch: Could not find a version that satisfies the requirement torch Wie ktoś może czym jest to spowodowane i jak to naprawić ? Python 3.9.2 Raspbian GNU/Linux 11
  23. Dzisiaj kolej na automatykę domową. Od razu na wstępie: nie cierpię określenia "inteligentny dom". Po pierwsze w większości jest ono nadużywane (co to za "inteligencja", która sprowadza się do możliwości zapalenia światła w łazience przez telefon), po drugie mój dom nie ma być inteligentny: ma być przede wszystkim wygodny. Nie wiem jak dla was - ale dla mnie ważniejsze jest zdublowanie wyłącznika oświetlenia przy moim ulubionym fotelu gdzie zwykłem popijać kawusię i oglądać telewizję, niż kombinowanie z pilotami (które zawsze leżą poza zasięgiem ręki) czy smartfonami (gdzie trzeba włączyć aplikację i wybrać odpowiednią funkcję przedzierając się przez jakieś menu). Podobny system już miałem, ale był on jakoś mało rozbudowywalny, poza tym sterowanie nie było zbyt wygodne. Postanowiłem więc zrobić nowy od zera. Cały system składa się z następujących komponentów: Centralka - czyli moduł główny, z klawiaturą, wyświetlaczem, głośnikiem i innymi bajerami, sterująca bezpośrednio kotłem; Wyłączniki oświetlenia - każdy steruje jednym lub dwoma punktami, wyposażone w różnej maści wyłączniki, czujki i takie tam; Kontroler termometrów - najprostszy ze wszystkich modułów, jego zadaniem jest wyłącznie odczyt temperatury z trzech DS18B20 i przesyłanie wyników do centralki. Najpierw może o centralce. Centralka składa się z dwóch głównych układów: Raspberry Pi Zero 2 W jako główny "mózg" urządzenia i ESP-12E do komunikacji ze światem zewnętrznym (poprzez esp-now z pozostałymi modułami oraz poprzez panel sterujący z użytkownikiem). Ponieważ mój kocioł pozwala jedynie na sterowanie typu "włącz/wyłącz", elektrycznie sprawa jest stosunkowo prosta. Do sterowania służą dwa przekaźniki. Pozostawiłem stary sterownik Euroster 2000, ale w czasie pracy jest on odłączony. Do sterowania służą dwa przekaźniki: jeden odpowiada za odłączenie Eurostera gdy centralka jest gotowa do pracy - tzn. RPi się ładnie zabootował, wie która jest godzina, odczytał dane z pamięci EEPROM i FRAM i ma informacje z przynajmniej jednego termometru. Drugi to właściwy przekaźnik sterujący piecem. Układ wygląda mniej więcej tak: Jak widać, aby centralka mogła sterować kotłem, na pinie READY musi pojawić się jedynka. Rezystor 10k zapewnia, że tranzystor będzie zatkany również w przypadku, gdy pin będzie w stanie wysokiej impedancji lub RPi w ogóle nie będzie zasilany. Diody sygnalizują stan przekaźników: READY (zielona) to gotowość do pracy, FAIL (czerwona) to sygnalizacja przełączenia na Euroster, FIRE (niebieska) zapala się przy włączeniu kotła. Oprócz tego do RPi podłączone są: Konwerter I2S ze wzmacniaczem i głośnikiem Moduł zegara DS3231 z pamięcią EEPROM 4 kB Moduł FRAM 32 kB ESP8266 wraz z układem RESET/BOOT Natomiast ESP oprócz komunikacji z pozostałymi modułami obsługuje: wyświetlacz klawiaturę czujnik odległości czujnik temperatury i ciśnienia fotorezystor (jeszcze nie wiem po co, ale miałem wolne wejście analogowe) ESP8266 jest podłączony tak, żeby umożliwić zarówno współpracę układu z malinką, jak i zdalne programowanie: Ponieważ do programowania ESP8266 używam frameworku Arduino, teoretycznie można zainstalować Arduino IDE na malince (tak też zrobiłem na początku). Uznałem jednak, że nie jesto to zbyt wygodne. Dlatego też kompiluję program na lokalnym pececie z linuksem na pokładzie, a do malinki przesyłam tylko gotowy plik bin. I tu mała uwaga: nie podaję pełnego rozwiązania choćby z tego względu, że w rzeczywistości skrypty są bardziej skomplikowane, ale jest to związane ze specyfiją jinjretnego urządzenia i jego możliwości. Ale zasada działania jest mniej więcej taka, jak niżej. Do przesłania skompilowanego pliku używam swojego wrappera. Zewnętrzne polecenie to: ssh centralka.local /usr/local/bin/esprmt a na wejście podaję plik BIN. Oczywiście połączenie ssh musi być skonfigurowane tak, aby nie trzeba było wpisywać hasła (czyli za pomocą kluczy). Na samej malince jest to już bardziej skomplikowane. Najpierw trzeba było napisać kawałek programu, który zresetuje ESP i wprowadzi w tryb programowania. W tym celu stworzyłem plik /etc/default/esp zawierający opisy pinów: boot=24 prog=23 Program w pythonie wygląda tak: #!/usr/bin/env python #coding: utf-8 cfg={} for line in open('/etc/default/esp'): line=line.strip().split('=') if len(line) == 2: cfg[line[0].strip()] = line[1].strip() bootp=int(cfg['boot']) progp=int(cfg['prog']) import sys arg = sys.argv[1] if arg not in ('boot','prog'): raise Exception('Bad arg') import RPi.GPIO as GPIO, time GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(bootp, GPIO.OUT) GPIO.setup(progp, GPIO.OUT) if arg == 'boot': GPIO.output(progp, 0) GPIO.output(bootp, 1) time.sleep(0.1) GPIO.output(bootp, 0) else: GPIO.output(bootp, 1) time.sleep(0.1) GPIO.output(progp, 1) time.sleep(0.1) GPIO.output(bootp, 0) time.sleep(0.1) GPIO.output(progp, 0) time.sleep(0.1) Po zapisaniu do /usr/local/bin/espy i nadaniu praw do uruchomienia mogę wykonać reset ESP poleceniem: espy boot lub wprowadzić w tryb programowania: espy prog Teraz tylko wystarczy wrzucić program do ESP. Do tego służy skrypt /usr/local/bin/esprmt: #!/bin/bash cat >/tmp/upload.bin /usr/local/bin/espy prog /home/pi/.arduino15/packages/esp8266/tools/python3/3.7.2-post1/python3 -I \ /home/pi/.arduino15/packages/esp8266/hardware/esp8266/3.0.2/tools/upload.py \ --chip esp8266 --port /dev/ttyS0 --baud 230400 \ --before no_reset_no_sync --after soft_reset \ write_flash 0x0 /tmp/upload.bin No i trochę o możliwościach. Ponieważ prekursorem urządzenia był po prostu regulator temperatury (na RPI 1B), i tu kontrola temperatura pozostała głównym zadaniem. Piec może pracować w następujących cyklach: zwykły dzień (rano i wieczorem cieplej, w nocy i w typowych godzinach wyjścia temperatura spada, poza tym w dzień utrzymane 18°). Ze względu na specyfikę naszej pracy na każdy dzień tygodnia mogę ustalić oddzielny program. dzień świąteczny. Jest taki sam jak program "niedziela", w stosunku do zwykłego dnia przesunięta jest godzina poranna (trochę później się wstaje) i nie ma godzin wyjścia. jestem w domu - jak zwykły dzień, ale bez godzin wyjścia wychodzę od-do (godziny wyjścia ustalane indywidualnie) wyjazd (utrzymana stała temperatura nie mniej niż 13°) program indywidualny (tu mogę zaprogramować dokładnie kiedy chcę mieć jaką temperaturę). Cykl pieca na bieżący dzień mogę sobie ustawić z klawiatury wybierając po prostu właściwą pozycję z menu: Oczywiście mogę sobie ustawić dowolne cykle na przyszłość - do tego służy kalendarz. Wszystkie święta (stałe i ruchome) są uwzględniane automatycznie, jak na zdjęciu poniżej: Ważną funkcją jest również możliwość chwilowego włączenia/wyłączenia pieca. W tym trybie mogę ustawić na stałe stan zapalony/zgaszony. Po upływie 5 minut, jeśli nie wcisnąłem żadnego klawisza, urządzenie powraca do trybu automatycznego. Algorytm ustalania temperatury jest stosunkowo prosty, ale wymagał wprowadzenia empirycznych danych, uzyskanych na podstawie obserwacji z poprzedniego sezonu grzewczego. Po prostu: Jeśli temperatura jest niższa niż zadana minus histereza piec jest włączony (rozpoczynamy grzanie) Jeśli piec pracuje a temperatura nie przekracza wartości zadanej plus histereza, piec jest włączony (kontynuujemy grzanie) Jeśli z obliczeń wypada, że trzeba rozpocząć grzanie aby o określonej porze osiągnąć odpowiednią temperaturę piec jest włączony (rozpoczynamy grzanie). W przeciwnym razie piec jest wyłączony. Dochodzą tu dodatkowe czynniki - np. temperatura na zewnątrz wpływająca na czas konieczny do ogrzania mieszkania. Również mimo, iż program uruchamiany jest z crona co minutę, jeśli od ostatniego przełączenia pieca minęło mniej niż pięć minut piec nie będzie przełączany. Wszystkie parametry niedostępne z klawiatury mogę sobie ustawić przeglądarką w sieci domowej. Przykładowo ustawianie programu grzania na konkretny dzień wygląda tak: Oprócz kontroli temperatury centralka ma jeszcze kilka przydatnych funkcji. Wbudowany syntezator mowy (kedrigern) może oznajmić godzinę, temperaturę na zewnątrz czy wygłosić prognozę pogody na dwa dni. Czujnik odległości odpowiada za włączenie wyświetlacza tylko wtedy, gdy ktoś stoi przed centralką. Ponieważ część punktów oświetleniowych w mieszkaniu wyposażona jest w moduły ESP8266, może również zapalać/gasić światło (np. automatycznie zapalić lampkę w sypialni o właściwej porze). Zastosowałem tu czujnik ToF - może się to wydawać przesadą, ale cała centralka wisi w przejściu, i żaden czujnik odbiciowy nie zapewnił mi prawidłowego wykrycia (tzn. nie reagowania na ścianę naprzeciw przy jednoczesnym wykryciu czegoś w pewnej minimalnej odległości). Ale trochę więcej o tych modułach. Taki najprostszy moduł to sterowanie oświetleniem w tzw. "saloniku". Mam tu dwie linie oświetlenia górnego i boczny kinkiet. Z przyczyn technicznych moduły są dwa: jeden po stronie wersalki z przekaźnikiem sterującym kinkietem, drugi w miejscu oryginalnego wyłącznika oświetlenia. Ponieważ moduły komunikują się ze sobą (bez pośrednictwa centralki), każdy z nich może sterować dowolną z trzech linii. Każdy moduł posiada zasilacz, jeden lub dwa przekaźniki (użyłem tu subminiaturowych z dopuszczalnym prądem obciążenia 0.5A, mam nadzieję że przy mocy LED max. 14 W/linię i połączonych równolegle stykach to wystarczy) sterowanych przez BC-817 (takie miałem w szufladzie) oraz oczywiście ESP8266. Użyłem tu "gołych" ESP-12E. Trochę problemów miałem z wyłącznikami. Ponieważ są to przyciski chwilowe - aż chciałoby się użyć przycisków sterujących do rolet. Niestety - nigdzie nie znalazłem takich które mi odpowiadają (na trzy rolety) więc zrobiłem je sam ze zwykłych tact-switchy. Chyba nawet lepiej wyszło: wyłącznik ma grubość ok. centymetra - czyli mniej więcej tyle, ile wystaje ze ściany zwykły wyłącznik podtynkowy. A oto efekt: Jak widać, do wyłącznika doprowadzone są tylko dwa przewody - jest to po prostu typowa klawiatura rezystancyjna. Odpowiednio dobierając rezystory mógłbym teoretycznie osiągnąć niezależny odczyt wszystkich trzech kanałów, ale musiałbym za dużo kombinować. Pominąłem więc niektóre kombinacje (np. wciśnięcie wszystkich trzech) i udało mi się zrobić to na zwykłych rezystorkach (tych z szuflady). Wyłącznik ma następujące funkcje: pojedyncze wciśnięcie klawisza - zapalenie (na górze) lub zgaszenie (na dole) linii; jeśli zapalane jest oświetlenie sufitowe, automatycznie gaszony jest kinkiet na ścianie; podwójne wciśnięcie klawisza na górze - zapalenie linii i wygaszenie pozostałych; podwójne wciśnięcie dowolnego klawisza na dole - zgaszenie wszystkich linii; dłuższe wciśnięcie niebieskiego klawisza na górze - przełączenie lampki stojącej w sypialni (syntezator anonsuje stan zapalona/zgaszona); dłuższe wciśnięcie dowolnego klawisza na dole - zaanonsowanie godziny i temperatury na zewnątrz przez syntezator; dłuższe wciśnięcie lewego i prawego klawisza na dole - odczytanie pogody (przed godziną 16:00 na dziś i jutro, po 16:00 na jutro i pojutrze). Moduł oświetlenia przedpokoju ma trochę inne funkcje. Przede wszystkim wyposażony jest w czujkę PIR, automatycznie zapalając światło kiedy ktoś się w przedpokoju pojawia. Za pomocą wyłącznika można również ręcznie zapalić czy zgasić światło (przydaje się, bo czujka nie widzi całego pomieszczenia, niestety nie mogłem się pozbyć martwej strefy przy szafie) lub nawet całkowicie wyłaczyć czujkę. Sama obudowa do czujki jest chyba jedynym "uniwersalnym" rozwiązaniem, tak więc przy okazji zamieszczam pliki STL i SCAD: czujka.zip. I to chyba by było na tyle - nie chcę się rozpisywać, bo o tym można książkę napisać. W razie jakichś pytań jestem do dyspozycji.
  24. Przedstawiam mój projekt: osiatkowany regał na kwiaty z programowalnym oświetleniem. Hardware Regał jest osiatkowany właśnie z powodu kotów, bo w środku są rośliny dla nich toksyczne. Osiatkowane są boki oraz przód - drzwi, natomiast tył nie ma siatki, bo regał jest wpasowany idealnie we wnękę okienną. Główna konstrukcja regału jest z desek o przekroju 2x5 cm, półki oraz drzwi z desek o grubości 1cm. Siatka stalowa przybita gwoździami. Drzwi regału po zamknięciu trzymają się na magnesy, można dodatkowo zabezpieczyć przed otwarciem przekręcając kółka wkręcone pomiędzy skrzydła drzwi. Warto to robić, bo kot wspinający się po siatce (a robią to, owszem) mógłby pewnie otworzyć drzwi, nawet niecelowo. Hardware raz jeszcze Każda półka oświetlona jest taśmą LED Philips (2500lm na metr, światło zimne, nie pamiętam dokładnej temperatury, ale generalnie rośliny potrzebują zimnego i mocnego), zamontowaną w aluminiowej rynience zapewniającej odprowadzanie ciepła. Zasilane zasilaczem PHILIPS CertaDrive 100W, i diody zużywają większość z tych 100W. Równolegle z zasilaczem do LEDów włączony jest mały zasilacz 5V dla Raspberry Pi Zero W odpowiedzialnego za sterowanie oświetleniem. Pi steruje oświetleniem przez wygodny w użyciu moduł przekaźników. Akurat miałem dwukanałowy moduł, więc chciałem sterować osobno górną i dolną połową regału, ale okazało się, że brakuje mi pół metra przewodu... więc jest inaczej: jeden kanał to dolna i górna półka razem, drugi to dwie środkowe. Teoretycznie można ustawić na jednych półkach rośliny bardziej światłolubne, a na drugich mniej. Zasilacze oraz Raspberry Pi przymocowane są do nogi regału z tyłu, więc nie widać ich bez specjalnego zaglądania, i dobrze, bo wyglądają nieco bałaganiarsko. Do szyby przyklejony jest czujnik światła skalibrowany tak, że wykrywa tylko bezpośrednie słońce padające na okno. Dodatkowo wyprowadzony jest mały panel sterowania w postaci DIP switcha, którym można wymusić stan włączony albo wyłączony na każdym z kanałów, a także wyłączyć czujnik słońca. Software Raspberry Pi zaprogramowany jest w języku Ruby. Program jest naprawdę prosty, czyta plik konfiguracyjny gdzie zapisane są godziny włączenia i wyłączenia obu kanałów, sprawdza stan panelu sterowania oraz czujnika światła, i steruje odpowiednio oświetleniem. Żeby zmienić godziny pracy oświetlenia trzeba się zalogować przez SSH i zmienić plik konfiguracyjny. Robię to parę razy w roku, żeby uniknąć efektu witryny sklepu ogrodniczego, jak na zdjęciu poniżej, który powstaje gdy oświetlenie jest włączone po zachodzie słońca. Oczywiście mógłbym zamiast tego sprawdzać godzinę zachodu słońca w jakimś serwisie, albo obliczać ją, albo mieć drugi światłomierz do wykrywania zmierzchu, ale aktualne prowizoryczne rozwiązanie działa dobrze już od ponad roku, więc chyba tak już zostanie...
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.