Przeszukaj forum
Pokazywanie wyników dla tagów 'Home Assistant'.
Znaleziono 2 wyniki
-
Witajcie Chciałbym przedstawić Wam mój pierwszy projekt na ESP32. Buduję smart home i potrzebowałem pomiaru temperatury i wilgotności w pokojach z możliwością ustawiania częstotliwości wysyłania tych danych np. co minutę lub 5 minut, oraz aby te dane nie przechodziły przez chmurę tak jak np. tuya, sonoff, aqara. Są dostępne gotowe urządzenia ale nie spełniały tych założeń , dlatego zrobiłem swoje a przy okazji zacząłem przygodę z esp32 i drukiem 3D. Tak wygląda już gotowe urządzenie Założenia: maksymalnie prosty układ z gotowych części, aby cenowo był tańszy niż już gotowe urządzenia (bo takich urządzeń będę potrzebował x5) komunikacja przez WiFi wysyłka danych co 5min do serwera Home Assistant wyświetlanie danych na ekranie urządzenia gdy by padł serwer z Home Assistant zasilanie z usb C, ponieważ nie chce zmieniać baterii Wykorzystane części: esp32 wroom czujnik temperatury i wilgotności - dht22 oled 128x32 np. ten SPI/I2C - czarno-biały Zaczynam od połączenia wszystkiego na płytce i przetestowaniu programu. Założenia programu są takie żeby na początku na ekranie wyświetlił adres IP pod którym można się zalogować , ponieważ esp32 działa w trybie AP, na ekranie możemy ustawić dane sieci WiFi, częstotliwość wysyłki danych temperatura i wilgotność do HA (w sekundach), logi i hasło do klienta MQTT, oraz adresy wysyłki temperatury i wilgotności do HA. Dodatkowo tryb AP działa przez pierwsze 90s, gdy nie podłączymy się pod WiFi to na ekranie wyświetli się temperatura i wilgotność. Gdy będziemy chcieli zmienić siec to wystarczy wcisnąć wbudowany przycisk resetu, który czeka przez pierwsze 3s od momentu włączenia zasilania , wtedy zresetowana zostaje pamięć i można wprowadzić dane ponownie. Obudowę przygotowałem w programie FreeCAD i jesto to moja pierwsza w życiu obudowa i projekt 3D, ale wciągneło mnie do tego stopnia że już pracuję nad kolejnymi projektami, które wykorzystam w Home Assistant. Przygotowuję kod według założeń, korzystam z programu Arduino IDE. #include <WiFiManager.h> #include <PubSubClient.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Adafruit_Sensor.h> #include <DHT.h> #include <Preferences.h> // Definicje pinów i rozdzielczości OLED #define BOOT_BUTTON_PIN 0 // Przycisk BOOT (GPIO0) #define DHTPIN 4 // Pin danych czujnika DHT22 #define DHTTYPE DHT22 // Typ czujnika #define SCREEN_WIDTH 126 // Szerokość OLED #define SCREEN_HEIGHT 32 // Wysokość OLED #define OLED_RESET -1 // Brak dedykowanego pinu reset Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); DHT dht(DHTPIN, DHTTYPE); unsigned long lastMQTTAttempt = 0; bool wifiFailed = false; Preferences preferences; WiFiManager wm; WiFiClient espClient; PubSubClient client(espClient); // Domyślne ustawienia MQTT i interwału (w sekundach) char mqtt_server[40] = "192.168.1.104"; int mqtt_port = 1883; int send_interval = 30; char mqtt_user[40] = "mqtt_user"; char mqtt_password[40] = "mqtt_password"; char temp_topic[100] = "homeassistant/sensor/esp32/temperature"; char hum_topic[100] = "homeassistant/sensor/esp32/humidity"; // Czas ostatniej publikacji MQTT unsigned long lastSendTime = 0; void displayWiFiInfo() { WiFi.softAP("ESP32_Config"); IPAddress apIP = WiFi.softAPIP(); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println("Konfiguracja WiFi"); display.print("S: "); display.println("ESP32_Config"); display.print("IP: "); display.println(apIP); display.display(); Serial.println("Tryb AP: ESP32_Config"); Serial.print("Adres konfiguracyjny: "); Serial.println(apIP); } void updateDisplay() { float temperature = dht.readTemperature(); float humidity = dht.readHumidity(); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); if (isnan(temperature) || isnan(humidity)) { Serial.println("Błąd odczytu DHT!"); display.setCursor(0, 10); display.println("DHT ERROR"); } else { String tempStr = "Temp: " + String(temperature,1) + " C"; String humStr = "Wilg: " + String(humidity,1) + " %"; // Wyśrodkowanie tekstu - obliczamy szerokość tekstu int16_t x1, y1; uint16_t w, h; display.getTextBounds(tempStr.c_str(), 0, 0, &x1, &y1, &w, &h); int xTemp = (SCREEN_WIDTH - w) / 2; int yTemp = (SCREEN_HEIGHT / 2) - h; display.setCursor(xTemp, yTemp); display.println(tempStr); display.getTextBounds(humStr.c_str(), 0, 0, &x1, &y1, &w, &h); int xHum = (SCREEN_WIDTH - w) / 2; int yHum = (SCREEN_HEIGHT / 2) + 2; display.setCursor(xHum, yHum); display.println(humStr); } display.display(); } void publishSensorData() { float temperature = dht.readTemperature(); float humidity = dht.readHumidity(); if (isnan(temperature) || isnan(humidity)) { Serial.println("Błąd odczytu DHT!"); return; } client.publish(temp_topic, String(temperature).c_str()); client.publish(hum_topic, String(humidity).c_str()); Serial.print("Publikacja: T: "); Serial.print(temperature); Serial.print("C, H: "); Serial.print(humidity); Serial.println("%"); } void reconnectMQTT() { if (!client.connected() && (millis() - lastMQTTAttempt >= 15000UL)) { lastMQTTAttempt = millis(); Serial.print("Łączenie z MQTT..."); if (client.connect("ESP32Client", mqtt_user, mqtt_password)) { Serial.println("Połączono z MQTT!"); } else { Serial.print("Błąd, rc="); Serial.print(client.state()); Serial.println(" Nie udało się połączyć."); } } } void setup() { Serial.begin(115200); pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP); delay(100); bool resetSettings = false; unsigned long startTime = millis(); while (millis() - startTime < 3000) { if (digitalRead(BOOT_BUTTON_PIN) == LOW) { resetSettings = true; break; } } if (resetSettings) { Serial.println("Przycisk BOOT wciśnięty - resetowanie ustawień..."); preferences.begin("config", false); preferences.clear(); preferences.end(); wm.resetSettings(); if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("Błąd OLED!"); while (true); } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 10); display.print("Reset ustawien!"); display.display(); delay(5000); } if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("Błąd OLED!"); while (true); } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 10); display.print("Uruchamianie ..."); display.display(); preferences.begin("config", false); String storedServer = preferences.getString("mqtt_server", "192.168.1.100"); storedServer.toCharArray(mqtt_server, sizeof(mqtt_server)); mqtt_port = preferences.getInt("mqtt_port", 1883); send_interval = preferences.getInt("interval", 300); String storedUser = preferences.getString("mqtt_user", "mqtt_user"); storedUser.toCharArray(mqtt_user, sizeof(mqtt_user)); String storedPass = preferences.getString("mqtt_password", "mqtt_password"); storedPass.toCharArray(mqtt_password, sizeof(mqtt_password)); String storedTempTopic = preferences.getString("temp_topic", "homeassistant/sensor/esp32/temperature"); storedTempTopic.toCharArray(temp_topic, sizeof(temp_topic)); String storedHumTopic = preferences.getString("hum_topic", "homeassistant/sensor/esp32/humidity"); storedHumTopic.toCharArray(hum_topic, sizeof(hum_topic)); preferences.end(); WiFiManagerParameter paramInterval("interval", "Co ile sekund wysyłać dane do HA?", String(send_interval).c_str(), 6); WiFiManagerParameter paramMQTTServer("mqtt_server", "MQTT serwer IP", mqtt_server, 40); WiFiManagerParameter paramMQTTPort("mqtt_port", "MQTT port", String(mqtt_port).c_str(), 6); WiFiManagerParameter paramMQTTUser("mqtt_user", "MQTT użytkownik", mqtt_user, 40); WiFiManagerParameter paramMQTTPassword("mqtt_password", "MQTT hasło", mqtt_password, 40); WiFiManagerParameter paramTempTopic("temp_topic", "MQTT Temat Temperatury", temp_topic, 100); WiFiManagerParameter paramHumTopic("hum_topic", "MQTT Temat Wilgotności", hum_topic, 100); wm.addParameter(¶mInterval); wm.addParameter(¶mMQTTServer); wm.addParameter(¶mMQTTPort); wm.addParameter(¶mMQTTUser); wm.addParameter(¶mMQTTPassword); wm.addParameter(¶mTempTopic); wm.addParameter(¶mHumTopic); displayWiFiInfo(); // Ustaw timeout, aby portal AP był aktywny przez 90 sekund (1 min i 30sec) wm.setTimeout(90); if (!wm.autoConnect("ESP32_Config")) { Serial.println("Brak połączenia z WiFi! Tryb AP."); WiFi.mode(WIFI_OFF); wifiFailed = true; } Serial.println("Połączono z WiFi!"); send_interval = String(paramInterval.getValue()).toInt(); strncpy(mqtt_server, paramMQTTServer.getValue(), sizeof(mqtt_server)); mqtt_port = String(paramMQTTPort.getValue()).toInt(); strncpy(mqtt_user, paramMQTTUser.getValue(), sizeof(mqtt_user)); strncpy(mqtt_password, paramMQTTPassword.getValue(), sizeof(mqtt_password)); strncpy(temp_topic, paramTempTopic.getValue(), sizeof(temp_topic)); strncpy(hum_topic, paramHumTopic.getValue(), sizeof(hum_topic)); preferences.begin("config", false); preferences.putString("mqtt_server", mqtt_server); preferences.putInt("mqtt_port", mqtt_port); preferences.putInt("interval", send_interval); preferences.putString("mqtt_user", mqtt_user); preferences.putString("mqtt_password", mqtt_password); preferences.putString("temp_topic", temp_topic); preferences.putString("hum_topic", hum_topic); preferences.end(); dht.begin(); client.setServer(mqtt_server, mqtt_port); } void loop() { if (!client.connected()) { reconnectMQTT(); } client.loop(); updateDisplay(); if (millis() - lastSendTime >= (send_interval * 1000UL)) { publishSensorData(); lastSendTime = millis(); } delay(1000); } Gdy wszystko pasowało zmontowałem w jedną całość i rozpocząłem kondigurację z Home Assistant aby odbierać dane temperatura i wilgotność. Całość gotowa i działa. Dane są odbierane w Home Assistant z dwóch encji. Dzięki temu mogę wykorzystać to do uruchamiania klimatyzacji, nawilżacza powietrza lub ogrzewania. Całość działą lokalnie dzięki temu wyeliminowałem chmure i chińskie serwery i całkowicie odszedłem od LocalTuya i Sonoff. Obecnie takich termometrów mam 5, testuje różne kolory wydruku 3D i wszystkie działają w Home Assistant. Projekt pewnie dla wielu z Was jest prosty ale bardzo mi pomógł w działamiu smart home i zrodził kolejne pomysły na następne urządzenia które są już w trakcie budowy. Dziękuję za uwagę. 😉
-
Przeglądając listę możliwych integracji Home Assistant w większości jest tam to czego potrzebujemy. Sam byłem w szoku, gdy aplikacja podpowiedziała mi integrację z tunerem audio, o którym bym nawet nie pomyślał że się do tego nadaje. Problem pojawia się, gdy wymyślimy sobie własne DIY, które robi coś unikatowego i chcemy to podłączyć pod automatykę domowa. Jedną z metod jest użycie Template Switch (czyli takiego wirtualnego przełącznika) i powiązanie go funkcją lambda z np. komponentem magistrali UART i komunikowanie się z naszym DIY. Problem w tym, że będziemy musieli poświęcić cały układ WiFi na pomost pomiędzy DIY, a centralką HA. W tym artykule postaram się nakreślić, jak zacząć pisać własne komponenty do ESPHome. Przygotowanie Niby jest do tego instrukcja (custom sensor i custom generic component), ale mimo wszystko po przeczytaniu tego co tam zamieszczono, przejrzeniu przykładów, zapytaniu na oficjalnym kanale na Discordzie, odpowiedź znalazłem dopiero na szarym końcu internetu. Wyjdźmy od tego jak tworzymy aplikacje (wsad np. do ESP8266 tu ESP-01S). Mając postawiony HA i zainstalowany dodatek ESPHome, dodajemy nowy sprzęt: Wybieramy nazwę: Rodzaj płytki: Pomijamy wgrywanie, bo trzeba zmienić coś w konfiguracji. Wybieramy więc edit: Mamy tu kilka domyślnych ustawień: esphome: name: spectrum-display esp8266: board: esp01_1m # Enable logging logger: # Enable Home Assistant API api: ota: password: "xxx" wifi: ssid: !secret wifi_ssid password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "xxx" password: "xxx" captive_portal: Nazwa - potrzebna do mDNS jako hostname. Niestety po aktualizacji coś słabo działa. Na stronie ESPHome sugeruje się używanie statycznego IP, które ma też przyspieszać łączenie: Dlatego dodajemy fragment dotyczący stałego IP: wifi: ssid: !secret wifi_ssid password: !secret wifi_password manual_ip: static_ip: 192.168.0.102 gateway: 192.168.0.1 subnet: 255.255.255.0 # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "xxx" password: "xxx" Co w efekcie zobaczymy w logu: Hasło do WiFi mamy ustawione w ESPHome (secrets). Zapisujemy, wgrywamy podłączając urządzenie przez USB lub wgrywając używając OTA, oczywiście jeżeli znamy hasło i adres urządzenia. W moim przypadku jest ta druga opcja: Gotowe komponenty Komponent to pewna funkcjonalność (nawet bardzo rozbudowana). Np. może to być przełącznik światła dołączonego do konkretnego wyprowadzenia (patrz przykład). W przypadku bardziej rozbudowanych komponentów, np. obsłudze protokołu komunikacji, obsługa zawęża się do wskazania urządzenia (np. DS18B20 1-wire) i podania wyprowadzenia. Takich komponentów możemy dodawać wiele i będą działać niezależnie od siebie. To tak jakbyśmy uruchomili wiele współbieżnych procesów. W praktyce wygląda to bardziej jak kod Arduino, w którym przy pomocy funkcji millis() wykonujemy "współbieżnie" wiele funkcji. Podstawy za nami. Teraz konfigurację można wzbogacić o jakieś podstawowe komponenty. Przykładowo kod do sterowania diodą na płytce pod pinem 2: # Enable Home Assistant API api: light: - platform: binary name: "Onboard LED" output: onboard_led_out output: - id: onboard_led_out platform: gpio pin: GPIO2 Id posłuży nam do dodania tzw. encji czyli kontrolki na panelu widocznym w Home Assistant. Wgrywamy i przechodzimy do głównej strony i edytujemy widok panelu: Od razu widzimy encję: Modyfikujemy wedle uznania: i gotowe! Możemy poklikać w nowo dodany przycisk. Najpewniej logika przycisku będzie odwrócona ze względu na sposób podłączenia LED na płytce ESP-01S. Możemy zmienić to w konfiguracji: output: - id: onboard_led_out platform: gpio pin: GPIO2 inverted: true I działa 🙂 Własny komponent Napisanie własnego komponentu nie jest aż tak trudne. Autorzy uznali, że będą wzorować się na koncepcji kodu Arduino , w którym można wyróżnić bloki setup() i loop(). Problem w tym, że nie wiadomo gdzie zapisać te pliki. Na stronie z poradnikiem tworzenia własnych komponentów jest co prawda informacja: Ale niewiele to wnosi, bo nigdzie nie jest napisane gdzie jest ten katalog... Z pomocą przychodzi test przykładowej konfiguracji, w której chcę dodać jakiś plik: Czyli jest to ścieżka: /config/esphome/ Sprawdźmy jak wygląda zawartość tego katalogu, tylko zanim do teog przejdziemy, potrzeba nam SSH - bo w systemie HA nie ma domyślnie takich udogodnień. W repozytorium znajdujemy SSH, ustawiamy hasło i możemy się zalogować. Użytkownik root, hasło własne, port 22. Używam polecenie ls z dopiskiem -a, aby wyświetlić ukryte pliki i katalogi: la -a /config/esphome/ W tym miejscu można umieścić własne pliki bibliotek - tuż obok widzimy plik yaml konfiguracji. Ja swoje pliki umieszczam w katalogu custom_components tak by nie tworzyć bałaganu: Wewnątrz katalogu tworzę plik fps_meter.h: touch fps_meter.h Do pracy korzystam z WinSCP i VSC - zapis pliku w VSC od razu zdalnie go aktualizuje: Dla testu wpisuję kod służący do wyznaczania częstotliwości odświeżeni: #include "esphome.h" class FPSCounter : public Component, public Sensor { private: const unsigned long INTERVAL = 5000; unsigned long counter; unsigned long last_millis; float fps; public: float get_setup_priority() const override { return esphome::setup_priority::LATE; } void setup() override { last_millis = millis(); int last_button_state = HIGH; } void loop() override { if(millis() - last_millis > INTERVAL) { fps = (1000.0 * counter) / (millis() - last_millis); ESP_LOGD("FPS_COUNTER", "%lu sec passed, FPS: %f", INTERVAL, fps); publish_state(fps); counter = 0; last_millis = millis(); } ++counter; } }; I zapisuję. Do tego jak działa ten kod jeszcze wrócimy, ale na razie istotne jest, że w pętli wykonywane jest sprawdzenie częstości odświeżania i okresowo informacja wysyła jest do HA. Komponent jest czujnikiem (dziedziczy po klasie Sensor) ponieważ zwraca pewne informacje do centralki. Teraz trzeba użyć naszą klasę. Przechodzimy do konfiguracji i najpierw dodajemy plik biblioteki: esphome: name: spectrum-display includes: - custom_components/fps_meter.h oraz tworzymy komponent: api: sensor: - platform: custom lambda: |- auto fps_meter = new FPSCounter(); App.register_component(fps_meter); return {fps_meter}; sensors: name: "FPS counter" light: - platform: binary name: "Onboard LED" output: onboard_led_out output: - id: onboard_led_out platform: gpio pin: GPIO2 inverted: true Dodajemy typ sensor (to po czym dziedziczy nasz komponent), a w funkcji lambda korzystamy ze zdefiniowanej klasy FPSCounter. Dodajemy też nazwę, która może nam się przydać. Wgrywamy nowy kod i w logu zobaczymy, że coś zostało wyznaczone - mamy częstotliwość odświeżania około 60Hz: Możemy też znaleźć nasz "czujnik" wśród encji: i nasze obie kontrolki działają, powiedzmy "równolegle" 🙂 Kod programu Wracając na chwilę do kodu programu: #include "esphome.h" class FPSCounter : public Component, public Sensor { public: float get_setup_priority() const override { return esphome::setup_priority::LATE; } void setup() override { } void loop() override { }; widzimy, że szablon jest dość prosty. Tworzymy klasę, która dziedziczy po komponencie i sensorze, aby móc wysyłać informacje (dostępna staje się wtedy metoda publish_state(). Jak wspomniałem setup i loop to metody, które możemy uzupełnić kodem wykonywanym odpowiednio przy starcie i cyklicznie. get_setup_priority() służy ustaleniu jaki priorytet ma nasza klasa. Możliwych narzędzi jest naprawdę wiele, zainteresowanych odsyłam do lektury dokumentacji i analizy przykładów. Kod klasy możemy rozbić na osobne pliki .cpp i .h. Warto jeszcze wspomnieć o czymś, co mnie bardzo mocno zmyliło - o bibliotekach/narzędziach deweloperskich ESPHome. W kodzie widzimy, że dodajemy plik esphome.h, ale nie jest to biblioteka: W pliku tym mamy podlinkowane biblioteki, które będziemy używać, a sam plik wygenerowany zostanie automatycznie... zostanie, ponieważ nasza biblioteka jest w katalogu /config/esphome, ale przed kompilacją jest kopiowana do katalogu projektu: Czyli dla powtórzenia: nasze pliki bibliotek trzymamy w głównym katalogu np. /config/esphome/custom_components/ w pliku .yaml dodajemy ścieżkę: custom_components/plik_biblioteki.h w kodzie pliku plik_biblioteki.h dodajemy esphome.h jakby był tuż obok, bo przed kompilacją zostanie tam przekopiowany tworząc kod pamietamy żeby podlinkować zawartość katalogu src Kuszące może być edytowanie zawartości katalogu src jednak tu uwaga w pliku README.txt: THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY ESPHome automatically populates the build directory, and any changes to this directory will be removed the next time esphome is run. For modifying esphome's core files, please use a development esphome install, the custom_components folder or the external_components feature. Oznacza to, że ten katalog jest wygenerowany automatycznie na bazie pliku .yaml i nie powinniśmy w nim nic mieszać.
- 5 odpowiedzi
-
- 3
-
-
- Raspberry Pi
- Home Assistant
- (i 1 więcej)