Narzędzia użytkownika

Narzędzia witryny


projekty:projektsystemupomiarowokontrolnegonabaziearduino

Różnice

Różnice między wybraną wersją a wersją aktualną.

Odnośnik do tego porównania

Nowa wersja
Poprzednia wersja
projekty:projektsystemupomiarowokontrolnegonabaziearduino [2025/05/07 11:33] – created administratorprojekty: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://doi.org/10.5281/zenodo.15270122|Projekt systemu pomiarowo-kontrolnego na bazie Arduino]]\\+Ostrowski, K. (2025). Projekt systemu pomiarowo-kontrolnego na bazie Arduino (Wersja 1). Zenodo. https://doi.org/10.5281/zenodo.15270122 
 + 
 +======= Arduino: Projekt Systemu pomiarowo-kontrolnego ======= 
 +****\\ 
 +**Kacper Ostrowski**\\ 
 + 
 +====== Wstęp ====== 
 + 
 +{{fiberglass-shelter-satellite-equipment.jpg?direct&200}} 
 + 
 +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, często instalowane w bezpośrednim sąsiedztwie anten komunikacyjnych, w których kluczowe znaczenie ma utrzymanie odpowiednich warunków temperaturowo-wilgotnościowych oraz nadzór nad stanem infrastruktury. 
 + 
 +{{mega.jpg?direct&200}} 
 + 
 +Płytka Arduino Mega 2560 
 + 
 +{{eth_shield.jpg?direct&200}} 
 + 
 +Płytka Arduino Ethernet Shield  
 + 
 + 
 +Celem niniejszej pracy jest przedstawienie projektu oraz realizacji systemu pomiarowo-kontrolnego opartego na platformie Arduino MEGA z wykorzystaniem modułu Ethernet Shield. System ten został zaprojektowany do monitorowania i sterowania środowiskiem wewnątrz kontenera technicznego, w którym znajduje się sprzęt wspierający pracę anteny nadawczo-odbiorczej. 
 + 
 +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, analiza ich poprawności, zapisywanie do bazy danych oraz prezentacja wyników użytkownikowi końcowemu. 
 + 
 +{{arduino_systemy.png}} 
 +Schemat blokowy systemu pomiarowo-kontrolnego 
 + 
 + 
 +System składa się z następujących głównych części: 
 + 
 +  * <WRAP> 
 +**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 
 +    * //Przekładnik prądowy i tor pomiarowy// – umożliwia pomiar poboru prądu przez grzejnik, sygnał przekształcany jest na napięcie i odczytywany przez przetwornik ADC.\\ 
 +**Opis:** przekładnik prądowy zastosowany tutaj to prosty transformator, który wymaga poniższego obwodu do poprawnego działania i konwersji napięć na poprawne takie które można odczytać za pomocą przetwornika analogowego Arduino. 
 +{{precision_rectifier.jpg}} 
 +Schemat obwodu do obsługi przekładnika prądowego razem z pomiarami wykonanymi w symulatorze TINA-TI 
 +</WRAP> 
 +  * <WRAP> 
 +**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// – strona WWW prezentująca aktualne wartości czujników, wykresy oraz umożliwiająca zmianę zakresów monitorowanych parametrów.\\ 
 +**Opis:** Jest to dashboard zbudowany za pomocą narzędzia node-red prezentuje wykresy aktualne parametry pozwala przełączyć zasilanie do grzejnika itp. 
 +</WRAP> 
 +  * **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, umożliwiając szybką reakcję w razie przekroczenia parametrów krytycznych. 
 + 
 +====== Omówienie zasady działania systemu Node-RED ====== 
 + 
 +Node-RED to środowisko programistyczne typu open-source, które umożliwia graficzne projektowanie przepływów danych (tzw. //flow//) przy pomocy połączeń pomiędzy tzw. węzłami (//nodes//). Środowisko to oparte jest na języku JavaScript (Node.js), a konfiguracja logiki systemu odbywa się z wykorzystaniem intuicyjnego interfejsu przeglądarkowego. 
 + 
 +===== Podstawowe pojęcia ===== 
 + 
 +Podstawową jednostką danych w systemie Node-RED jest obiekt ''%%msg%%'', który zawiera dane przekazywane między węzłami. Każdy //node// przyjmuje dane wejściowe, wykonuje określoną operację, a następnie przesyła wynik do kolejnych węzłów w postaci zaktualizowanego obiektu ''%%msg%%''. Obiekt ten ma strukturę podobną do JSON i może zawierać następujące właściwości: 
 + 
 +  * ''%%msg.payload%%'' – główna część wiadomości zawierająca dane przesyłane przez system. 
 +  * ''%%msg.topic%%'' – etykieta opisująca temat wiadomości (używana np. do filtrowania). 
 +  * ''%%msg.timestamp%%'' – znacznik czasu nadania wiadomości. 
 +  * inne niestandardowe właściwości – mogą być dodane przez użytkownika lub przez węzły (np. ''%%msg.sensorType%%'', ''%%msg.alert%%'', itp.). 
 + 
 +===== Przepływ danych między węzłami ===== 
 + 
 +Węzły (//nodes//) są specjalizowanymi blokami funkcyjnymi, które realizują konkretne operacje. Przykładowe typy węzłów: 
 + 
 +  * ''%%inject%%'' – inicjuje przepływ danych (np. co 5 minut). 
 +  * ''%%http request%%'' – pobiera dane z zewnętrznego źródła (np. Arduino). 
 +  * ''%%function%%'' – przetwarza dane przy pomocy kodu JavaScript. 
 +  * ''%%switch%%'' – sprawdza warunki logiczne i kieruje dane na odpowiednie ścieżki. 
 +  * ''%%debug%%'' – służy do testowania i podglądu zawartości wiadomości. 
 +  * ''%%email%%'' – wysyła wiadomości e-mail. 
 +  * ''%%database%%'' – zapisuje dane do bazy (np. MySQL, SQLite). 
 + 
 +W momencie przesłania danych z jednego węzła do drugiego, obiekt ''%%msg%%'' zostaje przekazany dalej. Każdy węzeł może odczytać, zmodyfikować lub rozgałęzić wiadomość, co pozwala na budowanie złożonych procesów przetwarzania informacji. 
 + 
 +===== 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** (''%%inject%%'') uruchamia co 5 minut żądanie HTTP do Arduino. 
 +  - **Węzeł HTTP** pobiera dane pomiarowe (np. temperatura, wilgotność, prąd). 
 +  - **Węzeł funkcyjny** analizuje dane i zapisuje je w obiekcie ''%%msg.payload%%''
 +  - **Węzeł warunkowy** (''%%switch%%'') sprawdza, czy dane znajdują się w dopuszczalnym zakresie. 
 +  - 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**, który tworzy wpis w historii systemu, 
 +    * **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 = "S5"; 
 +// Initialize the result object 
 +let result = { 
 +    analog_sensors: {}, 
 +    digital_inputs: {} 
 +}; 
 + 
 +// Regex patterns to match the sensor readings 
 +let analogSensorPattern = /<tr><td>([^<]+)<\/td><td>([^<]+)<\/td><\/tr>/g; 
 +let digitalSensorPattern = /<tr><td>([^<]+)<\/td><td>([^<]+)<\/td><\/tr>/g; 
 + 
 +// 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> 
 + 
 +<code javascript> 
 +// Extract the payload (HTML content) 
 +let html = msg.payload; 
 + 
 +// Initialize a variable to store the heater status 
 +let heaterStatus = "Unknown"; 
 + 
 +// Use a regular expression to find the heater status 
 +let statusMatch = html.match(/<p class='status-(on|off)'>Status: (ON|OFF)/i); 
 + 
 +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> 
 + 
 +<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, sensorValue] of Object.entries(digitalInputs)) { 
 +    // Push each sensor's name and value as an object to the array 
 +    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> 
 + 
 +<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 "0" 
 +let sensorsWithZeroValue = []; 
 + 
 +// Loop through each sensor in the array 
 +for (let sensor of digitalSensorsArray) { 
 +    // Check if the sensor's name starts with "Waveguide Position" 
 +    if (!sensor.name.startsWith("Waveguide Position")) { 
 +        // Check if the sensor's value is "0" 
 +        if (sensor.value === "0") { 
 +            // Add the sensor to the array of sensors with value "0" 
 +            sensorsWithZeroValue.push(sensor); 
 +        } 
 +    } 
 +
 + 
 +// Check if there are any sensors with value "0" 
 +if (sensorsWithZeroValue.length > 0) { 
 +    // Set the message payload to the array of sensors with value "0" 
 +    msg.payload = sensorsWithZeroValue; 
 +    // Return the message to the next node 
 +    return msg; 
 +} else { 
 +    // If no sensors have value "0", return null (which means the message is discarded) 
 +    return null; 
 +
 +</code> 
 + 
 +<code javascript> 
 +// Access the temperature value from the JSON object 
 +msg.payload = parseInt(msg.payload.analog_sensors["Temperature"]); 
 +msg.name = "Temperature" 
 +msg.container = "S5"; 
 +// Return the modified message 
 +return msg; 
 +</code> 
 + 
 +<code javascript> 
 +// Access the temperature value from the JSON object 
 +msg.payload = parseInt(msg.payload.analog_sensors["Humidity"]); 
 +msg.name = "Humidity"; 
 +msg.container = "S5"; 
 +// Return the modified message 
 +return msg; 
 +</code> 
 + 
 +<code javascript> 
 +// Access the temperature value from the JSON object 
 +msg.payload = msg.payload.analog_sensors["Electric Current"]; 
 +msg.payload = parseFloat(msg.payload.match(/[\d.]+/)[0]); 
 +msg.name = "Electric Current"; 
 +msg.container = "S5"; 
 +// Return the modified message 
 +return msg; 
 +</code> 
 + 
 +<code javascript> 
 +msg.topic = `INSERT INTO \`S5_Temperature\` (\`DATE\`, \`VALUE\`) VALUES (now(), '${msg.payload}');`; 
 + 
 +return msg; 
 +</code> 
 + 
 +<code javascript> 
 +//pierwszy 
 +msg.url = "http://<ip serwer www>/graphs/S5_Temperature_1d.png"; 
 +return msg; 
 +//drugi 
 +msg.url = "http://<ip serwer www>/graphs/S5_Current_1d.png"; 
 +return msg; 
 +//trzeci 
 +msg.url = "http://<ip serwer www>/graphs/S5_Humidity_1d.png"; 
 +return msg; 
 +</code> 
 + 
 +==== Wyjaśnienie zasady działania węzłów ==== 
 + 
 +  - <WRAP> 
 +Węzeł: Extract Data from Web 
 +Węzeł odpowiedzialny za przetwarzanie danych HTML pobranych ze strony WWW. W pierwszej kolejności przypisuje zawartość ''%%msg.payload%%'' do zmiennej ''%%payload%%'' oraz ustawia nazwę kontenera jako ''%%S5%%'' (''%%msg.container%%''). Następnie tworzy pusty obiekt ''%%result%%'', zawierający dwie sekcje: ''%%analog_sensors%%'' oraz ''%%digital_inputs%%''
 +Za pomocą wyrażeń regularnych\\ 
 +''%%/ <tr><td>([^<]+)<\/td><td>([^<]+)<\/td><\/tr>/g%%''\\ 
 +przeszukuje dane HTML w celu wyodrębnienia nazw i wartości sensorów analogowych i cyfrowych. W każdej pętli ''%%while%%'', odnalezione dane przypisywane są do odpowiednich pól w obiekcie ''%%result%%''
 +Na końcu wynikowy obiekt JSON jest przypisywany do ''%%msg.payload%%'' i przekazywany dalej. 
 +</WRAP> 
 +  - <WRAP> 
 +Węzeł: Extract heater status 
 +Węzeł służy do odczytu stanu grzejnika na podstawie treści HTML przekazanej w ''%%msg.payload%%''. Za pomocą wyrażenia regularnego ''%%/<p class=’status-(on|off)’>%%''\\ 
 +''%%Status: (ON|OFF)/i%%'' wyszukiwany jest fragment HTML zawierający informację o stanie urządzenia. 
 +Jeśli dopasowanie zakończy się powodzeniem (''%%if (statusMatch)%%''), z drugiego elementu tablicy wynikowej ''%%statusMatch[2]%%'' pobierany jest status grzejnika (np. ''%%ON%%'' lub ''%%OFF%%''). Wartość ta przypisywana jest do ''%%msg.payload%%'' i przekazywana dalej w przepływie. 
 +</WRAP> 
 +  - <WRAP> 
 +Węzeł: Extract Digital in simpler array 
 +Węzeł przekształca dane wejściowe z obiektu ''%%msg.payload.digital_inputs%%'' na prostszą formę — tablicę obiektów. Na początku tworzona jest pusta tablica ''%%digitalSensorsArray%%'', do której kolejno dodawane są obiekty zawierające nazwę i wartość każdego czujnika cyfrowego (''%%name%%'', ''%%value%%''). 
 +Dane są pozyskiwane za pomocą pętli ''%%for (let [sensorName, sensorValue] of Object.entries(...))%%'', a wynik przypisywany do ''%%msg.payload%%'' jako nowa, uproszczona struktura danych. 
 +</WRAP> 
 +  - <WRAP> 
 +Węzeł: check status 
 +Węzeł analizuje dane wejściowe zawarte w ''%%msg.payload%%'', które stanowią tablicę czujników cyfrowych. Dla każdego czujnika wykonywana jest kontrola — jeśli jego nazwa nie zaczyna się od ''%%"Waveguide Position"%%'' oraz jego wartość wynosi ''%%"0"%%'', to czujnik zostaje dodany do nowej tablicy ''%%sensorsWithZeroValue%%''
 +Jeśli po zakończeniu pętli tablica zawiera jakiekolwiek elementy, zostaje ona ustawiona jako nowe ''%%msg.payload%%'' i przekazana dalej. W przeciwnym przypadku węzeł zwraca ''%%null%%'', co powoduje przerwanie dalszego przetwarzania wiadomości. 
 +</WRAP> 
 +  - <WRAP> 
 +Węzeł: Extract Temperature 
 +Węzeł pobiera wartość temperatury z pola\\ 
 +''%%analog_sensors["Temperature"]%%'' znajdującego się w ''%%msg.payload%%'', a następnie konwertuje ją do liczby całkowitej za pomocą ''%%parseInt(...)%%'', przypisując wynik z powrotem do ''%%msg.payload%%''. Ustawia również identyfikatory ''%%msg.name%%'' oraz ''%%msg.container%%'' odpowiednio na ''%%"Temperature"%%'' i ''%%"S5"%%''
 +</WRAP> 
 +  - <WRAP> 
 +Węzeł: Extract Humidity 
 +Analogicznie do poprzedniego węzła, pobiera wartość wilgotności z\\ 
 +''%%analog_sensors["Humidity"]%%'', konwertuje ją na liczbę całkowitą funkcją ''%%parseInt(...)%%'' i przypisuje do ''%%msg.payload%%''. Ustawia także nazwę parametru i kontenera w ''%%msg.name%%'' oraz ''%%msg.container%%''
 +</WRAP> 
 +  - <WRAP> 
 +Węzeł: Extract Current 
 +Węzeł odpowiedzialny za ekstrakcję wartości natężenia prądu z pola\\ 
 +''%%analog_sensors["Electric Current"]%%''. Używa wyrażenia regularnego\\ 
 +''%%msg.payload.match(/[.̣]+/)%%'' do wyłuskania liczby zmiennoprzecinkowej z tekstu, a następnie przekształca ją do typu ''%%float%%''. Dodaje również pola opisujące nazwę parametru i kontener. 
 +</WRAP> 
 +  - <WRAP> 
 +Węzeł: make SQL query 
 +Generuje zapytanie SQL wstawiające wartość pomiaru temperatury do tabeli ''%%S5_Temperature%%'' z aktualnym znacznikiem czasu. Tworzy pole ''%%msg.topic%%'' zawierające zapytanie w formacie\\ 
 +''%%INSERT INTO ... VALUES (now(), '${msg.payload}')%%''
 +</WRAP> 
 +  - <WRAP> 
 +Węzły: set URL 
 +Każdy z węzłów ustawia odpowiedni adres URL wykresu parametru w polu ''%%msg.url%%'': 
 +    * Pierwszy węzeł: ''%%S5_Temperature_1d.png%%'' 
 +    * Drugi węzeł: ''%%S5_Current_1d.png%%'' 
 +    * Trzeci węzeł: ''%%S5_Humidity_1d.png%%'' 
 +Adresy odnoszą się do zasobów graficznych generowanych przez zewnętrzny serwer WWW. 
 +</WRAP> 
 + 
 +===== 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("temperatureLowerThold","file"); 
 +msg.upperThreshold = global.get("temperatureUpperThold","file"); 
 +return msg; 
 +</code> 
 + 
 +<code javascript> 
 +let value = msg.payload;  // The incoming numeric value 
 +let name = msg.name;  // Variable name 
 +let lowerThreshold = msg.lowerThreshold;  // Lower boundary 
 +let upperThreshold = msg.upperThreshold;  // Upper boundary 
 +let container = msg.container; // Name of the container 
 +msg.url = "http://api.ttcomm.net/graphs/S5_Temperature_1d.png"; 
 +msg.value = value; //used for extracting it for email 
 + 
 +if (value < lowerThreshold || value > upperThreshold) { 
 +    msg.topic = `INSERT INTO \`thold_log\` (\`TIME\`, \`CONTAINER\`, \`LOG\`)  
 +    VALUES (NOW(), '${container}',  
 +    'Parameter ${name} in container ${container} has a value of ${value}, which is outside the set boundaries (${lowerThreshold} - ${upperThreshold}).');`; 
 +    return msg;  // Send the message to the next node 
 +} else { 
 +    return null; 
 +
 +</code> 
 + 
 +==== Wyjaśnienie zasady działania węzłów ==== 
 + 
 +  - <WRAP> 
 +Węzeł: setValuesForThresholdNode 
 +Węzeł ten pobiera z pamięci globalnej zdefiniowane progi temperatury: dolny ''%%temperatureLowerThold%%'' oraz górny ''%%temperatureUpperThold%%'', zapisane w pliku konfiguracyjnym (drugi parametr: ''%%"file"%%''). Ustawia je w wiadomości jako pola ''%%msg.lowerThreshold%%'' oraz ''%%msg.upperThreshold%%'', umożliwiając ich dalsze użycie w logice porównawczej. 
 +</WRAP> 
 +  - <WRAP> 
 +Węzeł: thold 
 +Węzeł służy do porównania wartości pomiaru z zadanymi progami. Na podstawie pól ''%%msg.payload%%'' (wartość), ''%%msg.lowerThreshold%%'', ''%%msg.upperThreshold%%'', ''%%msg.name%%'' oraz ''%%msg.container%%'' wykonywana jest kontrola, czy wartość wychodzi poza zadane granice. 
 +Jeśli tak, to generowane jest zapytanie SQL (''%%msg.topic%%'') dodające rekord do tabeli ''%%thold_log%%'', zawierający czas, nazwę kontenera oraz opis przekroczenia. Dodatkowo przypisywane są pola ''%%msg.url%%'' (link do wykresu) oraz ''%%msg.value%%'' (wykorzystywana np. do wiadomości e-mail). Jeśli wartość mieści się w dopuszczalnych granicach, węzeł zwraca ''%%null%%'', zatrzymując dalszy przepływ wiadomości. 
 +</WRAP> 
 + 
 +===== 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; 
 +</code> 
 + 
 +==== Zasada działania ==== 
 + 
 +Tutaj zasada działania jest bardzo prosta pobieramy wszystko co jest w tabeli ''%%thold_log%%'' a następnie wyświetlamy to w interfejsie w formie 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 = "[email protected]" 
 +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}, which is outside the set boundaries (${msg.lowerThreshold} - ${msg.upperThreshold})<br><a href=\"${msg.url}\">Click here to view graph</a>`; 
 +return msg;; 
 +</code> 
 + 
 +==== Zasada działania ==== 
 + 
 +Tutaj pobieramy z obiektu ''%%msg%%'' potrzebne parametry żeby utworzyć wiadomośc email aby następnie wysłać ją do odpowiednich użytkowników 
 + 
 +===== 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("humidityLowerThold", msg.payload, "file"); 
 +//bloki upper 
 +global.set("currentUpperThold", msg.payload, "file"); 
 +</code> 
 + 
 +==== 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 = "[email protected]" 
 +msg.topic = `Alert: ${msg.container} digital sensors changed status`; 
 +msg.payload = `Sensor in container ${msg.container} status: ${msg.sensorStatusString}`; 
 +return msg; 
 +</code> 
 + 
 +==== 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 ("toast"). Całość interfejsu dopełnia element typu iframe, w którym użytkownik może wygodnie przeglądać wygenerowane wykresy. 
 + 
 +====== Skrypt w Pythonie do generowania grafów ====== 
 + 
 +Skrypt odpowiedzialny jest za automatyczne generowanie wykresów parametrów środowiskowych takich jak temperatura, wilgotność oraz pobór prądu na podstawie danych zgromadzonych w bazie MySQL. Dane te są następnie przetwarzane i wizualizowane w formie wykresów PNG, które mogą być publikowane w sieci lokalnej lub w przeglądarce użytkownika końcowego. 
 + 
 +{{graph.png}} 
 +Przykład grafu wygenerowanego przez skrypt 
 + 
 + 
 +===== Opis działania skryptu ===== 
 + 
 +Skrypt rozpoczyna działanie od zaimportowania niezbędnych bibliotek: ''%%pymysql%%'' do komunikacji z bazą danych, ''%%matplotlib.pyplot%%'' do generowania wykresów, ''%%pandas%%'' do analizy danych oraz ''%%datetime%%'' do obsługi czasu (''%%linia 1–5%%''). 
 + 
 +Funkcja ''%%plot_1d_graph(table_name)%%'' przyjmuje jako argument nazwę tabeli z bazy danych, z której zostaną pobrane dane (''%%linia 7%%''). Następnie nawiązywane jest połączenie z bazą danych MySQL (''%%linia 9–13%%''), a dane z kolumn ''%%DATE%%'' oraz ''%%VALUE%%'' są pobierane do ramki danych ''%%DataFrame%%'' przy użyciu zapytania SQL (''%%linia 16%%''). 
 + 
 +Po pobraniu danych połączenie zostaje zamknięte (''%%linia 19%%''), a kolumna ''%%DATE%%'' jest konwertowana do typu ''%%datetime%%'' (''%%linia 22%%''), co umożliwia filtrowanie danych w zadanym zakresie czasu. 
 + 
 +Zakres czasowy ustalany jest na ostatnie 24 godziny, co realizuje fragment: 
 + 
 +<code python> 
 +start_date = datetime.now() - timedelta(days=1) 
 +    filtered_df = df[df['DATE'] >= start_date] 
 +</code> 
 + 
 +Następnie, za pomocą biblioteki ''%%matplotlib%%'', generowany jest wykres typu liniowego (''%%linia 27–31%%''). Na wykresie umieszczane są podpisy osi oraz tytuł wykresu, który zawiera nazwę tabeli. 
 + 
 +Wygenerowany wykres jest zapisywany jako plik PNG w katalogu ''%%/var/www/html/graphs/%%'' pod nazwą odpowiadającą nazwie tabeli z przyrostkiem ''%%_1d%%'' (''%%linia 34%%''). 
 + 
 +Na końcu, skrypt wywołuje funkcję ''%%plot_1d_graph%%'' trzykrotnie dla różnych tabel: 
 + 
 +  * ''%%S5_Temperature%%'' 
 +  * ''%%S5_Humidity%%'' 
 +  * ''%%S5_Current%%'' 
 + 
 +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='localhost',        # Your MySQL host 
 +user='administrator',    # Your MySQL username 
 +password='PASS',# Your MySQL password 
 +database='mcTTcomm'      # Your database name 
 +
 + 
 +# Fetch data from the specified table 
 +query = f"SELECT DATE, VALUE FROM {table_name}" 
 +df = pd.read_sql(query, conn) 
 + 
 +# Close the connection 
 +conn.close() 
 + 
 +# Convert 'DATE' column to datetime format 
 +df['DATE'] = pd.to_datetime(df['DATE']) 
 + 
 +# Define time range for 1 day 
 +start_date = datetime.now() - timedelta(days=1) 
 +filtered_df = df[df['DATE'] >= start_date] 
 + 
 +# Plotting 
 +plt.figure(figsize=(10, 6)) 
 +plt.plot(filtered_df['DATE'], filtered_df['VALUE'], linestyle='-'
 +plt.title(f'{table_name} Measurements Over Last 1 Day'
 +plt.xlabel('Date'
 +plt.ylabel('Value'
 +plt.grid(True); 
 + 
 +# Save the plot as a PNG file 
 +output_path = f'/var/www/html/graphs/{table_name}_1d.png' 
 +plt.savefig(output_path) 
 +plt.close() 
 + 
 +print(f"1-day graph saved to {output_path}"
 + 
 +# Example usage: 
 +plot_1d_graph('S5_Temperature'
 +plot_1d_graph('S5_Humidity'
 +plot_1d_graph('S5_Current'
 +</code> 
 + 
 +====== 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: temperatury, wilgotności oraz natężenia prądu elektrycznego. Dodatkowo, użytkownik posiada możliwość manualnej kontroli urządzenia grzewczego, które można załączać lub wyłączać z częstotliwością maksymalnie raz na 5 minut. Po prawej stronie znajduje się przestrzeń do wyświetlania aktualnych wykresów przedstawiających historię pomiarów dla ostatniego dnia. 
 + 
 +{{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, wilgotności oraz natężenia prądu elektrycznego. Po lewej stronie widoczna jest aktualna konfiguracja, natomiast po prawej stronie użytkownik może intuicyjnie dostosowywać te wartości przy użyciu suwaków. 
 + 
 +{{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ł {{ :projekty:db.zip |}} 
 +  - Kod dla platformy Arduino MEGA  
 +<code cpp arduino.ino> 
 +#include <Streaming.h>  
 +#include <SimpleDHT.h>  
 +#include <SPI.h> 
 +#include <Ethernet.h> 
 +#include <C:\Users\kostrowski\Desktop\program\AVT5636lib.h> 
 +#include <Servo.h> 
 +#include <C:\Users\kostrowski\Desktop\program\MemoryFree.h> 
 + 
 + 
 +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); 
 +  //delay(3000); 
 + 
 +  //Serial.begin(9600); 
 +  //while (!Serial) { 
 +  //  ; // wait for serial port to connect. Needed for native USB port only 
 +  //} 
 + 
 +  Ethernet.begin(mac, ip); 
 +  server.begin(); 
 +  //Serial.print("server is at "); 
 +  //Serial.println(Ethernet.localIP()); 
 + 
 +
 + 
 +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/1024.0)*analogRead(10))*10; 
 +  //Serial.println(CurrentSensor); 
 + 
 +  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, &temperature, &humidity, NULL)) != SimpleDHTErrSuccess) { 
 +      //Serial.print("Failed to read from DHT sensor, err="); 
 +      //Serial.println(err); 
 +      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) { 
 +    //Serial.println("new client"); 
 +    bool currentLineIsBlank = true; 
 +    while (client.connected()) { 
 +      //Serial.println("test1"); 
 +      if (client.available()) { 
 +        //Serial.println("test2"); 
 +        char c = client.read(); 
 +        //Serial.write(c); 
 +        HTTP_req += c; 
 +        if (c == '\n' && currentLineIsBlank) { 
 +          //Serial.println("test3"); 
 +          if (HTTP_req.indexOf("LED4=On") > -1) { LED_status4 = 1; } 
 +          if (HTTP_req.indexOf("LED4=Off") > -1) { LED_status4 = 0; } 
 +          if (HTTP_req.indexOf("LED3=On") > -1) { LED_status3 = 1; } 
 +          if (HTTP_req.indexOf("LED3=Off") > -1) { LED_status3 = 0; } 
 +          if (HTTP_req.indexOf("LED2=On") > -1) { LED_status2 = 1; } 
 +          if (HTTP_req.indexOf("LED2=Off") > -1) { LED_status2 = 0; } 
 + 
 +          client.println(F("HTTP/1.1 200 OK")); 
 +          client.println("Content-Type: text/html"); 
 +          client.println("Connection: close"); 
 +          //client.println("Refresh: 10; URL=/"); 
 +          client.println(); 
 +          client.println("<!DOCTYPE HTML>"); 
 +          client.println("<html>"); 
 +          client.println("<head>"); 
 +          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"); 
 +          // client.println("<style>"); 
 +          // client.println("body { font-family: Arial, sans-serif; margin: 0; padding: 0; font-size: 12px; }"); 
 +          // client.println("h2 { color: #333; }"); 
 +          // client.println(".container { width: 80%; margin: 0 auto; padding: 20px; }"); 
 +          // client.println("table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }"); 
 +          // client.println("th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }"); 
 +          // client.println("tr:hover { background-color: #f5f5f5; }"); 
 +          // client.println(".btn { padding: 8px 16px; margin: 5px; text-decoration: none; color: white; border: none; display: inline-block; font-size: 12px; }"); 
 +          // client.println(".btn-on { background-color: #4CAF50; }"); 
 +          // client.println(".btn-off { background-color: #f44336; }"); 
 +          // client.println(".status-on { background-color: #39FF14; }"); // Neon green 
 +          // client.println(".status-off { background-color: #FF073A; }"); // Neon red 
 +          // client.println("</style>"); 
 +          client.println("</head>"); 
 +          client.println("<body>"); 
 +          client.println("<div class=\"container\">"); 
 +          client.println("<h2>System Pomiarowo-Kontrolny TTCOMM Sp.z.o.o.</h2>"); 
 +          client.println("<h4>Opracowano przez: Kacper Ostrowski</h4>"); 
 +          client.println("<h4>Wersja z dnia: 11.06.2024</h4>"); 
 +          client.println("<h2>Analog Sensor Readings</h2>"); 
 +          client.println("<table>"); 
 +          client.println("<tr><th>Sensor</th><th>Value</th></tr>"); 
 + 
 +          client.println("<tr><td>Temperature</td><td>" + String(IntTemperature) + " C</td></tr>"); 
 +          client.println("<tr><td>Humidity</td><td>" + String(IntHumidity) + " %</td></tr>"); 
 + 
 +          client.println("<tr><td>Electric Current</td><td>   "+String(CurrentSensor)+"     A</td></tr>"); 
 +          client.println("</table>"); 
 +          client.println("<h2>Digital Input Readings</h2>"); 
 +          client.println("<table>"); 
 +          client.println("<tr><th>Sensor</th><th>Value</th></tr>"); 
 +          client.print("<tr><td>Door Open/Closed</td><td>"); 
 +          client.print(digitalRead(22)); 
 +          client.println("</td></tr>"); 
 +          client.print("<tr><td>Flood sensor</td><td>"); 
 +          client.print(digitalRead(24)); 
 +          client.println("</td></tr>"); 
 +          client.print("<tr><td>Waveguide Position 1</td><td>"); 
 +          client.print(digitalRead(26)); 
 +          client.println("</td></tr>"); 
 +          client.print("<tr><td>Waveguide Position 2</td><td>"); 
 +          client.print(digitalRead(28)); 
 +          client.println("</td></tr>"); 
 +          client.print("<tr><td>Waveguide Power Supply</td><td>"); 
 +          client.print(digitalRead(30)); 
 +          client.println("</td></tr>"); 
 +          client.println("</table>"); 
 +          client.println("<h2>Control Buttons</h2>"); 
 +          client.println("Override Control Buttons:"); 
 +          client.println("<form method='get'>"); 
 +           
 +          // LED4 control and mode selection 
 +          client.println("<p>Waveguide position 1: <button name='LED4' type='submit' class='btn btn-on' value='On'>ON</button> Please check afterwards the status of position in the above table"); 
 +          //client.println("<button name='LED4' type='submit' class='btn btn-off' value='Off'>OFF</button>"); 
 + 
 +          if (LED_status4) { 
 +            myBoard.ledOn(4); 
 +            delay(3000); 
 +            myBoard.ledOff(4); 
 +            delay(3000); 
 +          } else { 
 +            myBoard.ledOff(4); 
 +          } 
 + 
 +          // LED3 control and mode selection 
 +          client.println("<p>Waveguide position 2: <button name='LED3' type='submit' class='btn btn-on' value='On'>ON</button> Please check afterwards the status of position in the above table"); 
 +          //client.println("<button name='LED3' type='submit' class='btn btn-off' value='Off'>OFF</button>"); 
 + 
 +           
 +          if (LED_status3) { 
 +            myBoard.ledOn(3); 
 +            delay(3000); 
 +            myBoard.ledOff(3); 
 +            delay(3000); 
 +          } else { 
 +            myBoard.ledOff(3); 
 +          } 
 + 
 +          // LED2 control and mode selection 
 +          client.println("<p>Room Heater: <button name='LED2' type='submit' class='btn btn-on' value='On'>ON</button>"); 
 +          client.println("<button name='LED2' type='submit' class='btn btn-off' value='Off'>OFF</button>"); 
 + 
 + 
 +          if (LED_status2) { 
 +            myBoard.ledOn(2); 
 +            client.println("<p class='status-on'>Status: ON"); 
 +          } else { 
 +            myBoard.ledOff(2); 
 +            client.println("<p class='status-off'>Status: OFF"); 
 +          } 
 + 
 +          client.println("</form>"); 
 +          client.println("</div>"); 
 +          client.println("</body>"); 
 +          client.println("</html>"); 
 +           
 +          HTTP_req = ""; 
 +          break; 
 +        } 
 +        if (c == '\n') { 
 +          currentLineIsBlank = true; 
 +        } else if (c != '\r') { 
 +          currentLineIsBlank = false; 
 +        } 
 +      } 
 +    } 
 +    client.stop(); 
 + 
 +    //Serial.println("client disconnected"); 
 +  } 
 +
 +</code> 
 + 
  
  
projekty/projektsystemupomiarowokontrolnegonabaziearduino.1746610399.txt.gz · ostatnio zmienione: 2025/05/07 11:33 przez administrator