Monitorowanie Temperatury w Serwerowni
W dzisiejszych czasach prawie każda firma posiada mniejszą lub większa serwerownie, która powinna być przed administratorów monitorowana. Ponieważ monitorowanie temperatury w serwerowni dziś to bardzo ważny aspekt bezpieczeństwa. Temperatura oraz wilgotność ma bezpośredni wpływ na żywotność urządzeń znajdujących się w serwerowni. Dlatego też, monitorowanie temperatury oraz wilgotności jest nie tylko zalecane, ale często obowiązkowe.
Współcześnie, kiedy serwerownie są coraz większe i bardziej skomplikowane, rola monitorowania temperatury staje się fundamentem efektywnego zarządzania infrastrukturą IT.
W niniejszym artykule przedstawię sposób na proste ale jednocześnie skuteczne monitorowanie temperatury oraz wilgotności w serwerowni.
Jaka temperatura i wilgotność w serwerowni?
Istnieją standardy, takie jak ASHRAE (American Society of Heating, Refrigerating and Air-Conditioning Engineers), które także podają wytyczne dotyczące temperatury i wilgotności w serwerowniach.
Bardzo dużo wykresów oraz opinii można znaleźć na internecie. Poniżej moje osobiste opracowanie na podstawie aktualnych danych oraz własnych doświadczeń.
Prawda jest taka że im bardziej zawansowana serwerownia tym łatwiej jest utrzymać idealne parametry, no ale nie zawsze jest idealnie. Staramy się utrzymać chociaż te dopuszczalne.
Przygotowanie środowiska
Zaczynamy od aktualizacji listy pakietów
apt update
Instalacja Pakietów
apt install php php-cgi php-mysqli php-pear php-mbstring libapache2-mod-php php-common php-phpseclib php-mysql mariadb-server mariadb-client -y
Konfiguracja bezpieczeństwa
mysql_secure_installation
(Optymalne) Odpowiedzi na pytania: n,y,y,y,y,y
Pobieranie i instalacja phpMyAdmin
wget -P Downloads https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-all-languages.tar.gz
mkdir /var/www/html/phpmyadmin
tar xvf phpMyAdmin-latest-all-languages.tar.gz --strip-components=1 -C /var/www/html/phpmyadmin
cp /var/www/html/phpmyadmin/config.sample.inc.php /var/www/html/phpmyadmin/config.inc.php
Konfiguracja tajnego klucza phpMyAdmin
nano /var/www/html/phpmyadmin/config.inc.php
$cfg['blowfish_secret'] = 'JOFw435365IScA&Q!cDugr!lSfuAz*OW';
Edycji pliku konfiguracyjnego phpMyAdmin i ustawienia sekret Blowfish, który jest używany do szyfrowania ciasteczek należy podać własne
Ustawienia uprawnień i właściciela dla phpMyAdmin:
chmod 660 /var/www/html/phpmyadmin/config.inc.php
chown -R www-data:www-data /var/www/html/phpmyadmin
Tworzenie użytkownika i nadanie uprawnień (przykład)
CREATE USER 'root'@'localhost' IDENTIFIED BY 'tomek';
GRANT ALL PRIVILEGES ON *.* TO 'tomek'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
EXIT;
Jak już wszystko mamy skonfigurowane, nadszedł czas aby umieścić nasze pliki. Pliku umieszczamy w katalogi /var/www/html
Pliki PHP
index.php
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wykres</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>
<body class="d-flex justify-content-center mt-3">
<div class="d-flex flex-column justify-content-center align-items-center w-25">
<h2>Lista czujników</h2>
<ul class="list-group w-100">
<?php
include_once("config.php");
// Pobranie listy czujników
$query = $pdo -> prepare("SELECT ID, nazwa FROM listaczujnikow");
$query -> execute();
$lista = $query->fetchAll(PDO::FETCH_ASSOC);
//wygenerowanie listy czujników
foreach ($lista as $one) {
echo "<li class='list-group-item d-flex flex-row justify-content-between pe-5 ps-5 pt-3 pb-3'> <h3>" . $one['nazwa'] . "</h3>";
echo "<a href='wykres.php?id=" . $one['ID'] . "' class='btn btn-primary'>Zobacz</a></li>";
}
?>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</body>
</html>
config.php
<?php
// Zdefiniowanie danych do logowania do bazy danych
define("DBLOGIN", "login");
define("DBPASS", "haslo");
define("DBADDRESS", "localhost");
define("DBNAME", "baza");
define("DELAY", 1);
// Stworzenie łączenia z bazą danych
$pdo;
try {
$dsn = "mysql:host=" . DBADDRESS . ";dbname=" . DBNAME . ";charset=utf8mb4";
$pdo = new PDO($dsn, DBLOGIN, DBPASS);
} catch (PDOException $e) {
echo 'Błąd połączenia: ' . $e->getMessage();
}
?>
cron.php
<?php
include_once("config.php");
// Sprawdzenie czy już pobrać dane
$godzina = date("H"); // Pobiera godzinę w formacie 24-godzinnym
$minuta = date("i"); // Pobiera minutę
$sumaMinut = 60 * $godzina + $minuta;
if ($sumaMinut % DELAY == 0) {
// Pobranie IP i ID czujników
$query = $pdo->prepare("SELECT IP, ID FROM listaczujnikow");
$query -> execute();
$lista = $query -> fetchAll(PDO::FETCH_ASSOC);
foreach ($lista as $one) {
try {
// Pobranie pliku
$ch = curl_init($one['IP'] . "/dane");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$plik = curl_exec($ch);
curl_close($ch);
// Dekodowanie JSON
if ($plik !== false) {
$data = json_decode($plik, true); // Uwaga na drugi argument, true oznacza, że dekodujemy do tablicy asocjacyjnej
} else {
echo "Nie udało się pobrać pliku z adresu: " . $one['IP'];
continue; // Przechodzimy do następnej iteracji pętli
}
// Dodanie rekordu do bazy
$query = $pdo->prepare("INSERT INTO danezczujnikow(ID, IDCzujnikow, temperatura, wilgotnosc, data) VALUES
(NULL, :idczujnika, :temperatura, :wilgotnosc, NULL)");
$query -> bindValue(':idczujnika', $one['ID'], PDO::PARAM_INT);
$query -> bindValue(':temperatura', $data['temperature']);
$query -> bindValue(':wilgotnosc', $data['humidity']);
$query -> execute();
} catch (\Throwable $th) {
if ($th instanceof \ErrorException && strpos($th->getMessage(), 'file_get_contents') !== false) {
echo "Nie udało się pobrać pliku z adresu: " . $one['IP'];
} else {
echo "Wystąpił inny błąd: " . $th->getMessage();
}
exit();
}
}
echo "Wszytko przeprowadzono prawidłowo";
}
exit();
wykres.php
<?php
if (!isset($_GET['id'])) {
header("Location: index.php");
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wykres</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>
<body class="d-flex justify-content-center mt-3 position-relative">
<img class="position-absolute img-fluid z-1" src="" alt="">
<div class="d-flex flex-column justify-content-center align-items-center w-75">
<!-- PHP -->
<?php
include_once("config.php");
// Pobranie nazwy czujnika
$query = $pdo->prepare("SELECT nazwa FROM listaczujnikow WHERE ID = :id");
$query -> bindValue(':id', $_GET['id'], PDO::PARAM_INT);
$query -> execute();
$lista = $query -> fetchAll(PDO::FETCH_ASSOC);
$nazwaCzytnika = $lista[0]['nazwa'];
// Pobranie danych z ostatniego dnia
$limit = (60 * 3) / DELAY;
$query = $pdo->prepare("SELECT * FROM danezczujnikow WHERE IDCzujnikow = :id ORDER BY data DESC LIMIT :iloscdanych");
$query -> bindValue(':iloscdanych', $limit, PDO::PARAM_INT);
$query -> bindValue(':id', $_GET['id'], PDO::PARAM_INT);
$query -> execute();
$dane = $query -> fetchAll(PDO::FETCH_ASSOC);
$jsonDanych = json_encode($dane);
// Przekazanie danych do JS
echo "<script> let dane = " . $jsonDanych . "</script>";
?>
<h2><?php echo $nazwaCzytnika; ?></h2>
<canvas id="wykres" class="w-100" ></canvas>
<!-- JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script>
// Stworzenie tabel dla danych
var daty = [];
var temperatury = [];
var wilgotnosci = [];
dane = dane.reverse();
// Przewertowanie danych
dane.forEach(function (element) {
daty.push(element.data);
temperatury.push(element.temperatura);
wilgotnosci.push(element.wilgotnosc);
});
// Genergowanie wykresu poprzez Chart.js
var ctx = $("#wykres")[0].getContext('2d');
ctx.clearRect(0, 0, $("#wykres").width(), $("#wykres").height());
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: daty, // Ustawiamy daty jako etykiety osi X
datasets: [
{
label: 'Temperatura (°C)',
data: temperatury, // Ustawiamy dane temperatury
borderColor: 'rgba(255, 99, 132, 1)', // Czerwony kolor linii
backgroundColor: 'rgba(255, 99, 132, 0.2)', // Przezroczyste tło
borderWidth: 1,
fill: true // Wypełnienie pod linią
},
{
label: 'Wilgotność (%)',
data: wilgotnosci, // Ustawiamy dane wilgotności
borderColor: 'rgba(54, 162, 235, 1)', // Niebieski kolor linii
backgroundColor: 'rgba(54, 162, 235, 0.2)', // Przezroczyste tło
borderWidth: 1,
fill: true // Wypełnienie pod linią
}
]
},
options: {
responsive: true,
plugins:
{
legend:
{
labels:
{
color: 'black' // Kolor tekstu w legendzie
}
},
title:
{
display: true,
text: 'Wykres Temperatury i Wilgotnośćci',
color: 'black' // Kolor tekstu tytułu
}
},
scales: {
x:
{
ticks:
{
color: 'black' // Kolor tekstu na osi X
}
},
y:
{
ticks:
{
color: 'black' // Kolor tekstu na osi Y dla pierwszego zestawu danych
}
}
}
}
});
</script>
</body>
</html>
Baza danych
Po całej konfiguracji musimy jeszcze utworzyć gotową bazę danych która będzie przechowywać nasze dane.
Tworzymy tabelę dane z czujników.
CREATE TABLE `danezczujnikow` (
`ID` bigint(20) NOT NULL,
`IDCzujnikow` bigint(20) NOT NULL,
`temperatura` float NOT NULL,
`wilgotnosc` float NOT NULL,
`data` timestamp NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
Tworzymy tabelę lista czujników
CREATE TABLE `listaczujnikow` (
`ID` bigint(20) NOT NULL,
`IP` text NOT NULL,
`nazwa` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
Indeksy dla tabeli danezczujnikow
ALTER TABLE `danezczujnikow`
ADD PRIMARY KEY (`ID`),
ADD KEY `IDCzujnikow` (`IDCzujnikow`);
Indeksy dla tabeli listaczujnikow
ALTER TABLE `listaczujnikow`
ADD PRIMARY KEY (`ID`);
Ustawiamy autoincrement
ALTER TABLE `danezczujnikow`
MODIFY `ID` bigint(20) NOT NULL AUTO_INCREMENT;
ALTER TABLE `listaczujnikow`
MODIFY `ID` bigint(20) NOT NULL AUTO_INCREMENT;
ESP8266 + DHT22
Zacznijmy od schematu jak powinniśmy wszystko podłączyć.
Czas na instalację, tutaj polecam Visual Studio Code
Po instalacji musimy doinstalować rozszerzenie które nazywa się
PlatformIO IDE – tak jak na obrazku poniżej.
Następnie instalujemy biblioteki
ESPAsyncWebServer-esphome
DHT22 sensor
Monitorowanie Temperatury w Serwerowni gotowy kod do wgrania na ESP8266+DHT22
Jak już wszystko udało nam się skonfigurować, czas wgrać nasz kod na urządzenie i sprawdzić czy wszystko działa poprawnie.
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
#define DHTPIN D7 // Zdefiniowanie wejścia danych z DHT
#define DHTTYPE DHT22 // Zdefiniowanie modelu DHT
DHT_Unified dht(DHTPIN, DHTTYPE); // Zdefiniowanie obiektu DHT
const char* ssid = "SSID"; // Nazwa sieci WiFi
const char* password = "Hasło"; // Hasło do sieci WiFi
const char* newHostname = "ESP_CZUJNIK_NAP"; // Hostname
// Zdefiniowanie serwera na ESP oraz jego portu
ESP8266WebServer server(80);
void setup() { // Funkcja bootowa kontrolera
// Inicjalizacja komunikacji szeregowej oraz jej prędkości
Serial.begin(9600);
// Inicjalizacja czujnika DHT
dht.begin();
// Set custom hostname
WiFi.hostname(newHostname);
// Połączenie z wcześniej zdefiniowaną siecią WiFi
WiFi.begin(ssid, password);
// Czekanie na połączenie z WiFi
while (WiFi.status() != WL_CONNECTED) {
Serial.println("Łączenie z WiFi...");
}
Serial.println("Połączono z WiFi");
Serial.println("Twoje IP");
Serial.println(WiFi.localIP());
Serial.println("--------------------");
// Definiowanie endpointu /dane, który zwróci dane w formacie JSON
server.on("/dane", HTTP_GET, []() {
// Odczytaj temperaturę i wilgotność
// Pobranie eventu
sensors_event_t event;
// Pobranie temperatury
dht.temperature().getEvent(&event);
float temperature = event.temperature;
// Pobranie wilgotności
dht.humidity().getEvent(&event);
float humidity = event.relative_humidity;
// Tworzenie JSON
String jsonData = "{\"temperature\": " + String(temperature) + ", \"humidity\": " + String(humidity) + "}";
// Wysłanie JSON jako odpowiedź na zapytanie
server.send(200, "application/json", jsonData);
});
// Tworzenie serwera
server.begin();
}
void loop() {
// Nasłuch połączeń
server.handleClient();
}
Uwagi
Monitorowanie Temperatury w Serwerowni
Ważne jest aby po wgraniu na urządzenie kodu sprawdzić czy wszystko działa poprawnie. Jeżeli mamy możliwość podejrzenia na routerze jakie urządzenie się podłączyło do naszej sieci. Nie powinno to byś też trudne, ponieważ w linijce:
const char* newHostname = "ESP_CZUJNIK_NAP";
Ustawmy taki hostname aby był on powiązany z lokalizacją naszego czujnika.
Warto również przypisać na routerze stały adres IP dla czujnika, ponieważ jeżeli tego nie zrobimy a adres się zmieni to automatycznie ten adres również będziemy musieli zmienić w bazie danych a a dokładnie w tabeli listaczujnikow
.
Sprawdzenie podstawowych funkcjonalności
Aby sprawdzić czy czujnik działa i prawidłowo, wystarczy że wejdziesz na stronę:
http://ip_esp8622/dane
Aby sprawdzić czy cron oraz reszta czujników które dodaliśmy działają prawidłowo działa prawidłowo wystarczy wpisać
http://ip_serwer_www/cron.php