Różnice między wybraną wersją a wersją aktualną.
Nowa wersja | Poprzednia wersja | ||
projekty:projektsystemupomiarowokontrolnegonabaziearduino [2025/05/07 11:33] – created administrator | projekty:projektsystemupomiarowokontrolnegonabaziearduino [2025/05/16 17:30] (aktualna) – administrator | ||
---|---|---|---|
Linia 1: | Linia 1: | ||
- | Jeżeli chcesz zacytować tą pracę to wejdź na zenodo.org:\\ | + | Jeżeli chcesz zacytować tą pracę to użyj indetyfikatora DOI:\\ |
- | [[https:// | + | Ostrowski, K. (2025). Projekt systemu pomiarowo-kontrolnego na bazie Arduino (Wersja 1). Zenodo. |
+ | |||
+ | ======= Arduino: | ||
+ | ****\\ | ||
+ | **Kacper Ostrowski**\\ | ||
+ | |||
+ | ====== Wstęp ====== | ||
+ | |||
+ | {{fiberglass-shelter-satellite-equipment.jpg? | ||
+ | |||
+ | Przykład kontenera z osprzętem satelitarnym | ||
+ | |||
+ | |||
+ | W dobie dynamicznego rozwoju systemów automatyki i zdalnego nadzoru coraz większe znaczenie zyskują rozwiązania umożliwiające stały monitoring oraz kontrolę parametrów środowiskowych w różnego rodzaju obiektach technicznych. Jednym z takich obiektów są kontenery ze sprzętem elektronicznym, | ||
+ | |||
+ | {{mega.jpg? | ||
+ | |||
+ | Płytka Arduino Mega 2560 | ||
+ | |||
+ | {{eth_shield.jpg? | ||
+ | |||
+ | Płytka Arduino Ethernet Shield | ||
+ | |||
+ | |||
+ | Celem niniejszej pracy jest przedstawienie projektu oraz realizacji | ||
+ | |||
+ | W skład funkcjonalności systemu wchodzi: | ||
+ | |||
+ | * pomiar temperatury i wilgotności powietrza, | ||
+ | * kontrola poboru prądu przez grzejnik znajdujący się wewnątrz kontenera, | ||
+ | * detekcja otwarcia i zamknięcia drzwi, | ||
+ | * monitorowanie obecności cieczy poprzez czujnik zalania. | ||
+ | |||
+ | Zebrane dane są przesyłane za pomocą interfejsu sieciowego, co umożliwia ich zdalny odczyt oraz integrację z zewnętrznymi systemami nadzorującymi. Rozwiązanie to ma na celu zwiększenie niezawodności pracy urządzeń znajdujących się w kontenerze poprzez wczesne wykrywanie nieprawidłowości oraz zapewnienie odpowiednich warunków środowiskowych. | ||
+ | |||
+ | ====== Opis ogólny działania systemu pomiarowo-kontrolnego ====== | ||
+ | |||
+ | Na poniższym schemacie blokowym przedstawiono uproszczoną strukturę działania systemu pomiarowo-kontrolnego zbudowanego w oparciu o mikrokontroler Arduino MEGA oraz środowisko Node-RED pracujące na serwerze. Celem systemu jest pozyskiwanie danych środowiskowych, | ||
+ | |||
+ | {{arduino_systemy.png}} | ||
+ | Schemat blokowy systemu pomiarowo-kontrolnego | ||
+ | |||
+ | |||
+ | System składa się z następujących głównych części: | ||
+ | |||
+ | * < | ||
+ | **Moduł Arduino z Ethernet Shield** – odpowiada za pobieranie danych z czujników oraz ich przesyłanie do serwera za pomocą żądań HTTP. | ||
+ | * //Sensor DHT11// – mierzy temperaturę i wilgotność powietrza.\\ | ||
+ | **Opis:** Zastosowano sensor w tej wersji ponieważ wahania temperatury wewnątrz kontenera nie spadają poniżej zera | ||
+ | * // | ||
+ | **Opis:** przekładnik prądowy zastosowany tutaj to prosty transformator, | ||
+ | {{precision_rectifier.jpg}} | ||
+ | Schemat obwodu do obsługi przekładnika prądowego razem z pomiarami wykonanymi w symulatorze TINA-TI | ||
+ | </ | ||
+ | * < | ||
+ | **Serwer z oprogramowaniem Node-RED** – realizuje główną logikę systemu: | ||
+ | * cykliczne pobieranie danych z Arduino, | ||
+ | * analiza zakresu poprawności danych, | ||
+ | * zapis danych do bazy danych, | ||
+ | * generowanie wykresów i raportów, | ||
+ | * wysyłka powiadomień mailowych. | ||
+ | **Opis poszczególnych modułów: | ||
+ | * //Moduł sprawdzający wartości danych// – decyduje, czy parametry środowiskowe mieszczą się w ustalonym zakresie.\\ | ||
+ | **Opis:** Składa się z kilku części jedna część pobiera informację z płytki Arduino następnie dane są weryfikowane czy nie przekraczają odpowiednich zakresów. | ||
+ | * //Moduł formatujący i wysyłający maile// – wysyła ostrzeżenie w przypadku przekroczenia progów alarmowych.\\ | ||
+ | **Opis:** Pobiera informację jaki parametr został przekroczony na którym kontenerze i wysyła maila | ||
+ | * //Moduł zapisu do bazy danych oraz rysowania wykresów// – dane są zapisywane i przedstawiane graficznie za pomocą zestawu skryptów w języku Python.\\ | ||
+ | **Opis:** Formatuje kwerendę INSERT do bazy danych a następnie wpisuje te dane. Druga część to zestaw skryptów w języku Python które łączą się do bazy danych a następnie pobierają i rysują dane które zostają zapisane w katalogu WWW serwera apache. Panel Dashboard w node-red potem się do nich odnosi. | ||
+ | * //Finalna strona użytkownika// | ||
+ | **Opis:** Jest to dashboard zbudowany za pomocą narzędzia node-red prezentuje wykresy aktualne parametry pozwala przełączyć zasilanie do grzejnika itp. | ||
+ | </ | ||
+ | * **Moduły komunikacyjne i użytkowe: | ||
+ | * //Serwer pocztowy// – obsługuje wysyłkę wiadomości email.\\ | ||
+ | **Opis:** Jest to zewnętrzny serwer pocztowy na który program wysyła maile. Jest to element zewnętrzny nie będziemy omawiać jego implementacji. | ||
+ | * //Baza danych i katalog serwera WWW// – dane są przechowywane i udostępniane użytkownikowi w postaci wykresów i historii pomiarów.\\ | ||
+ | **Opis:** Są to zewnętrzne komponenty które pozwalają na zbieranie danych oraz na udostępnianie wygenerowanych wykresów po sieci. | ||
+ | |||
+ | System został zaprojektowany z myślą o niezawodnym działaniu w trudnych warunkach środowiskowych, | ||
+ | |||
+ | ====== Omówienie zasady działania systemu Node-RED ====== | ||
+ | |||
+ | Node-RED to środowisko programistyczne typu open-source, | ||
+ | |||
+ | ===== Podstawowe pojęcia ===== | ||
+ | |||
+ | Podstawową jednostką danych w systemie Node-RED jest obiekt '' | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * inne niestandardowe właściwości – mogą być dodane przez użytkownika lub przez węzły (np. '' | ||
+ | |||
+ | ===== Przepływ danych między węzłami ===== | ||
+ | |||
+ | Węzły (//nodes//) są specjalizowanymi blokami funkcyjnymi, | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | W momencie przesłania danych z jednego węzła do drugiego, obiekt '' | ||
+ | |||
+ | ===== Zastosowanie w omawianym systemie ===== | ||
+ | |||
+ | W omawianym systemie pomiarowo-kontrolnym Node-RED pełni rolę centralnej logiki zarządzania i przetwarzania danych. Przykładowy przepływ danych wygląda następująco: | ||
+ | |||
+ | - **Wyzwalacz czasowy** ('' | ||
+ | - **Węzeł HTTP** pobiera dane pomiarowe (np. temperatura, | ||
+ | - **Węzeł funkcyjny** analizuje dane i zapisuje je w obiekcie '' | ||
+ | - **Węzeł warunkowy** ('' | ||
+ | - W zależności od wyniku, dane kierowane są do: | ||
+ | * **Węzła bazy danych**, gdzie następuje ich zapis, | ||
+ | * **Węzła logującego**, | ||
+ | * **Węzła wysyłającego e-mail**, w przypadku wystąpienia alarmu. | ||
+ | - Dane są również przekazywane do modułu odpowiedzialnego za generowanie wykresów i prezentowane użytkownikowi w postaci graficznej na stronie WWW. | ||
+ | |||
+ | Dzięki modularnej budowie system Node-RED pozwala w prosty sposób modyfikować lub rozbudowywać logikę działania bez konieczności pisania dużej ilości kodu. Każdy z węzłów można edytować wizualnie, a debugowanie odbywa się w czasie rzeczywistym. | ||
+ | |||
+ | ====== Omówienie i analiza przepływów na platformie Node-RED ====== | ||
+ | |||
+ | {{ard_html.png}} | ||
+ | Przykład strony zwracanej przez Arduino | ||
+ | |||
+ | |||
+ | Środowisko Node-RED zostało wykorzystane jako centralny komponent systemu odpowiedzialny za komunikację z urządzeniem pomiarowym (Arduino), przetwarzanie danych oraz ich dalszą dystrybucję do pozostałych elementów infrastruktury informatycznej (baza danych, serwer WWW, system powiadamiania). Ze względu na swoją modularną budowę oraz intuicyjny interfejs graficzny, Node-RED doskonale sprawdza się w systemach automatyki, monitoringu oraz zbierania danych w czasie rzeczywistym. | ||
+ | |||
+ | Stworzony program został podzielony na zestaw logicznych bloków, z których każdy odpowiada za realizację konkretnego zadania w ramach systemu. Dzięki takiej strukturze możliwe było zapewnienie wysokiej przejrzystości działania oraz łatwości rozbudowy o dodatkowe funkcjonalności. | ||
+ | |||
+ | ===== Bloki: Main Logic i Graphing ===== | ||
+ | |||
+ | {{1.png}} | ||
+ | Widok bloków z aplikacji Node-RED | ||
+ | |||
+ | |||
+ | ==== Listingi poszczególnych węzłów funkcyjnych ==== | ||
+ | |||
+ | <code javascript> | ||
+ | // Extract the payload | ||
+ | let payload = msg.payload; | ||
+ | msg.container = " | ||
+ | // Initialize the result object | ||
+ | let result = { | ||
+ | analog_sensors: | ||
+ | digital_inputs: | ||
+ | }; | ||
+ | |||
+ | // Regex patterns to match the sensor readings | ||
+ | let analogSensorPattern = /< | ||
+ | let digitalSensorPattern = /< | ||
+ | |||
+ | // Extract analog sensor readings | ||
+ | let match; | ||
+ | while (match = analogSensorPattern.exec(payload)) { | ||
+ | let sensorName = match[1].trim(); | ||
+ | let sensorValue = match[2].trim(); | ||
+ | result.analog_sensors[sensorName] = sensorValue; | ||
+ | } | ||
+ | |||
+ | // Extract digital input readings | ||
+ | while (match = digitalSensorPattern.exec(payload)) { | ||
+ | let sensorName = match[1].trim(); | ||
+ | let sensorValue = match[2].trim(); | ||
+ | result.digital_inputs[sensorName] = sensorValue; | ||
+ | } | ||
+ | |||
+ | // Return the result as a JSON object | ||
+ | msg.payload = result; | ||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | <code javascript> | ||
+ | // Extract the payload (HTML content) | ||
+ | let html = msg.payload; | ||
+ | |||
+ | // Initialize a variable to store the heater status | ||
+ | let heaterStatus = " | ||
+ | |||
+ | // Use a regular expression to find the heater status | ||
+ | let statusMatch = html.match(/< | ||
+ | |||
+ | if (statusMatch) { | ||
+ | // Extract the status (ON or OFF) from the match | ||
+ | heaterStatus = statusMatch[2]; | ||
+ | } | ||
+ | |||
+ | // Set the heater status as the new payload | ||
+ | msg.payload = heaterStatus; | ||
+ | |||
+ | // Return the modified message | ||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | <code javascript> | ||
+ | // Initialize an empty array to store digital sensor data | ||
+ | let digitalSensorsArray = []; | ||
+ | |||
+ | // Extract the digital_inputs object from the payload | ||
+ | let digitalInputs = msg.payload.digital_inputs; | ||
+ | |||
+ | // Loop through each key-value pair in the digital_inputs object | ||
+ | for (let [sensorName, | ||
+ | // Push each sensor' | ||
+ | digitalSensorsArray.push({ | ||
+ | name: sensorName, | ||
+ | value: sensorValue | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | // Set the output message payload to the array of digital sensors | ||
+ | msg.payload = digitalSensorsArray; | ||
+ | |||
+ | // Return the modified message | ||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | <code javascript> | ||
+ | // Extract the digital sensors array from the incoming message payload | ||
+ | let digitalSensorsArray = msg.payload; | ||
+ | |||
+ | // Initialize an empty array to store sensors with value " | ||
+ | let sensorsWithZeroValue = []; | ||
+ | |||
+ | // Loop through each sensor in the array | ||
+ | for (let sensor of digitalSensorsArray) { | ||
+ | // Check if the sensor' | ||
+ | if (!sensor.name.startsWith(" | ||
+ | // Check if the sensor' | ||
+ | if (sensor.value === " | ||
+ | // Add the sensor to the array of sensors with value " | ||
+ | sensorsWithZeroValue.push(sensor); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Check if there are any sensors with value " | ||
+ | if (sensorsWithZeroValue.length > 0) { | ||
+ | // Set the message payload to the array of sensors with value " | ||
+ | msg.payload = sensorsWithZeroValue; | ||
+ | // Return the message to the next node | ||
+ | return msg; | ||
+ | } else { | ||
+ | // If no sensors have value " | ||
+ | return null; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <code javascript> | ||
+ | // Access the temperature value from the JSON object | ||
+ | msg.payload = parseInt(msg.payload.analog_sensors[" | ||
+ | msg.name = " | ||
+ | msg.container = " | ||
+ | // Return the modified message | ||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | <code javascript> | ||
+ | // Access the temperature value from the JSON object | ||
+ | msg.payload = parseInt(msg.payload.analog_sensors[" | ||
+ | msg.name = " | ||
+ | msg.container = " | ||
+ | // Return the modified message | ||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | <code javascript> | ||
+ | // Access the temperature value from the JSON object | ||
+ | msg.payload = msg.payload.analog_sensors[" | ||
+ | msg.payload = parseFloat(msg.payload.match(/ | ||
+ | msg.name = " | ||
+ | msg.container = " | ||
+ | // Return the modified message | ||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | <code javascript> | ||
+ | msg.topic = `INSERT INTO \`S5_Temperature\` (\`DATE\`, \`VALUE\`) VALUES (now(), ' | ||
+ | |||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | <code javascript> | ||
+ | // | ||
+ | msg.url = " | ||
+ | return msg; | ||
+ | //drugi | ||
+ | msg.url = " | ||
+ | return msg; | ||
+ | //trzeci | ||
+ | msg.url = " | ||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | ==== Wyjaśnienie zasady działania węzłów ==== | ||
+ | |||
+ | - < | ||
+ | Węzeł: Extract Data from Web | ||
+ | Węzeł odpowiedzialny za przetwarzanie danych HTML pobranych ze strony WWW. W pierwszej kolejności przypisuje zawartość '' | ||
+ | Za pomocą wyrażeń regularnych\\ | ||
+ | '' | ||
+ | przeszukuje dane HTML w celu wyodrębnienia nazw i wartości sensorów analogowych i cyfrowych. W każdej pętli '' | ||
+ | Na końcu wynikowy obiekt JSON jest przypisywany do '' | ||
+ | </ | ||
+ | - < | ||
+ | Węzeł: Extract heater status | ||
+ | Węzeł służy do odczytu stanu grzejnika na podstawie treści HTML przekazanej w '' | ||
+ | '' | ||
+ | Jeśli dopasowanie zakończy się powodzeniem ('' | ||
+ | </ | ||
+ | - < | ||
+ | Węzeł: Extract Digital in simpler array | ||
+ | Węzeł przekształca dane wejściowe z obiektu '' | ||
+ | Dane są pozyskiwane za pomocą pętli '' | ||
+ | </ | ||
+ | - < | ||
+ | Węzeł: check status | ||
+ | Węzeł analizuje dane wejściowe zawarte w '' | ||
+ | Jeśli po zakończeniu pętli tablica zawiera jakiekolwiek elementy, zostaje ona ustawiona jako nowe '' | ||
+ | </ | ||
+ | - < | ||
+ | Węzeł: Extract Temperature | ||
+ | Węzeł pobiera wartość temperatury z pola\\ | ||
+ | '' | ||
+ | </ | ||
+ | - < | ||
+ | Węzeł: Extract Humidity | ||
+ | Analogicznie do poprzedniego węzła, pobiera wartość wilgotności z\\ | ||
+ | '' | ||
+ | </ | ||
+ | - < | ||
+ | Węzeł: Extract Current | ||
+ | Węzeł odpowiedzialny za ekstrakcję wartości natężenia prądu z pola\\ | ||
+ | '' | ||
+ | '' | ||
+ | </ | ||
+ | - < | ||
+ | Węzeł: make SQL query | ||
+ | Generuje zapytanie SQL wstawiające wartość pomiaru temperatury do tabeli '' | ||
+ | '' | ||
+ | </ | ||
+ | - < | ||
+ | Węzły: set URL | ||
+ | Każdy z węzłów ustawia odpowiedni adres URL wykresu parametru w polu '' | ||
+ | * Pierwszy węzeł: '' | ||
+ | * Drugi węzeł: '' | ||
+ | * Trzeci węzeł: '' | ||
+ | Adresy odnoszą się do zasobów graficznych generowanych przez zewnętrzny serwer WWW. | ||
+ | </ | ||
+ | |||
+ | ===== Bloki: Thresholds i CATCH ERRORS ===== | ||
+ | |||
+ | {{2.png}} | ||
+ | Widok bloków z aplikacji Node-RED | ||
+ | |||
+ | |||
+ | ==== Listingi poszczególnych węzłów funkcyjnych ==== | ||
+ | |||
+ | <code javascript> | ||
+ | msg.lowerThreshold = global.get(" | ||
+ | msg.upperThreshold = global.get(" | ||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | <code javascript> | ||
+ | let value = msg.payload; | ||
+ | let name = msg.name; | ||
+ | let lowerThreshold = msg.lowerThreshold; | ||
+ | let upperThreshold = msg.upperThreshold; | ||
+ | let container = msg.container; | ||
+ | msg.url = " | ||
+ | msg.value = value; //used for extracting it for email | ||
+ | |||
+ | if (value < lowerThreshold || value > upperThreshold) { | ||
+ | msg.topic = `INSERT INTO \`thold_log\` (\`TIME\`, \`CONTAINER\`, | ||
+ | VALUES (NOW(), ' | ||
+ | ' | ||
+ | return msg; // Send the message to the next node | ||
+ | } else { | ||
+ | return null; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Wyjaśnienie zasady działania węzłów ==== | ||
+ | |||
+ | - < | ||
+ | Węzeł: setValuesForThresholdNode | ||
+ | Węzeł ten pobiera z pamięci globalnej zdefiniowane progi temperatury: | ||
+ | </ | ||
+ | - < | ||
+ | Węzeł: thold | ||
+ | Węzeł służy do porównania wartości pomiaru z zadanymi progami. Na podstawie pól '' | ||
+ | Jeśli tak, to generowane jest zapytanie SQL ('' | ||
+ | </ | ||
+ | |||
+ | ===== Blok: Show logs in table ===== | ||
+ | |||
+ | {{3.png}} | ||
+ | Widok bloków z aplikacji Node-RED | ||
+ | |||
+ | |||
+ | ==== Listingi poszczególnych węzłów funkcyjnych ==== | ||
+ | |||
+ | <code javascript> | ||
+ | msg.topic = `SELECT * FROM thold_log; | ||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | ==== Zasada działania ==== | ||
+ | |||
+ | Tutaj zasada działania jest bardzo prosta pobieramy wszystko co jest w tabeli '' | ||
+ | |||
+ | ===== Blok: Send Numerical notificaiton from numerical sensors ===== | ||
+ | |||
+ | {{4.png}} | ||
+ | Widok bloków z aplikacji Node-RED | ||
+ | |||
+ | |||
+ | ==== Listingi poszczególnych węzłów funkcyjnych ==== | ||
+ | |||
+ | <code javascript> | ||
+ | msg.from = " | ||
+ | msg.topic = `Alert: ${msg.container} ${msg.name} is outside thresholds`; | ||
+ | msg.payload = `Parameter ${msg.name} in container ${msg.container} has a value of ${msg.value}, | ||
+ | return msg;; | ||
+ | </ | ||
+ | |||
+ | ==== Zasada działania ==== | ||
+ | |||
+ | Tutaj pobieramy z obiektu '' | ||
+ | |||
+ | ===== Blok: Thold settings ===== | ||
+ | |||
+ | {{5.png}} | ||
+ | Widok bloków z aplikacji Node-RED | ||
+ | |||
+ | |||
+ | ==== Listingi poszczególnych węzłów funkcyjnych ==== | ||
+ | |||
+ | <code javascript> | ||
+ | //bloki lower | ||
+ | global.set(" | ||
+ | //bloki upper | ||
+ | global.set(" | ||
+ | </ | ||
+ | |||
+ | ==== Zasada działania ==== | ||
+ | |||
+ | Tutaj ustawiamy wartości globalne dla poszczególnych thresholdów. Są one nastepnie wykorzystywane w poprzednich omawianych tutaj przepływach. | ||
+ | |||
+ | ===== Blok: Send email notification from digital sensors ===== | ||
+ | |||
+ | {{6.png}} | ||
+ | Widok bloków z aplikacji Node-RED | ||
+ | |||
+ | |||
+ | ==== Listingi poszczególnych węzłów funkcyjnych ==== | ||
+ | |||
+ | <code javascript> | ||
+ | msg.from = " | ||
+ | msg.topic = `Alert: ${msg.container} digital sensors changed status`; | ||
+ | msg.payload = `Sensor in container ${msg.container} status: ${msg.sensorStatusString}`; | ||
+ | return msg; | ||
+ | </ | ||
+ | |||
+ | ==== Zasada działania ==== | ||
+ | |||
+ | Ten blok jest potrzebny z racji trochę innej struktury maili dla sensorów z wartościami numerycznymi. | ||
+ | |||
+ | ===== Blok: Python graphing ===== | ||
+ | |||
+ | {{7.png}} | ||
+ | Widok bloków z aplikacji Node-RED | ||
+ | |||
+ | |||
+ | ==== Zasada działania ==== | ||
+ | |||
+ | Przedstawiony przepływ Node-RED realizuje automatyczną oraz ręczną generację wykresów na podstawie skryptów Pythona. Cztery wyzwalacze czasowe (co 5 minut, 8 godzin, 48 godzin, oraz 500 godzin) inicjują wykonanie odpowiednich skryptów generujących wykresy dla okresów: 1, 7, 30 i 365 dni. Dodatkowo, zastosowany został przycisk umożliwiający ręczne wygenerowanie wszystkich wykresów jednocześnie oraz kolejny przycisk służący do powrotu do domyślnego widoku wykresów. Wyniki uruchamiania skryptów prezentowane są użytkownikowi na pulpicie Node-RED poprzez elementy UI, wyświetlające logi z wykonania skryptów oraz powiadomienia (" | ||
+ | |||
+ | ====== Skrypt w Pythonie do generowania grafów ====== | ||
+ | |||
+ | Skrypt odpowiedzialny jest za automatyczne generowanie wykresów parametrów środowiskowych takich jak temperatura, | ||
+ | |||
+ | {{graph.png}} | ||
+ | Przykład grafu wygenerowanego przez skrypt | ||
+ | |||
+ | |||
+ | ===== Opis działania skryptu ===== | ||
+ | |||
+ | Skrypt rozpoczyna działanie od zaimportowania niezbędnych bibliotek: '' | ||
+ | |||
+ | Funkcja '' | ||
+ | |||
+ | Po pobraniu danych połączenie zostaje zamknięte ('' | ||
+ | |||
+ | Zakres czasowy ustalany jest na ostatnie 24 godziny, co realizuje fragment: | ||
+ | |||
+ | <code python> | ||
+ | start_date = datetime.now() - timedelta(days=1) | ||
+ | filtered_df = df[df[' | ||
+ | </ | ||
+ | |||
+ | Następnie, za pomocą biblioteki '' | ||
+ | |||
+ | Wygenerowany wykres jest zapisywany jako plik PNG w katalogu '' | ||
+ | |||
+ | Na końcu, skrypt wywołuje funkcję '' | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | Każde z tych wywołań skutkuje wygenerowaniem osobnego wykresu z ostatnich 24 godzin dla danego parametru. | ||
+ | |||
+ | <code python> | ||
+ | import pymysql | ||
+ | import matplotlib.pyplot as plt | ||
+ | import pandas as pd | ||
+ | from datetime import datetime, timedelta | ||
+ | |||
+ | def plot_1d_graph(table_name): | ||
+ | # Establish connection to the MySQL database | ||
+ | conn = pymysql.connect( | ||
+ | host=' | ||
+ | user=' | ||
+ | password=' | ||
+ | database=' | ||
+ | ) | ||
+ | |||
+ | # Fetch data from the specified table | ||
+ | query = f" | ||
+ | df = pd.read_sql(query, | ||
+ | |||
+ | # Close the connection | ||
+ | conn.close() | ||
+ | |||
+ | # Convert ' | ||
+ | df[' | ||
+ | |||
+ | # Define time range for 1 day | ||
+ | start_date = datetime.now() - timedelta(days=1) | ||
+ | filtered_df = df[df[' | ||
+ | |||
+ | # Plotting | ||
+ | plt.figure(figsize=(10, | ||
+ | plt.plot(filtered_df[' | ||
+ | plt.title(f' | ||
+ | plt.xlabel(' | ||
+ | plt.ylabel(' | ||
+ | plt.grid(True); | ||
+ | |||
+ | # Save the plot as a PNG file | ||
+ | output_path = f'/ | ||
+ | plt.savefig(output_path) | ||
+ | plt.close() | ||
+ | |||
+ | print(f" | ||
+ | |||
+ | # Example usage: | ||
+ | plot_1d_graph(' | ||
+ | plot_1d_graph(' | ||
+ | plot_1d_graph(' | ||
+ | </ | ||
+ | |||
+ | ====== Interfejs Graficzny ====== | ||
+ | |||
+ | {{gui1.png}} | ||
+ | Główna strona interfejsu | ||
+ | |||
+ | |||
+ | Główna strona aplikacji prezentuje kluczowe informacje w czytelnej formie graficznej. Znajdują się tutaj trzy wskaźniki wskazówkowe (gauges), pokazujące odczyty bieżących parametrów środowiskowych: | ||
+ | |||
+ | {{gui2.png}} | ||
+ | Tabela z logami | ||
+ | |||
+ | |||
+ | Zakładka tabeli z logami wyświetla szczegółowe informacje o zdarzeniach systemowych. Każdy rekord zawiera znacznik czasowy konkretnego zdarzenia, identyfikator kontenera, którego dotyczy zdarzenie oraz treść powiadomienia. Logi informują przede wszystkim o przekroczeniach zdefiniowanych progów parametrów środowiskowych (np. temperatura poza dopuszczalnym zakresem). | ||
+ | |||
+ | {{gui3.png}} | ||
+ | Ustawienia progów powiadomień | ||
+ | |||
+ | |||
+ | Interfejs ustawień pozwala użytkownikowi na konfigurację progów upozorowaniowych dla poszczególnych parametrów środowiskowych. Parametry te to górne i dolne limity temperatury, | ||
+ | |||
+ | {{gui4.png}} | ||
+ | Interfejs do przeglądania grafów | ||
+ | |||
+ | |||
+ | Sekcja przeglądania wykresów pozwala na generowanie oraz wizualizację wcześniej zapisanych plików graficznych prezentujących historyczne pomiary parametrów. Po lewej stronie widoczne są logi informujące użytkownika o statusie generacji wykresów wraz z możliwością ich wyczyszczenia. Po prawej stronie znajduje się widok katalogu z wygenerowanymi wykresami, posortowanymi według parametrów oraz zakresów czasowych (np. 1 dzień, 7 dni, 30 dni oraz 1 rok). Użytkownik może również ręcznie wymusić wygenerowanie wszystkich wykresów za pomocą dedykowanego przycisku. | ||
+ | |||
+ | ====== Spisy ====== | ||
+ | |||
+ | |||
+ | |||
+ | ===== Wykaz załączników ===== | ||
+ | |||
+ | - Skrypt bazy danych oraz dane które system zebrał {{ : | ||
+ | - Kod dla platformy Arduino MEGA | ||
+ | <code cpp arduino.ino> | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | |||
+ | AVT5636 myBoard; | ||
+ | Servo myServo; | ||
+ | |||
+ | SimpleDHT11 dht11; | ||
+ | |||
+ | byte mac[] = { | ||
+ | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED | ||
+ | }; | ||
+ | IPAddress ip(192, 168, 80, 53); | ||
+ | |||
+ | // Initialize the Ethernet server library | ||
+ | EthernetServer server(8080); | ||
+ | |||
+ | float temperature; | ||
+ | float humidity; | ||
+ | |||
+ | int term2=0; | ||
+ | int term3=0; | ||
+ | int term4=0; | ||
+ | int term5=0; | ||
+ | |||
+ | uint32_t prevMillis = millis(); | ||
+ | |||
+ | |||
+ | void setup() { | ||
+ | pinMode(22, INPUT_PULLUP); | ||
+ | pinMode(24, INPUT_PULLUP); | ||
+ | pinMode(26, INPUT_PULLUP); | ||
+ | pinMode(28, INPUT_PULLUP); | ||
+ | pinMode(30, INPUT_PULLUP); | ||
+ | myBoard.init(); | ||
+ | myServo.attach(PULSE2_PIN); | ||
+ | // | ||
+ | |||
+ | // | ||
+ | //while (!Serial) { | ||
+ | // ; // wait for serial port to connect. Needed for native USB port only | ||
+ | //} | ||
+ | |||
+ | Ethernet.begin(mac, | ||
+ | server.begin(); | ||
+ | // | ||
+ | // | ||
+ | |||
+ | } | ||
+ | |||
+ | String HTTP_req; | ||
+ | bool LED_status4 = 0; | ||
+ | bool LED_status3 = 0; | ||
+ | bool LED_status2 = 0; | ||
+ | |||
+ | int IntTemperature; | ||
+ | int IntHumidity; | ||
+ | |||
+ | unsigned long previousMillis = 0; | ||
+ | |||
+ | float CurrentSensor = 0; | ||
+ | |||
+ | void loop() { | ||
+ | CurrentSensor = ((5.0/ | ||
+ | // | ||
+ | |||
+ | LED_status4 = 0; | ||
+ | LED_status3 = 0; | ||
+ | |||
+ | unsigned long currentMillis = millis(); | ||
+ | |||
+ | if (currentMillis - previousMillis >= 60000) { | ||
+ | |||
+ | previousMillis = currentMillis; | ||
+ | delay(2000); | ||
+ | |||
+ | byte temperature = 0; | ||
+ | byte humidity = 0; | ||
+ | byte err = SimpleDHTErrSuccess; | ||
+ | |||
+ | if ((err = dht11.read(31, | ||
+ | // | ||
+ | // | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | IntTemperature = temperature; | ||
+ | IntHumidity = humidity; | ||
+ | term2 = temperature; | ||
+ | term3 = humidity; | ||
+ | } | ||
+ | |||
+ | |||
+ | term4 = 444; | ||
+ | term5 = 555; | ||
+ | |||
+ | EthernetClient a = server.available(); | ||
+ | serveWebsite(a); | ||
+ | |||
+ | |||
+ | } | ||
+ | |||
+ | EthernetClient serveWebsite(EthernetClient client){ | ||
+ | if (client) { | ||
+ | // | ||
+ | bool currentLineIsBlank = true; | ||
+ | while (client.connected()) { | ||
+ | // | ||
+ | if (client.available()) { | ||
+ | // | ||
+ | char c = client.read(); | ||
+ | // | ||
+ | HTTP_req += c; | ||
+ | if (c == ' | ||
+ | // | ||
+ | if (HTTP_req.indexOf(" | ||
+ | if (HTTP_req.indexOf(" | ||
+ | if (HTTP_req.indexOf(" | ||
+ | if (HTTP_req.indexOf(" | ||
+ | if (HTTP_req.indexOf(" | ||
+ | if (HTTP_req.indexOf(" | ||
+ | |||
+ | client.println(F(" | ||
+ | client.println(" | ||
+ | client.println(" | ||
+ | // | ||
+ | client.println(); | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | // client.println("< | ||
+ | // client.println(" | ||
+ | // client.println(" | ||
+ | // client.println(" | ||
+ | // client.println(" | ||
+ | // client.println(" | ||
+ | // client.println(" | ||
+ | // client.println(" | ||
+ | // client.println(" | ||
+ | // client.println(" | ||
+ | // client.println(" | ||
+ | // client.println(" | ||
+ | // client.println("</ | ||
+ | client.println("</ | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | |||
+ | client.println("< | ||
+ | client.println("< | ||
+ | |||
+ | client.println("< | ||
+ | client.println("</ | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | client.print("< | ||
+ | client.print(digitalRead(22)); | ||
+ | client.println("</ | ||
+ | client.print("< | ||
+ | client.print(digitalRead(24)); | ||
+ | client.println("</ | ||
+ | client.print("< | ||
+ | client.print(digitalRead(26)); | ||
+ | client.println("</ | ||
+ | client.print("< | ||
+ | client.print(digitalRead(28)); | ||
+ | client.println("</ | ||
+ | client.print("< | ||
+ | client.print(digitalRead(30)); | ||
+ | client.println("</ | ||
+ | client.println("</ | ||
+ | client.println("< | ||
+ | client.println(" | ||
+ | client.println("< | ||
+ | |||
+ | // LED4 control and mode selection | ||
+ | client.println("< | ||
+ | // | ||
+ | |||
+ | if (LED_status4) { | ||
+ | myBoard.ledOn(4); | ||
+ | delay(3000); | ||
+ | myBoard.ledOff(4); | ||
+ | delay(3000); | ||
+ | } else { | ||
+ | myBoard.ledOff(4); | ||
+ | } | ||
+ | |||
+ | // LED3 control and mode selection | ||
+ | client.println("< | ||
+ | // | ||
+ | |||
+ | |||
+ | if (LED_status3) { | ||
+ | myBoard.ledOn(3); | ||
+ | delay(3000); | ||
+ | myBoard.ledOff(3); | ||
+ | delay(3000); | ||
+ | } else { | ||
+ | myBoard.ledOff(3); | ||
+ | } | ||
+ | |||
+ | // LED2 control and mode selection | ||
+ | client.println("< | ||
+ | client.println("< | ||
+ | |||
+ | |||
+ | if (LED_status2) { | ||
+ | myBoard.ledOn(2); | ||
+ | client.println("< | ||
+ | } else { | ||
+ | myBoard.ledOff(2); | ||
+ | client.println("< | ||
+ | } | ||
+ | |||
+ | client.println("</ | ||
+ | client.println("</ | ||
+ | client.println("</ | ||
+ | client.println("</ | ||
+ | |||
+ | HTTP_req = ""; | ||
+ | break; | ||
+ | } | ||
+ | if (c == ' | ||
+ | currentLineIsBlank = true; | ||
+ | } else if (c != ' | ||
+ | currentLineIsBlank = false; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | client.stop(); | ||
+ | |||
+ | // | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||