Skocz do zawartości

Pomocna odpowiedź

Napisano (edytowany)

1. Cel i zakres

Celem projektu jest ciągły nadzór nad parametrami środowiskowymi w serwerowni: temperaturą, wilgotnością względną oraz poziomem hałasu. Urządzenie ma wczesne wykrywać anomalie (np. awaria klimatyzacji, wzrost hałasu wentylatorów), rejestrować historię i raportować wartości do systemu Domoticz.

2. Architektura systemu

System składa się z dwóch warstw:

  • Warstwa akwizycjiArduino Nano (8-bit MCU) zbiera szybkie próbki analogowe z mikrofonu MAX9814 oraz dane z czujnika SHT20 po magistrali I²C. Dane są wstępnie przetwarzane i przesyłane przez UART do Raspberry Pi.
  • Warstwa bramki i zapisuRaspberry Pi 4 (Raspbian/Linux) realizuje:
    • odczyt dwóch sond DS18B20 po 1-Wire (wejście jądra: /sys/bus/w1/devices/28-00000053483a oraz drugi czujnik),
    • harmonogram zadań cron (interwał 1 min),
    • agregację i wysyłkę wszystkich wartości do Domoticz poprzez HTTP:
      http://{domoticz_host}:{domoticz_port}/json.htm?type=command&param=udevice&idx={device_idx}&nvalue={nvalue}&svalue={svalue}
       

3. Sensory i interfejsy

3.1. Temperatura – DS18B20 (2 szt.)

Wodoodporne sondy 1-Wire zasilane z 3,3 V lub 5 V (w zależności od trasy). Obie podłączone równolegle do jednego kanału 1-Wire na Raspberry Pi z rezystorem podciągającym 4,7 kΩ do linii danych. Oprogramowanie identyfikuje unikalne adresy czujników; skrypt czyta pliki w1_slave, filtruje wartości CRC i przekazuje wynik w °C do Domoticz. Interwał próbkowania: 60 s.

3.2. Wilgotność i temperatura – SHT20 (I²C)

Czujnik cyfrowy z sondą w stalowej obudowie, połączony przewodem ok. 5 m. Ze względu na długość magistrali zastosowano TCA4307 (Adafruit 5159) – bufor/Hot-Swap I²C stabilizujący zbocza i umożliwiający gorące dołączanie. Kolory przewodów: biały – GND, niebieski – 3,3 V, zielony – SDA → A5 Arduino, żółty – SCL → A4 Arduino. Częstotliwość I²C nominalnie 100 kHz (zalecane przy długich liniach).

3.3. Poziom dźwięku – MAX9814 (A0)

Mikrofon elektretowy z automatycznym wzmocnieniem (AGC), zasilany 3,3–5 V, wyjście analogowe do A0 Arduino. Procedura pomiaru: przez 3 s wykonywany jest pomiar co 0,2 s (15 próbek), a następnie liczona jest amplituda (różnica między maksimum a minimum). Wynik odpowiada przybliżonej głośności/zmienności akustycznej w otoczeniu i służy do detekcji nietypowych zdarzeń (np. hałas łożysk, alarmy).

4. Komunikacja i format danych

Arduino Nano komunikuje się z Raspberry Pi przez UART (np. 115200 8N1). Ramka danych może mieć postać JSON/CSV, np.:
TEMP1=23.56;TEMP2=23.42;HUM=45.1;SHTT=23.7;SND=128
Raspberry Pi łączy dane z DS18B20 z ramką z Nano, waliduje zakresy (np. –40…85 °C dla DS18B20, 0…100% RH dla SHT20) i wysyła do Domoticz odpowiednimi idx. Wysyłka realizowana przez skrypt uruchamiany z cron co minutę; w przypadku błędu HTTP przewidziany jest retry oraz zapis do lokalnego logu.

5. Zasilanie i okablowanie

