Przeszukaj forum
Pokazywanie wyników dla tagów 'wordpress'.
Znaleziono 2 wyniki
-
Z Wordpressa na Hugo! Zakładanie własnego bloga na Mikrusie
Leoneq opublikował temat w Artykuły użytkowników
Czy w dzisiejszych czasach warto mieć bloga? Odpowiedzi tutaj mogą być różne, przecież jest już tyle serwisów społecznościowych że i tak nikt nie znajdzie naszej "małej" strony. Z drugiej strony własna strona to też wizytówka, zgromadzenie wiedzy w jednym miejscu, prywatne miejsce wolne od reklam. Uważam że warto pomyśleć o założeniu własnej strony, szczególnie że nie jest to w dzisiejszych czasach drogie - a może być nawet darmowe! W tym poradniku postaram się opisać proces instalacji generatora stron statycznych Hugo na serwerze. Dodatkowo opiszę jeszcze proces instalacji CMSa - Content Management System - który pozwala poczuć się tak, jak by się korzystało z Wordpressa. Haczykiem jest dobra znajomość Linuxa. Dlaczego by po prostu nie zainstalować Wordpressa?! WP się chwali że ponad 43% Internetu jest na nim oparte (no, a przynajmniej teraz). Do działania wymaga pełnego zestawu oprogramowania (serwera www, PHP, bazy danych), co sprawia że nie jest to najlżejsze oprogramowanie. Wygląd strony albo ustalamy sami - i jest to fajne dla "zwykłych" użytkowników korzystających z edytorów WYSIWYG - jednak gdy chcemy zrobić własny motyw (theme) to już się zaczynają schody. Do tego trzeba dodać ogrom wtyczek jakie czasem musimy zainstalować... i nawet jeżeli mamy stronę statyczną, skrypty i style potrafią mieć kilka ładnych megabajtów. Czy to dobrze czy źle? Przecież dzisiaj internet jest powszechny i szybki, 1Mb/s to już standard "lejka" w pakietach. I jest to pytanie na które każdy sam sobie musi odpowiedzieć. Jeżeli nikomu nie przeszkadza WP i jest dla niego wygodny, nie widzę problemu. Z kolei ja uznałem że gdyby większość Internetu wróciła do takich stron statycznych, ile zasobów by było zaoszczędzone! Nie mówiąc o tym że sam czasem mam kłopoty z internetem, a i też nie lubię mieć "bloatware" u siebie - tymbardziej go wspierać. Alternatywą są strony statycznie generowane. Na serwerze mamy pewną strukturę plików, które potem są """kompilowane""" na jedną stronę statyczną, która zawiera surowego HTMLa ze stylami i skryptami. Bez dodatkowych baz danych, pehapów itd. Wyobraźmy sobie że zamiast bazy danych, którą odpytujemy podczas ładowania strony, dane co jakiś czas "pakujemy" do chociażby pliku tekstowego, i ten mały plik pobieramy. Dla wielu stron statycznych, a nawet blogów jest to zupełnie wystarczające. Jeżeli przekonałem do wypróbowania Hugo (bo takiego generatora będziemy używać), zapraszam do dalszego czytania. Jak działa Hugo Nasza strona przed wygenerowaniem to zbiór plików Markdown, oraz potrzebnych mediów (jak zdjęcia, filmy itd). Pojedynczy plik .md to pojedynczy wpis, i tam możemy pisać co chcemy. Kiedy uznamy że chcemy stronę opublikować, generujemy ją - a pliki w katalogu publicznym są aktualizowane, i to tyle. Generator dodaje tylko to co jest potrzebne, więc to czego nie chcemy opublikować nie zostanie dodane. Jest to bardzo dobra alternatywa względem WP dla stron statycznych - czyli wygenerowanych jednorazowo, lub aktualizowanych rzadko. I jest to też lepsza alternatywa niż pisanie HTML "z ręki", edytując pliki Markdown po prostu piszemy sprawniej niż w czystym HTMLu. Do tego strona na Hugo może być repozytorium Git - dzięki czemu aktualizowanie modułów, kopie danych itd. są dość uproszczone. Weźmy na ten przykład stronę którą udało mi się utworzyć jako demo WP, z domyślnym motywem i bez mediów. Z kolei stronę na Hugo wybrałem jako demo motywu Papermod. Po kliknięciu CTRL-F5 (czyli załadowaniu od nowa wszystkich plików), na dole możemy odczytać podsumowanie; strona na WP załadowała się w nieco ponad 2 sekundy, przesyłając ok. 600kB przesłanych danych. Z kolei przykładowy wpis na Hugo to pół sekundy i 75kB! Warto zauważyć mniejszą ilość plików oraz mniejszy rozmiar głównego pliku HTML i CSS względem strony na WP. (oczywiście to porównanie nie jest do końca dobre, więc zachęcam do samodzielnego wypróbowania obu metod!) Platforma testowa Ponieważ swoją główną stronę już dawno postawiłem, nową będę stawiać serwerze Frog z oferty Mikrusa. Pozwolę sobie tutaj na małą reklamę, bo aż żal tego nie robić! Serwery Mikrusa to małe VPSy które zostały stworzone z myślą o małych projektach, nauce, słowem - dla hobbystów. Oferta Frog jest specjalna, bo są to serwery DARMOWE, przeznaczone do nauki. Serwer jest przypisany na czas nieokreślony, wystarczy się logować co 3 miesiące, i jest też idealny do małych projektów IoT! Oczywiście jest to bardzo mały serwer, mający tylko 256MB RAMu. Będzie to jednak idealne do postawienia bloga na Hugo - bo domenę "jakąś" mamy nawet w gratisie, a Wordpress mógłby mieć już pewne problemy z tak małą ilością RAMu. Myślę że jednak wielu Czytelników spojrzy na ten serwer ze strony zastosowania go w branży IoT. Oczywiście nikt nie zabrania podłączyć własnej domeny, płatnej kilkadziesiąt złotych rocznie, do darmowego Froga, i mieć prawie darmową, profesjonalną stronę. Zabrania się za to hakowania 😛 Instalacja LEMP LEMP to modyfikacja pakietu LAMP (Linux Apache MySQL PHP), gdzie Apache zostało zmienione na Nginx. Jest to moja preferencja, więc jeżeli wolisz Apacza (i się na nim znasz) to oczywiście instalujemy to co umiemy i lubimy. Jest to taki pakiet podstawowy do obsługi sieci, nawet jeżeli nie będziemy korzystać z PHP ani MySQL - warto od razu na wstępie mieć je przygotowane, aby w przyszłości z nich po prostu skorzystać. W końcu nasza strona to nie musi być sam blog, to może być też galeria zdjęć, baza rzeczy w warsztacie, czy prywatne Trello - wszystko co tylko chcemy, i pozwala nam na to serwer! Oczywiście, jeżeli na pewno chcemy tylko stronę, możemy zainstalować tylko Nginxa/Apache. Po zalogowaniu się na serwer, wita nas MOTD: Zachęceni, przechodzimy do katalogu ze skryptami, i uruchamiamy skrypt instalujący Nginxa. Na początku wpisałem też "sudo su" aby za każdym razem nie być pytany o hasło. Jeżeli instalacja przebiegła pomyślnie, powinniśmy zobaczyć coś takiego: Ponieważ skupię się na tym jak konkretnie serwer Frog uruchomić z Hugo, nie będę opisywał procesu podpinania domeny - skorzystamy z tej domyślnej. W MOTD dostaliśmy przydzielone porty, całe trzy. Wybieramy dowolny, i edytujemy plik /etc/nginx/http.d/default.conf tak, aby port 80 podmienić na ten nasz. Na końcu, zmieniamy jeszcze katalog root (wskazuje on gdzie jest nasza strona); dodajemy podkatalog /public. Sprawdzamy czy wszystko jest dobrze wpisane, a następnie przeładowujemy Nginxa. nginx -t rc-service nginx restart Trzeba podkreślić tutaj, że Mikrus działa na Alpine Linuxie; na zwykłym Debianie zamiast rc-service byśmy mieli systemctl. Jeżeli stworzymy plik index.html w folderze /var/www/html/public, będziemy mogli go otworzyć wchodząc na naszą stronę (frogNR-port.wykr.es), jednak my od razu przejdziemy do instalacji Hugo. Instalowanie Hugo Aby zainstalować Hugo, najlepiej przejść tutaj i zrobić to zgodnie z poradnikiem. Ponieważ mamy Alpine, użyłem poniższej komendy: apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community hugo cd /var/www # poprzednie pliki w katalogu html możemy usunąć lub zignorować hugo new site html cd html Jak już wcześniej wspomniałem, nasza strona to tak naprawdę repozytorium Gita. Nie jest to obowiązkowe, lecz zalecane więc tak też zrobimy. Zainstalujemy od razu motyw Papermod - ten sam, który jest używany w dokumentacji Mikrusa! git init git submodule add https://github.com/adityatelange/hugo-PaperMod themes/papermod Jest szansa że Git zapyta się o permisje. Jeżeli tak będzie, wystarczy dodać wyjątek (lub naprawić permisje, jeżeli sie nie jest leniwym). Następnie musimy edytować plik hugo.toml; zmieniamy adres na ten jaki dostaliśmy, oraz ustawiamy motyw. Opcjonalnie możemy zmienić język oraz nazwę strony. Pozostało już tylko wygenerować stronę! Wpisujemy komendę "hugo", i jeżeli wszystko się udało - powinniśmy dostać ładną tabelkę: A po wejściu na stronę, nasz blog! Cieszymy się niezmiernie z naszego nowego bloga, ale jak do niego dodawać wpisy?! Musimy wygenerować plik Markdown, a następnie go edytować: Strukturę naszego bloga w katalogu content/ określamy sami. Zachęcam tutaj do zapoznania się z dokumentacją Hugo. Pamiętajmy o zmianie "draft" na false, aby strona została opublikowana! Dodałem na potrzeby prezentacji nagłówek (#), tekst oraz `kod`. Po zapisaniu pliku, ponownie wpisujemy hugo i odświeżamy stronę: I tutaj poradnik mógłbym zakończyć - udało nam się postawić ładnego bloga na darmowym serwerze, jednak podejrzewam że nie każdy lubi korzystać z vima jako edytor wpisów na blogu, a za każdym razem aby coś edytować logować się poprzez SSH. Do strony możemy bowiem dodać CMS - Content Management System - który jest swego rodzaju "frontendem" do Hugo. Dzięki temu wpisy możemy edytować bezpośrednio z przeglądarki, w ładnym edytorze WYSIWYG. Niestety tracimy już tutaj na niezależności, gdyż zaczniemy opierać się o usługi Githuba/Gitlaba oraz Cloudflare. Decyzja zatem ponownie należy do Czytelnika. Instalacja Sveltii Moim ulubionym CMSem jest Sveltia, będącym leciutkim i ładnym edytorem który jest bardzo aktywnie rozwijany przez twórcę. Alternatywą jest Decap CMS, więc ponownie mamy wybór. Polecam teraz zapiąć pasy, ponieważ proces instalacji tego jest dość skomplikowany. Jeżeli nie czujemy się na siłach, możemy spróbować skorzystać z gotowej usługi oferowanej przez Netlify, która ma ten proces znacznie uprościć. Postanowiłem jednak na Sveltię właśnie z tego względu że nasza strona jest u nas, i w razie czego - po prostu stracimy do niej dostęp z przeglądarki (w razie awarii Cloudflare, chociaż jest to mało prawdopodobne). Zacznijmy od podpięcia naszej strony do repozytorium. Skonfigurujmy w systemie nazwę użytkownika oraz mail: Na stronie naszego Gita zakładamy nowe, puste repozytorium. Kopiujemy adres, następnie: git remote add origin https://adres.naszego.repo.pl git add . git commit -m "Pierwszy commit" git push -u origin master Zostaniemy poproszeni o nazwę użytkownika oraz hasło. Jeżeli wszystko się udało, na stronie naszego repozytorium powinniśmy zobaczyć naszą stronę. Następnie tworzymy nowy folder oraz dwa pliki: stronę oraz konfigurację. mkdir static/admin nano static/admin/index.html nano static/admin/config.yml Pierwszy plik to prosta strona która ładuje skrypt CMSa. <!doctype html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="robots" content="noindex" /> <title>Content Manager</title> </head> <body> <script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script> </body> </html> Drugi plik to konfiguracja naszego CMSa. name zmieniamy na github lub gitlab repo zmieniamy na np. uzytkownik/moje_repozytorium branch najlepiej zostawić jak jest. Pozostałe dane to zaproponowana przeze mnie konfiguracja, oczywiście można ją zmienić po zapoznaniu się z dokumentacją Sveltii/Decapa. W skrócie, skonfigurowałem aby posty były w katalogu content/blog, oraz dane które możemy wpisać podczas edycji wpisu. backend: name: github repo: uzytkownik/repozytorium branch: master # opcjonalnie, i tak domyslnie jest master media_folder: static/media public_folder: media collections: - name: 'blog' label: 'Blog' folder: 'content/blog' create: true #slug: '{{slug}}' path: '{{slug}}/index' editor: preview: false media_folder: '' public_folder: '' #-------------------------- fields: - { label: 'Title', name: 'title', widget: 'string' } - { label: 'Draft', name: 'draft', widget: 'boolean' } - { label: 'Publish Date', name: 'date', widget: 'datetime', format: 'YYYY-MM-DDTHH:mm:ssZ' } - { label: 'Description', name: 'description', widget: 'string' } - { label: "Featured Image", name: "image", widget: "image" } - { label: "Tags", name: "tags", widget: "list" } - { label: "Categories", name: "categories", widget: "list" } - { label: 'Body', name: 'body', widget: 'markdown' } Dodatkowo możemy też usunąć plik hugo.toml z głównego katalogu, i stworzyć hugo.yaml z moją przykładową konfiguracją: baseURL: "https://frogNR-port.wykr.es" languageCode: pl-pl title: "testowy blog" theme: "papermod" enableEmoji: true params: headless_cms: engine: "sveltia" site_url: "https://frogNR-port.wykr.es" backend: name: "github" repo: "org/repo" collections: - label: "post" name: "post" folder: "post" create: true sort_by: "date" sort_ascending: false fields: - {label: "Title", name: "title", widget: "string"} - {label: "Publish Date", name: "date", widget: "datetime"} - {label: "Featured Image", name: "thumbnail", widget: "image"} - {label: "Body", name: "body", widget: "markdown"} Po zapisaniu i przeładowaniu dzięki hugo, po przejściu na naszą stronę i dodaniu /admin powinniśmy zobaczyć coś w tym stylu: Jeżeli spróbujemy się zalogować, niestety zostaniemy jedynie zaskoczeni okienkiem z napisem "File not found". I tutaj pojawia się problem: aby uwierzytelnianie było wiarygodne, musi być zatwierdzone przez Githuba lub Gitlaba. Niestety, jeżeli twórca Sveltii by hostował skrypt do zatwierdzania to mógłby się narazić na problemy prawne (przechowywanie danych, RODO itd.) więc jest to obecnie robione za pośrednictwem Cloudflare. Więcej szczegółów jest opisane tutaj. Wchodzimy w tą stronę, i klikamy "Deploy with Workers". Jest opcja użycia Github Pages i innych usług - jednak skoncentrujemy się na tej zalecanej opcji. Zgadzamy się na autoryzację. Następnie logujemy się lub rejestrujemy. Do wpisania są ID użytkownika oraz token. ID użytkownika będzie napisane po prawej stronie w głównym panelu; token wygenerujemy klikając w link "My profile", oraz tworząc nowy token z szablonu "Edit Cloudflare Workers". W przypadku problemów, zapraszam do skorzystania z pomocy w Internecie. Tak uzyskany token wpisujemy w pole, i zatwierdzamy. Zgadzamy się na wszystko, aktywujemy Github Actions. Po kliknięciu Deploy przechodzimy do dashboarda, i wybieramy nowo utworzonego Workera. Tam klikamy "View version", aż w końcu ukaże się adres strony pomocniczej! Kopiujemy go, bo przyda się później. Wracamy do Githuba/Laba. Rejestrujemy nową aplikację: nazwa dowolna (np. Sveltia CMS Authenticator) strona dowolna (np. https://github.com/sveltia/sveltia-cms-auth) opis dowolny lub pusty callback URL: strona którą zapisaliśmy, z dopiskiem /callback Kopiujemy ID klienta oraz Sekret. Wracamy do naszej aplikacji sveltia-cms-auth na Cloudflare. Przechodzimy do Compute (Workers) -> Workers & Pages -> sveltia-cms-auth -> Settings -> Variables and Secrets. Klikamy Add, wklajamy nasze ID oraz sekret. Ważne aby nazwy zmiennych (GITHUB_CLIENT_ID oraz GITHUB_CLIENT_SECRET) się zgadzały!! Klikamy Deploy. Ostatnim krokiem jest powrót na sam początek - edytujemy plik static/admin/config.yml. Dodajemy poniższą linijkę: backend: ... base_url: https://strona.do.workers.dev Wpisujemy hugo, klikamy CTRL+F5, i tym razem po próbie zalogowania powinniśmy dostać przepiękne okienko: I po zalogowaniu się, panel główny. Tak wygląda edytor WYSIWYG; wszystkie te formatowania zostały wyklikane z górnej belki: Po kliknięciu w górnym prawym rogu na ikonkę M, będziemy mogli edytować artykuł w surowym Markdownie. Po kliknięciu Save jednak nic się nie stało! Możemy albo manualnie aktualizować stronę wywołując Hugo, lub trochę leniwa ale działająca opcja - małego crontaba który to robi za nas np. co minutę. Tutaj już pozostawiam to do wyboru Czytelnikowi. Oczywiście przed zbudowaniem strony trzeba "zassać" zmiany z repozytorium - wykonując polecenie git pull. Zdjęcia możemy wybierać z naszej własnej biblioteki, bezpośrednio z linku, lub z biblioteki stockowych zdjęć. Polecam Pixabay! I po wszystkim udało nam się stworzyć ładnego bloga, przypominającego Wordpressa, ale za to o wiele czystszego. Proces instalacji nie jest przyjemny, ale na pewno satysfakcjonujący. Oczywiście jeżeli potrzebujemy stronę "wizytówkę" - ściągamy gotowy motyw do Hugo, wpisujemy to co chcemy, polecenie hugo i gotowe. Poradnik ten napisałem, bo w Internecie jest dość mało informacji o instalacji Decapa/Sveltii bez Netlify, a rozgryzanie tego samemu zajęło mi tydzień. Jeżeli ktoś chciałby motyw do bloga z "prawdziwego zdarzenia", bardzo polecam zobaczyć demo motywu Stack. Zatem udało nam się zainstalować wspólnie generator stron, wraz z ładnym edytorem, a to wszystko dlatego że lubimy lekkie rozwiązania opensource. Jestem świadomy że poradnik nie pokazuje najprostszej drogi, więc ponownie zachęcam do spróbowania z Netlify! Pozostaje mi życzyć powodzenia i przyjemnego pisania stron! -
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>
- 5 odpowiedzi
-
- 6
-
-
-
- Arduino
- Raspberry Pi
-
(i 3 więcej)
Tagi: