diff --git a/02_Software/.gitignore b/02_Software/.gitignore new file mode 100644 index 0000000..d8b4157 --- /dev/null +++ b/02_Software/.gitignore @@ -0,0 +1,5 @@ +# Gitignore settings for ESPHome +# This is an example and may include too much for your use-case. +# You can modify this file to suit your needs. +/.esphome/ +/secrets.yaml diff --git a/02_Software/esphome-bohemahouse.yaml b/02_Software/esphome-bohemahouse.yaml new file mode 100644 index 0000000..9b6e4e2 --- /dev/null +++ b/02_Software/esphome-bohemahouse.yaml @@ -0,0 +1,213 @@ +packages: + led_indicator: !include led_status.yaml + +globals: + - id: custom_prefix + type: std::string + +esphome: + name: esphome-bohemahouse + friendly_name: ESPHome BohemaHouse + name_add_mac_suffix: true + on_boot: + priority: 800.0 + then: + - lambda: |- + std::string auto_node_name = App.get_name(); + + /* MQTT topic prefix */ + id(custom_prefix) = "bohemahouse/" + auto_node_name; + id(mqtt_client)->set_topic_prefix(id(custom_prefix), auto_node_name); + + /* AP fallback */ + auto ap_config = id(wifi_component)->get_ap(); + ap_config.set_ssid(auto_node_name); + ap_config.set_password("bohemahouse"); + id(wifi_component)->set_ap(ap_config); + - script.execute: + id: set_led_state + state_name: "WIFI_SEARCHING" + +esp32: + board: nodemcu-32s + framework: + type: arduino + +logger: + level: VERBOSE + +interval: + - interval: 2s + then: + - if: + condition: + lambda: 'return id(bh_captive_portal)->is_active();' + then: + - script.execute: + id: set_led_state + state_name: "AP_FALLBACK" + +switch: + - platform: gpio + name: LED + id: switch_led + internal: true + pin: GPIO2 + +web_server: + port: 80 + auth: + username: !secret web_server_username + password: !secret web_server_password + +ota: + - platform: web_server + on_progress: + then: + - script.execute: + id: set_led_state + state_name: "OTA_UPDATE" + +wifi: + id: wifi_component + ssid: !secret wifi_ssid_test + password: !secret wifi_password_test + fast_connect: true + power_save_mode: HIGH + on_connect: + then: + - script.execute: + id: set_led_state + state_name: "WIFI_CONNECTED" + on_disconnect: + then: + - script.execute: + id: set_led_state + state_name: "WIFI_SEARCHING" + ap: + ap_timeout: 30s +captive_portal: + id: bh_captive_portal + +# MQTT over TLS +mqtt: + id: mqtt_client + broker: jacknick.xyz + password: !secret mqtt_secret + username: winery_device + port: 8883 + discover_ip: false + discovery: false + discovery_unique_id_generator: mac + certificate_authority: | + -----BEGIN CERTIFICATE----- + MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw + TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh + cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 + WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu + ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY + MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc + h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ + 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U + A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW + T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH + B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC + B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv + KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn + OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn + jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw + qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI + rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV + HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq + hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL + ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ + 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK + NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 + ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur + TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC + jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc + oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq + 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA + mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d + emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= + -----END CERTIFICATE----- + skip_cert_cn_check: true + on_disconnect: + then: + - script.execute: + id: set_led_state + state_name: "MQTT_FAILED" + +# DS18B20 Temperature Sensor +one_wire: + - platform: gpio + id: one_wire_gpio_1 + pin: GPIO23 + +sensor: + - platform: dallas_temp + name: Temperature + id: sensor_dallas_temp_1 + one_wire_id: one_wire_gpio_1 + update_interval: 10s + resolution: 12 + unit_of_measurement: °C + accuracy_decimals: 1 + internal: true + on_value: + then: + # Topic 1: Raw temperature value + - mqtt.publish: + topic: !lambda |- + return id(custom_prefix) + "/barrel_1/sensor/temperature/state"; + payload: !lambda 'return String(x).c_str();' + + # Topic 2: Temperature + 5 degrees + - mqtt.publish: + topic: !lambda |- + return id(custom_prefix) + "/barrel_2/sensor/temperature/state"; + payload: !lambda 'return String(x + 1.0).c_str();' + + # Topic 3: Temperature - 5 degrees + - mqtt.publish: + topic: !lambda |- + return id(custom_prefix) + "/barrel_3/sensor/temperature/state"; + payload: !lambda 'return String(x + 2.0).c_str();' + + # Topic 4: Custom offset (+2.5 degrees) + - mqtt.publish: + topic: !lambda |- + return id(custom_prefix) + "/barrel_4/sensor/temperature/state"; + payload: !lambda 'return String(x + 3.0).c_str();' + - script.execute: + id: set_led_state + state_name: "MQTT_SUCCESS" + +# Factory Reset — hold BOOT button 10 seconds +binary_sensor: + - platform: gpio + name: GPIO Binary Sensor + id: boot_button + pin: + number: GPIO0 + mode: + input: true + pullup: true + inverted: true + filters: + - delayed_on: 10s + on_press: + - logger.log: "Factory reset triggered!" + - button.press: button_factory_reset + +button: + - platform: factory_reset + name: Factory Reset + id: button_factory_reset + +text_sensor: + - platform: wifi_info + ip_address: + name: "Device IP Address" + mac_address: + name: "Device Unique MAC Address" diff --git a/02_Software/isrgrootx1.pem b/02_Software/isrgrootx1.pem new file mode 100644 index 0000000..b85c803 --- /dev/null +++ b/02_Software/isrgrootx1.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/02_Software/led_status.yaml b/02_Software/led_status.yaml new file mode 100644 index 0000000..6c07e6d --- /dev/null +++ b/02_Software/led_status.yaml @@ -0,0 +1,130 @@ +globals: + - id: led_state + type: std::string + initial_value: '"WIFI_SEARCHING"' + +# Corrected led_status.yaml looping scripts +script: + # Pattern 1: Three rapid blinks (Looping) + - id: led_pattern_wifi_searching + mode: restart + then: + - while: + condition: + lambda: 'return true;' + then: + - repeat: + count: 3 + then: + - switch.turn_on: switch_led + - delay: 100ms + - switch.turn_off: switch_led + - delay: 100ms + - delay: 1000ms + + # Pattern 2: Fallback AP active (1 long blink + 3 rapid blinks loop) + - id: led_pattern_ap_fallback + mode: single + then: + - while: + condition: + lambda: 'return true;' + then: + - switch.turn_on: switch_led + - delay: 1000ms + - switch.turn_off: switch_led + - delay: 500ms + - repeat: + count: 3 + then: + - switch.turn_on: switch_led + - delay: 100ms + - switch.turn_off: switch_led + - delay: 100ms + - delay: 1000ms + + # Pattern 3: Two long blinks (Connected WiFi) + - id: led_pattern_wifi_connected + mode: single + then: + - while: + condition: + lambda: 'return true;' + then: + - repeat: + count: 2 + then: + - switch.turn_on: switch_led + - delay: 600ms + - switch.turn_off: switch_led + - delay: 400ms + - delay: 1500ms + + # Pattern 4: One rapid blink (Transient event for successful MQTT publish) + - id: led_event_mqtt_success + mode: queued + then: + - switch.turn_on: switch_led + - delay: 100ms + - switch.turn_off: switch_led + + # Pattern 5: One long blink (Transient/State event for MQTT fail) + - id: led_event_mqtt_failed + mode: restart + then: + - switch.turn_on: switch_led + - delay: 1000ms + - switch.turn_off: switch_led + + # Pattern 6: OTA update + - id: led_ota_update_progress + mode: restart + then: + - switch.turn_on: switch_led + - delay: 200ms + - switch.turn_off: switch_led + + - id: set_led_state + parameters: + state_name: string + then: + # 1. Stop all active pattern loops + - script.stop: led_pattern_wifi_searching + - script.stop: led_pattern_wifi_connected + - script.stop: led_pattern_ap_fallback + - script.stop: led_event_mqtt_success + - script.stop: led_event_mqtt_failed + - script.stop: led_ota_update_progress + # 2. Update global state + - lambda: 'id(led_state) = state_name;' + # 3. Route to active pattern + - if: + condition: + lambda: 'return state_name == "WIFI_SEARCHING";' + then: + - script.execute: led_pattern_wifi_searching + - if: + condition: + lambda: 'return state_name == "WIFI_CONNECTED";' + then: + - script.execute: led_pattern_wifi_connected + - if: + condition: + lambda: 'return state_name == "AP_FALLBACK";' + then: + - script.execute: led_pattern_ap_fallback + - if: + condition: + lambda: 'return state_name == "MQTT_SUCCESS";' + then: + - script.execute: led_event_mqtt_success + - if: + condition: + lambda: 'return state_name == "MQTT_FAILED";' + then: + - script.execute: led_event_mqtt_failed + - if: + condition: + lambda: 'return state_name == "OTA_UPDATE";' + then: + - script.execute: led_ota_update_progress diff --git a/README.md b/README.md index 479568b..3c0c85a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,140 @@ # fw-BH-barrel-automation Automation of barrel control for BohemaHouse winery + +MQTT + * User: winery_device + * Pass: B2h3maH@use + +AP + * SSID: esphome-bohemahouse-xxxxxx + * Pass: bohemahouse + +Local IP (OTA) + * User: bohemahouse + * Pass: B2h3maH@use + +--- + +## 1. Mosquitto (MQTT Broker) + +**Installation:** +```bash +sudo apt update +sudo apt install -y mosquitto mosquitto-clients +``` + +**Configuration (Authentication & WebSockets):** +Create the password file using the credentials above: +```bash +sudo mosquitto_passwd -c /etc/mosquitto/passwd winery_device +``` + +Edit the Mosquitto configuration: +```bash +sudo nano /etc/mosquitto/conf.d/default.conf +``` +Add the following configuration to allow standard MQTT locally and WebSockets for external Apache proxying: +```text +# Standard MQTT (Local) +listener 1883 +allow_anonymous false +password_file /etc/mosquitto/passwd + +# WebSockets (Required for Apache reverse proxy from outside) +listener 9001 +protocol websockets +``` +Restart the service: +```bash +sudo systemctl restart mosquitto +``` + +## 2. Node-RED + +**Installation:** +Use the official installation script for Linux/Raspberry Pi: +```bash +bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-sh-script/master/node-red-pi-setup) +``` + +**Start and Enable on Boot:** +```bash +sudo systemctl enable nodered.service +sudo systemctl start nodered.service +``` +*Access Node-RED at: `http://:1880`* + +## 3. InfluxDB (v2) + +**Installation:** +```bash +curl -O https://dl.influxdata.com/influxdb/releases/influxdb2_2.7.5-1_$(dpkg --print-architecture).deb +sudo dpkg -i influxdb2_2.7.5-1_*.deb +sudo systemctl enable --now influxdb +``` + +**Configuration:** +* Access the dashboard at `http://:8086` +* Setup Organization: `BohemaHouse` +* Setup Bucket: `barrels` +* Generate an **All Access API Token**. In Node-RED, install `node-red-contrib-influxdb` and use this token to connect the output node. + +## 4. Grafana + +**Installation:** +```bash +sudo apt-get install -y apt-transport-https software-properties-common wget +sudo wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key +echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list +sudo apt update +sudo apt install -y grafana +``` + +**Change Port (Avoids conflict with Gitea on port 3000):** +```bash +sudo nano /etc/grafana/grafana.ini +``` +Find the `[server]` section, uncomment the port line, and change it: +```ini +http_port = 3030 +``` + +**Start and Enable on Boot:** +```bash +sudo systemctl enable --now grafana-server +``` +*Access Grafana at: `http://:3030`* +*Note: Connect to InfluxDB using the **Flux** query language.* + +--- + +## Remote Access (Apache Reverse Proxy for Mosquitto) + +To safely receive MQTT packages from outside the local network using your existing Apache server, you should expose Mosquitto via **MQTT over WebSockets**. + +1. **Enable Apache WebSocket Proxy Modules:** +```bash +sudo a2enmod proxy +sudo a2enmod proxy_wstunnel +sudo systemctl restart apache2 +``` + +2. **Apache VirtualHost Configuration:** +Add the following `ProxyPass` rules inside your existing HTTPS `` block for your domain (e.g., in `/etc/apache2/sites-available/yourdomain.conf`): + +```apache +# Proxy MQTT WebSockets to local Mosquitto on port 9001 +ProxyPass /mqtt ws://127.0.0.1:9001/ +ProxyPassReverse /mqtt ws://127.0.0.1:9001/ +``` + +3. **Apply Changes:** +```bash +sudo systemctl reload apache2 +``` + +**Connecting from the Outside:** +Your external devices (like the ESP32 or an external web dashboard) can now connect to your MQTT broker using the WebSocket protocol over TLS: +* **URL:** `wss://yourdomain.com/mqtt` +* **Username/Password:** Use the `winery_device` credentials.