Urządzenia zasilane z jednej szyny 5 V z zabezpieczeniem (bezpiecznik/ogranicznik prądu). Zastosowano:

  • Radiator i wentylację Raspberry Pi 4 (zespół odprowadzania ciepła),
  • Obudowę plastikową z przepływem powietrza,
  • Shield Proto z listwą ARK dla solidnych przyłączy,
  • Przewody 3- i 4-żyłowe (ok. 30 m) prowadzone z dala od kabli zasilania 230 V; zalecane skrętki dla linii sygnałowych. Przewidziano gniazdo RJ45 jako przepust/organizację okablowania sygnałowego.

6. Montaż i bezpieczeństwo

Wszystkie połączenia sygnałowe wykonane jako niskonapięciowe SELV. Linie 1-Wire i I²C prowadzone możliwie krótko; dla odcinków dłuższych – ekran lub bufor (jak TCA4307). Obudowa zamknięta, dostęp serwisowy przez pokrywę. Brak bezpośrednich połączeń z siecią 230 V w urządzeniu.

7. Oprogramowanie i utrzymanie

  • System: Raspbian z włączonymi modułami w1-gpio, i2c, serial.
  • Usługi: skrypt akwizycji uruchamiany przez cron co 1 min; logi rotowane (logrotate).
  • Zabezpieczenia: ograniczenie dostępu HTTP do Domoticz (token/hasło), firewall sieciowy, separacja VLAN gdzie możliwe.
  • Kalibracja: wstępna weryfikacja sond temperatury w znanym punkcie (np. 0 °C z lodem); sanity-check wilgotności na referencyjnych warunkach; próg alarmu akustycznego ustalany empirycznie w godzinach normalnej pracy serwerowni.

8. Integracja z Domoticz

Dla każdego parametru utworzono urządzenie w Domoticz (oddzielne idx dla: Temp1, Temp2, Wilgotność, Temp SHT, Hałas). Dane przekazywane poprzez żądanie HTTP GET zgodnie z API Domoticz. W systemie konfiguruje się sceny/zdarzenia: alarm wysokiej temperatury, długotrwały wzrost hałasu, trend wilgotności (np. wykrycie zalania/awarii nawilżania).

9. Testy i kryteria akceptacji

  • Test komunikacji: poprawny odczyt wszystkich sensorów przez 24 h bez utraty ramek.
  • Test odporności: symulacja wzrostu temperatury (np. odłączenie jednego klimatyzatora) – rejestracja i alarm.
  • Test akustyczny: sztuczne źródło hałasu – wzrost amplitudy o ustalony próg, wygenerowanie zdarzenia.

 

 

Pora na zdjęcia poglądowe, najpierw po zmontowaniu:


zrobione1.thumb.jpg.f1af26ff7ace8f0b9b2023ee299952a8.jpg

 

 

Uruchomienie systemu bez niespodzianki:
zrobione2.thumb.jpg.5d1a06af25859f6c41127d003d913e1e.jpg

 

A tak po uporządkowaniu połączeń i skręceniu:
zrobione3.thumb.JPG.79f438e7c97a32efe55d02b93f432c63.JPG
 

 

Na koniec kody źródłowe do czujników. Zaczynam od dwóch czujników temperatury w serwerowni. Posiadają tylko trzy wyprowadzenia.
 

#!/usr/bin/env python3
import os
import time
import requests

# Ładowanie modułów (możesz dodać te polecenia do /etc/rc.local lub skonfigurować je w systemd)
os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')

# Ustawienie ścieżki do czujnika DS18B20
device_folder = '/sys/bus/w1/devices/28-00000053483a'
device_file = os.path.join(device_folder, 'w1_slave')

def read_temp_raw():
    with open(device_file, 'r') as f:
        lines = f.readlines()
    return lines

def read_temp():
    lines = read_temp_raw()
    # Czekamy, aż pierwszy wiersz potwierdzi poprawny odczyt ('YES')
    while lines[0].strip()[-3:] != 'YES':
        time.sleep(0.2)
        lines = read_temp_raw()
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        temp_string = lines[1][equals_pos+2:]
        temp_c = float(temp_string) / 1000.0
        return temp_c
    raise RuntimeError("Błąd odczytu temperatury!")

# Odczyt temperatury
temperature = read_temp()
print("Temperatura: {:.2f} °C".format(temperature))

# Konfiguracja Domoticz
domoticz_host = "localhost"
domoticz_port = "8080"
device_idx = "1"  # <--- zmień na właściwy numer urządzenia w Domoticz
nvalue = 0
svalue = "{:.2f}".format(temperature)

# Ustaw dane autoryzacyjne (login/hasło)
username = "update"  # <--- wpisz swój login
password = "password"  # <--- wpisz swoje hasło

# Przygotowanie adresu URL do aktualizacji urządzenia w Domoticz
url = f"http://{domoticz_host}:{domoticz_port}/json.htm?type=command&param=udevice&idx={device_idx}&nvalue={nvalue}&svalue={svalue}"
print("Wysyłanie danych do Domoticz:", url)

try:
    response = requests.get(url, timeout=10, auth=(username, password))
    if response.status_code == 200:
        print("Pomyślnie wysłano dane do Domoticz.")
    else:
        print("Błąd wysyłania danych, kod HTTP:", response.status_code)
except Exception as e:
    print("Błąd przy wysyłaniu danych do Domoticz:", e)

Powinny być dwa takie skrypty umieszczone w podkatalogu aplikacji Domoticz, u mnie są temp1.py i temp2.py, różnią się tylko w jednej linii kodu:
 

device_idx = "1"  # <--- zmień na właściwy numer urządzenia w Domoticz - dla temp1.py
device_idx = "2"  # <--- zmień na właściwy numer urządzenia w Domoticz - dla temp2.py

 

Pora teraz na czujnik wilgotności i temperatury w serwerowni:
 

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
#  Odczyt SHT30 + wysyłka temperatury i wilgotności do Domoticz
#
import time
import socket
import requests
from smbus2 import SMBus

# ---------- 1. Funkcje pomocnicze ----------

# a) Bieżący adres IP (lub wpisz "localhost")
def get_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(("8.8.8.8", 80))
        return s.getsockname()[0]
    finally:
        s.close()

# b) CRC-8 z datasheetu SHT3x (polinom 0x31, init 0xFF)
def crc8(data):
    crc = 0xFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            crc = (crc << 1) ^ 0x31 if (crc & 0x80) else (crc << 1)
            crc &= 0xFF
    return crc

# c) Odczyt jednorazowy temperatury [°C] i RH [%]
def read_sht30(bus, addr=0x44):
    CMD_SINGLE_HIGHREP = [0x2C, 0x06]     # single-shot, high repeatability, no CS
    bus.write_i2c_block_data(addr, CMD_SINGLE_HIGHREP[0], CMD_SINGLE_HIGHREP[1:])
    time.sleep(0.015)                     # 15 ms zgodnie z arkuszem
    raw = bus.read_i2c_block_data(addr, 0x00, 6)
    # weryfikacja CRC
    if crc8(raw[0:2]) != raw[2] or crc8(raw[3:5]) != raw[5]:
        raise RuntimeError("Błędna suma CRC (SHT30)")
    raw_temp  = raw[0] << 8 | raw[1]
    raw_humid = raw[3] << 8 | raw[4]
    temp_c  = -45 + 175 * (raw_temp  / 65535.0)
    rh      =      100 * (raw_humid / 65535.0)
    return round(temp_c, 2), round(rh, 1)

# d) Podpowiedz status wilgotności dla Domoticz
def humidity_status(rh):
    if 40 <= rh <= 60:
        return 1   # Comfortable
    if rh < 30:
        return 2   # Dry
    if rh > 70:
        return 3   # Wet
    return 0       # Normal

# ---------- 2. Konfiguracja ----------

DOMO_HOST   = get_local_ip()        # albo "localhost"
DOMO_PORT   = 8080
DEVICE_IDX  = 3                     # <-- wstaw IDX czujnika Temp+Hum
USERNAME    = "update"
PASSWORD    = "password"

# ---------- 3. Główna logika ----------

with SMBus(1) as bus:
    try:
        temp, rh = read_sht30(bus)
        h_stat   = humidity_status(rh)
        # Format dla czujnika Temp+Hum: "T;RH;HumStat"
        svalue = f"{temp:.2f};{rh:.1f};{h_stat}"
        url = (f"http://{DOMO_HOST}:{DOMO_PORT}/json.htm?"
               f"type=command&param=udevice&idx={DEVICE_IDX}"
               f"&nvalue=0&svalue={svalue}")
        print(f"Odczyt SHT30  →  {temp:.2f} °C  {rh:.1f}% RH")
        r = requests.get(url, timeout=10, auth=(USERNAME, PASSWORD))
        r.raise_for_status()
        #print(f"Wysłano do Domoticz ({DOMO_HOST}), HTTP {r.status_code} OK")
    except Exception as e:
        print("Błąd:", e)


Teraz pora na kod, który został wgrany do Arduino Nano (bardzo lubię z niego korzystać). Skrypt ma za zadanie wysyłać dane z drugiego czujnika temperatury i wilgotności oraz poziom dźwięku w serwerowni. Te wszystkie dane są przekazywane do Raspberry Pi 4 w zadanych interwałach czasowych.
 

#include <Wire.h>
#include <Adafruit_SHT31.h>

Adafruit_SHT31 sht31;

/* ---------- CZASY ---------- */
const unsigned long PERIOD_SHT_MS   = 30000UL;   // 30 s
const unsigned long SOUND_STEP_MS   =   200UL;   // 0,2 s
const uint8_t       SOUND_BUF_LEN   =    18;     // 18 próbek → 3,6 s

/* ---------- PINY ---------- */
const uint8_t PIN_SOUND = A0;

/* ---------- ZMIENNE ---------- */
unsigned long lastSht   = 0;
unsigned long lastSound = 0;

uint16_t      soundBuf[SOUND_BUF_LEN];
uint8_t       soundIx   = 0;
bool          bufFilled = false;

void setup() {
  Serial.begin(9600);
  Wire.begin();

  if (!sht31.begin(0x44)) {
    Serial.println(F("Nie znaleziono czujnika SHT-30!"));
    while (true) delay(1000);
  }
}

void loop() {
  unsigned long now = millis();

/* --- 1. SHT-30 co 30 s --- */
  if (now - lastSht >= PERIOD_SHT_MS) {
    lastSht = now;

    float t  = sht31.readTemperature();
    float rh = sht31.readHumidity();

    if (!isnan(t) && !isnan(rh)) {
      Serial.print(F("SHT:"));
      Serial.print(t, 1);
      Serial.print(',');
      Serial.println(rh, 1);
    } else {
      Serial.println(F("SHT_ERR"));
    }
  }

/* --- 2. próbkowanie dźwięku co 0,2 s --- */
  if (now - lastSound >= SOUND_STEP_MS) {
    lastSound = now;

    uint16_t raw = analogRead(PIN_SOUND);   // 0-1023
    soundBuf[soundIx++] = raw;

    if (soundIx >= SOUND_BUF_LEN) {         // bufor pełny
      soundIx   = 0;
      bufFilled = true;
    }

    if (bufFilled && soundIx == 0) {        // co 18 próbek (3,6 s)
      uint16_t vMin = soundBuf[0];
      uint16_t vMax = soundBuf[0];
      for (uint8_t i = 1; i < SOUND_BUF_LEN; ++i) {
        if (soundBuf[i] < vMin) vMin = soundBuf[i];
        if (soundBuf[i] > vMax) vMax = soundBuf[i];
      }
      uint16_t amp = vMax - vMin;           // amplituda
      Serial.print(F("WPSE:"));
      Serial.println(amp);                  // np. WPSE:187
    }
  }
}


Teraz muszę jeszcze odebrać dane z portu USB Raspberry Pi 4 i wysłać to do Domoticza, program działa w pętli nieskończonej i musi być uruchomiony na starcie tylko raz. 

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Odczyt danych z Arduino (SHT30 + czujnik dźwięku) po UART
i wysyłka do Domoticz (idx: temperatura+wilgotność oraz sensor dźwięku).
"""

import serial
import time
import requests

# ---------- KONFIGURACJA ----------
SERIAL_PORT   = "/dev/ttyUSB0"
BAUDRATE      = 9600

DOMO_HOST     = "localhost"
DOMO_PORT     = 8080
IDX_SHT       = 4        # Temp + Hum (dummy)
IDX_SOUND     = 5        # Custom Sensor (General)

USERNAME      = "update"
PASSWORD      = "password"
NVALUE        = 0        # nvalue = 0 dla pomiarów liczbowych

# ---------- FUNKCJE POMOCNICZE ----------
def humidity_status(rh: float) -> int:
    """Zwraca kod statusu RH zgodnie z Domoticz."""
    if 40 <= rh <= 60:
        return 1   # Comfortable
    if rh < 30:
        return 2   # Dry
    if rh > 70:
        return 3   # Wet
    return 0       # Normal

def domo_update(idx: int, svalue: str) -> None:
    """Wysyła pojedynczy odczyt do Domoticz."""
    url = (f"http://{DOMO_HOST}:{DOMO_PORT}/json.htm"
           f"?type=command&param=udevice&idx={idx}"
           f"&nvalue={NVALUE}&svalue={svalue}")
    r = requests.get(url, timeout=10, auth=(USERNAME, PASSWORD))
    r.raise_for_status()

# ---------- INICJALIZACJA UART ----------
ser = serial.Serial(SERIAL_PORT, BAUDRATE, timeout=1)
time.sleep(2)                       # Arduino resetuje się po ustawieniu DTR

print("Start – oczekiwanie na dane z UART...")

# ---------- GŁÓWNA PĘTLA ----------
while True:
    try:
        line = ser.readline().decode("utf-8", errors="ignore").strip()
    except serial.SerialException as e:
        print("Błąd portu szeregowego:", e)
        time.sleep(5)
        continue

    if not line:
        continue

    print("UART >", line)

    try:
        # ----- Pakiet SHT30 -------------------------------------------------
        if line.startswith("SHT:"):
            # Oczekiwany format z Arduino: "SHT:23.4,46.7"
            try:
                temp_str, rh_str = line[4:].split(",")
                temp = float(temp_str)
                rh   = float(rh_str)
            except ValueError:
                print("Błędny format SHT:", line)
                continue

            h_stat = humidity_status(rh)
            # Domoticz wymaga: "temp;humidity;humidity_status"
            svalue = f"{temp:.2f};{rh:.1f};{h_stat}"
            domo_update(IDX_SHT, svalue)
            # print("↑ Domoticz SHT", svalue)

        # ----- Pakiet natężenia dźwięku ------------------------------------
        elif line.startswith("WPSE:"):
            # Oczekiwany format: "WPSE:512"
            try:
                raw = int(line[5:])
            except ValueError:
                print("Błędny format WPSE:", line)
                continue

            domo_update(IDX_SOUND, str(raw))
            # print("↑ Domoticz SOUND", raw)

        # ----- Nierozpoznany prefiks ---------------------------------------
        else:
            print("Nieznany format:", line)

    except requests.RequestException as e:
        print("Błąd HTTP:", e)

    # ­Niewielka pauza odciążająca CPU
    time.sleep(0.05)


Ostatnią rzeczą jest konfiguracja crona, aby regularnie przesyłać dane co minutę:

* * * * * /home/norbert/domoticz/myscrypts/temp1.py
* * * * * /home/norbert/domoticz/myscrypts/temp2.py
* * * * * /usr/bin/python3 /home/norbert/domoticz/myscrypts/sht30.py

Dane, które są przesyłane z Arduino do Rasperry Pi 4 są realizowane jako zwyczajna usługa linuksa, poniżej jej konfiguracja:


[Unit]
Description=Arduino  Domoticz bridge
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=norbert
# Użytkownik musi być w grupie „dialout”, żeby otworzyć /dev/ttyUSB0
Group=norbert
ExecStart=/usr/bin/env python3 /home/norbert/domoticz/myscrypts/arduino2domoticz2.py
WorkingDirectory=/home/norbert/domoticz/myscrypts
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target


 

Edytowano przez norbineo3
estetyka wpisu.
  • Lubię! 2

Podoba Ci się ten projekt? Zostaw pozytywny komentarz i daj znać autorowi, że zbudował coś fajnego!

Masz uwagi? Napisz kulturalnie co warto zmienić. Doceń pracę autora nad konstrukcją oraz opisem.

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...