Categorie: Geen categorie

Slim Irrigatiesysteem

Slim Irrigatiesysteem

🌱 Slim Irrigatiesysteem met ESPHome, KC868‑A6 en Dynamische Energieprijzen

Water geven is niet moeilijk — maar het efficiënt, energiezuinig en volledig geautomatiseerd doen, dat is een ander verhaal. In dit project bouwde ik een compleet irrigatiesysteem op basis van:

  • ESPHome
  • Kincony KC868‑A6
  • Home Assistant
  • Dynamische energieprijzen via cheapest_energy_hours.jinja (TheFes)

Het resultaat is een systeem dat:

  • vijf irrigatiezones aanstuurt
  • een pomp schakelt
  • lokaal bedienbaar is via hardware‑knoppen
  • volledig integreert met Home Assistant
  • automatisch draait op de goedkoopste uren van de dag
  • en zelfs na een reboot weet wanneer elke zone voor het laatst actief was
  • irrigatie duur wordt in de ESP geregeld, zodat een zone niet per ongeluk aan blijft als Home Assistant in de tussentijd herstart

🔧 Hardware: Kincony KC868‑A6

De KC868‑A6 is een veelzijdige controller die ideaal is voor irrigatieprojecten.

Eigenschap Beschrijving
MCU ESP32 (WiFi + Bluetooth)
Relais 6 kanalen, 10A, optisch geïsoleerd
Ingangen 6 digitale ingangen (PCF8574)
Uitgangen 8 digitale uitgangen (PCF8574)
I²C‑bus Voor displays, sensoren, uitbreidingen
Voeding 12V input, interne 5V/3.3V conversie
Extra’s RS485, IR, RF, uitbreidingsheaders

Meer info:
KC868‑A6 hardware details


💧 Irrigatiecontroller met ESPHome

De configuratie bevat:

  • 5 irrigatiezones
  • pompbeveiliging
  • OLED‑display
  • lokale bediening via knoppen
  • persistent “last run” geheugen
  • Home Assistant integratie

⚡ Slimme automatisering: irrigeren op de goedkoopste uren

Met dynamische energieprijzen is het zonde om de pomp te laten draaien op dure uren. Daarom gebruik ik de cheapest_energy_hours.jinja template van TheFes.

Repo:
https://github.com/TheFes/cheapest-energy-hours


🧠 template sensor: “energy low 1 hour daytime”


# template sensor
- trigger:
    - platform: time
      at:
        - "8:00"
#        - "14:16"
    - platform: homeassistant
      event: start
  #  - platform: state
  #    entity_id:
  #          - binary_sensor.dishwasher_door
  #          - binary_sensor.dishwasher_remote_active
  sensor:
    - unique_id: f8853830-bf81-46d4-85a9-379c25c37c23
      name: Vijver bijvullen Start Time
      device_class: timestamp
      state: >
        {%- set sensor = 'zonneplan' -%}
        {% from "cheapest_energy_hours.jinja" import cheapest_energy_hours %}
        {{ cheapest_energy_hours(sensor=sensor, hours=1, start='8:00', end='17:00', include_tomorrow=false) }}


🧠 Automation: “Vijver bijvullen op goedkoopste uur”


alias: Lowest price vijver bijvullen
triggers:
  - trigger: time
    at: sensor.vijver_bijvullen_start_time

conditions:
  - condition: template
    value_template: >
      {{
        states('sensor.irrigation_vijver_laatst_gestart') not in ['unknown','unavailable','','Nog nooit']
        and
        (now() - (states('sensor.irrigation_vijver_laatst_gestart') | as_datetime | as_local)).total_seconds() > 18 * 3600
      }}

  - condition: numeric_state
    entity_id: sensor.cmx_temp
    above: 5

actions:
  - action: number.set_value
    target:
      entity_id: number.irrigation_vijver
    data:
      value: "7"

  - action: switch.turn_on
    target:
      entity_id: switch.vijver

mode: single

📄 Volledige ESPHome‑configuratie

Hieronder staat de volledige YAML‑configuratie van het irrigatiesysteem.


# Processing irrigation (board: nodemcuv2; framework: arduino; platform: platformio/espressif8266@3.2.0)
# --------------------------------------------------------------------------------
# HARDWARE: ESP8266 80MHz, 80KB RAM, 4MB Flash
# https://www.kincony.com/kc868-a6-hardware-design-details.html
# https://www.kincony.com/forum/showthread.php?tid=1963
# Based on ESPHome Sprinkler Controller - https://esphome.io/components/sprinkler.html
# https://community.home-assistant.io/t/sprinkler-automation-with-esphome-a-complete-project/565077
# Change Log
# 2023 01 XX
  # Initial version
  
# 2023 04 09 V03
  # fix run duration to seconds 
# 2023 04 22 V04
  # fix GPIO order to match relay 1- 4
  # added % at the lambda return for progress sensor return value
# 2023 04 25 V05
  # added nodemcu as sensor to display in HA ui
  # added includes for api key en ota password
# 2023 05 07 V06
  # added a NTC temp sensor to  watch the enclosure temperature
# 2023 05 10 V07
  # addjusted settings reference voltage to adjust to actual temp (default 3.3)
# 2023 06 06 V08
  # adjusted settings for valves corresponding to switches
  # added repeat function
# 2024 04 04 V09
  # Corrected the value of KEY ESPHOME_PROJECT_VERSION due to compiling error esphome v2024.3.1
  # initializer-string for 'char [30]' is too long
  # changed 2023 06 06 V08 to 20240404_V09
  # removed the text "Irrigation Controller.,"  
# 20260312_V01Peterpc
  # Wanneer laatst actief toegevoegd.
# 20260314_V01Peterpc
  # Bij reboot laatst actief weer naar HA sturen.


# Establish Substitutions
substitutions:
### Modify only the following lines.
  friendly_name: "Irrigation Controller"

  zone_1_name: Voortuin
  zone_1_last_run_id: voortuin_last_run

  zone_2_name: Achtertuin
  zone_2_last_run_id: achtertuin_last_run

  zone_3_name: Serre
  zone_3_last_run_id: serre_last_run

  zone_4_name: Vijver
  zone_4_last_run_id: vijver_last_run

  zone_5_name: Spare
  zone_5_last_run_id: zone5_last_run

##############################################
#  DO NOT CHANGE ANYTHING BELOW THIS LINE  ###
##############################################
  software_version: 20260314_V02Peterpc
  sensor_update_frequency: 1s
  log_level: debug 
  # Enable levels logging https://esphome.io/components/logger.html
  # none, error, warn, info, debug (default), verbose, very_verbose
  zone_1_valve_id: valve_0
  zone_2_valve_id: valve_1
  zone_3_valve_id: valve_2
  zone_4_valve_id: valve_3
  zone_5_valve_id: valve_4
  esphome_name: irrigation
  esphome_platform: ESP32
  esphome_board: esp32dev
  esphome_comment: Five Valve Irrigation Controller
  esphome_project_name: jaya.Irrigation Controller
  esphome_project_version: $software_version
  devicename: irrigation_controller
  upper_devicename: "Irrigation Controller"
  uom: Min # this overrides the uom in sprinkler -> run_duration 


#Define Project Deatils and ESP Board Type
esphome:
  name: $esphome_name
  comment: $esphome_comment
  project:
    name: $esphome_project_name
    version: $esphome_project_version
  on_boot:
    - priority: -100
      then:
        # Set default state for Valve Status
        - text_sensor.template.publish:
            id: valve_status
            state: "Idle"
        # Set multiplier to 60, convert seconds to minutes
        - sprinkler.set_multiplier:
            id: $devicename
            multiplier: 60
    - priority: -10
      then:
        - text_sensor.template.publish:
            id: ${zone_1_last_run_id}
            state: !lambda 'return id(zone1_last_run_global);'
        - text_sensor.template.publish:
            id: ${zone_2_last_run_id}
            state: !lambda 'return id(zone2_last_run_global);'
        - text_sensor.template.publish:
            id: ${zone_3_last_run_id}
            state: !lambda 'return id(zone3_last_run_global);'
        - text_sensor.template.publish:
            id: ${zone_4_last_run_id}
            state: !lambda 'return id(zone4_last_run_global);'
        - text_sensor.template.publish:
            id: ${zone_5_last_run_id}
            state: !lambda 'return id(zone5_last_run_global);'


esp32:
  board: $esphome_board
  framework:
    type: esp-idf
wifi:
  ssid: !secret wifi_ssid24
  password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "$esphome_name Fallback Hotspot"
    password: !secret esphome_ap_password
  
logger:
  level: ${log_level}
  logs:
    text_sensor: WARN
  
<<: !include common/ota.yaml

# Enable Web server.
web_server:
  port: 80

# Sync time with Home Assistant.
time:
  - platform: homeassistant
    id: homeassistant_time
###############################################
# restart device at 4 at night so if a wifi
# problem is there it will solve
###############################################
    on_time:
      - seconds: 0
        minutes: 0
        hours: 4
        days_of_week: MON-SUN
        then:
           - switch.toggle: restartit

   
###############################################
# Enable Home Assistant API
###############################################
api:
  encryption:
    key: !secret encryption_key
  reboot_timeout: 0s

###############################################
# store last run text sensors to flash
###############################################
globals:
  - id: zone1_last_run_global
    type: std::string
    restore_value: true
    initial_value: "\"Nog nooit\""

  - id: zone2_last_run_global
    type: std::string
    restore_value: true
    initial_value: "\"Nog nooit\""

  - id: zone3_last_run_global
    type: std::string
    restore_value: true
    initial_value: "\"Nog nooit\""

  - id: zone4_last_run_global
    type: std::string
    restore_value: true
    initial_value: "\"Nog nooit\""

  - id: zone5_last_run_global
    type: std::string
    restore_value: true
    initial_value: "\"Nog nooit\""



###############################################
# Text sensors with general information.
###############################################
text_sensor:
  - platform: version # Expose ESPHome version as sensor.
    name: $esphome_name ESPHome Version
    hide_timestamp: false
  - platform: wifi_info
    ip_address:
      name: "$esphome_name IP"
    ssid:
      name: "$esphome_name SSID"
    bssid:
      name: "$esphome_name BSSID"

# Expose Time Remaining as a sensor.
  - platform: template
    id: time_remaining
    name: $upper_devicename Time Remaining
    update_interval: $sensor_update_frequency
    icon: "mdi:timer-sand"
    lambda: |-
      int seconds = round(id($devicename).time_remaining_active_valve().value_or(0));
      int days = seconds / (24 * 3600);
      seconds = seconds % (24 * 3600);
      int hours = seconds / 3600;
      seconds = seconds % 3600;
      int minutes = seconds / 60;
      seconds = seconds % 60;

      std::string result;

      if (days > 0) result += std::to_string(days) + "d ";
      if (hours > 0) result += std::to_string(hours) + "h ";
      if (minutes > 0) result += std::to_string(minutes) + "m ";
      result += std::to_string(seconds) + "s";

      return result;

  # Expose Progress Percent as a sensor.
  - platform: template
    id: progress_percent
    name: $upper_devicename Progress %
    update_interval: $sensor_update_frequency
    icon: "mdi:progress-clock"
    lambda: |-
      int progress_percent = round(((id($devicename).valve_run_duration_adjusted(id($devicename).active_valve().value_or(0)) - id($devicename).time_remaining_active_valve().value_or(0)) * 100 / id($devicename).valve_run_duration_adjusted(id($devicename).active_valve().value_or(0))));
      std::string progress_percent_as_string = std::to_string(progress_percent);
      return progress_percent_as_string +"%";

  # Expose Valve Status as a sensor.
  - platform: template
    id: valve_status
    name: $upper_devicename Status
    update_interval: never
    icon: "mdi:information-variant"

  - platform: template # Expose the board type as a sensor
    id: espboard_type
    icon: "mdi:developer-board"
    name: $esphome_name ESPBoard
    lambda: |-
      return esphome::optional(std::string("${esphome_board}"));

  - platform: template
    id: ${zone_1_last_run_id}
    name: "$zone_1_name Laatst Gestart"
    icon: "mdi:clock-start"
    update_interval: never
    lambda: |-
      return id(zone1_last_run_global);

  - platform: template
    id: ${zone_2_last_run_id}
    name: "$zone_2_name Laatst Gestart"
    icon: "mdi:clock-start"
    update_interval: never
    lambda: |-
      return id(zone2_last_run_global);

  - platform: template
    id: ${zone_3_last_run_id}
    name: "$zone_3_name Laatst Gestart"
    icon: "mdi:clock-start"
    update_interval: never
    lambda: |-
      return id(zone3_last_run_global);

  - platform: template
    id: ${zone_4_last_run_id}
    name: "$zone_4_name Laatst Gestart"
    icon: "mdi:clock-start"
    update_interval: never
    lambda: |-
      return id(zone4_last_run_global);

  - platform: template
    id: ${zone_5_last_run_id}
    name: "$zone_5_name Laatst Gestart"
    icon: "mdi:clock-start"
    update_interval: never
    lambda: |-
      return id(zone5_last_run_global);



sensor:
  # Uptime sensor.
  - platform: uptime
    name: $upper_devicename Uptime

  # WiFi Signal sensor.
  - platform: wifi_signal
    name: $upper_devicename WiFi Signal
    update_interval: 60s
  
###############################################
# Configuration to set multiplier via number 
############################################### 
number:
  - platform: template
    id: $zone_1_valve_id
    name: $zone_1_name
    min_value: 0
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(0);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 0
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_2_valve_id
    name: $zone_2_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(1);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 1
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_3_valve_id
    name: $zone_3_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(2);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 2
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_4_valve_id
    name: $zone_4_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(3);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 3
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_5_valve_id
    name: $zone_5_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(4);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 4
          run_duration: !lambda 'return x;'
  - platform: template
    id: sprinkler_ctrlr_repeat_cycles
    name: "Sprinkler Repeat Cycles"
    min_value: 0
    max_value: 4
    step: 1
    mode: box
    icon: "mdi:water-sync"
    lambda: "return id($devicename).repeat();"
    set_action:
      - sprinkler.set_repeat:
          id: $devicename
          repeat: !lambda 'return x;'

###############################################
# Main Sprinkler Controller
###############################################
sprinkler:
  - id: $devicename
    pump_start_pump_delay: 3s
    pump_stop_valve_delay: 3s
    main_switch:
      name: "Start/Stop/Resume"
      id: main_switch
    auto_advance_switch: "Auto Advance"
    valve_open_delay: 2s
    repeat_number: "Repeat"
    valves:
      - valve_switch: $zone_1_name
        enable_switch: Enable $zone_1_name
        pump_switch_id: sprinkler_pump_sw
        run_duration: 30s
        valve_switch_id: ${devicename}_1
      - valve_switch: $zone_2_name
        enable_switch: Enable $zone_2_name
        pump_switch_id: sprinkler_pump_sw
        run_duration: 3s
        valve_switch_id: ${devicename}_2
      - valve_switch: $zone_3_name
        enable_switch: Enable $zone_3_name
        pump_switch_id: sprinkler_pump_sw
        run_duration: 3s
        valve_switch_id: ${devicename}_3
      - valve_switch: $zone_4_name
        enable_switch: Enable $zone_4_name
        pump_switch_id: sprinkler_pump_sw
        run_duration: 7s
        valve_switch_id: ${devicename}_4
      - valve_switch: $zone_5_name
        enable_switch: Enable $zone_5_name
        pump_switch_id: sprinkler_pump_sw
        run_duration: 15s
        valve_switch_id: ${devicename}_5
  
button:
  - platform: template
    id: sprinkler_pause
    name: "Pause"
    icon: "mdi:pause"
    on_press:
      then:
        - text_sensor.template.publish:
            id: valve_status
            state: "Paused"
        - sprinkler.pause: $devicename

####################################################
# init i2c bus.   
####################################################
i2c:
  sda: 4
  scl: 15
  scan: true
  id: bus_a

####################################################
# Fonts for display.   
####################################################
font:
  - file: "common/fonts/Roboto-Regular.ttf"
    id: my_font
    size: 20
    bpp: 2

####################################################
# display
# https://esphome.io/components/display/ssd1306.html  
####################################################
display:
  - platform: ssd1306_i2c
    i2c_id: bus_a
    model: "SSD1306 128x64"
    address: 0x3C
    #    lambda: |-
    #      it.print(0, 0, id(my_font), "Hello World!");

    #it.printf(0, 0, id(roboto), "T: %.1f°C", id(temperature).state);
    #it.printf(0, 20, id(roboto), "H: %.1f%%", id(humidity).state);
    #it.printf(0, 40, id(roboto), "AC:%.1fV", id(voltage_2).state);
    #      it.printf(0, 0, id(my_font), "Z1: %.1f", id(valve_status).state);
#      it.printf(0, 20, id(my_font), "Z2: %.1f", id(valve_1.state);
    lambda: |-
      if (id(${devicename}_1).state) {
        it.print(0, 0, id(my_font), "$zone_1_name: ON");
        it.printf(20, 20, id(my_font), "%s", id(time_remaining).state.c_str());
      } else {
        it.strftime(20, 40, id(my_font), "%H:%M:%S", id(homeassistant_time).now());
      }
      if (id(${devicename}_2).state) {
        it.print(0, 0, id(my_font), "$zone_2_name: ON");
        it.printf(20, 20, id(my_font), "%s", id(time_remaining).state.c_str());
      } else {
        it.print(0, 0, id(my_font), "");
      }
      if (id(${devicename}_3).state) {
        it.print(0, 0, id(my_font), "$zone_3_name: ON");
        it.printf(20, 20, id(my_font), "%s", id(time_remaining).state.c_str());
      } else {
        it.print(0, 0, id(my_font), "");
      }
      if (id(${devicename}_4).state) {
        it.print(0, 0, id(my_font), "$zone_4_name: ON");
        it.printf(20, 20, id(my_font), "%s", id(time_remaining).state.c_str());
      } else {
        it.print(0, 0, id(my_font), "");
      }
      if (id(${devicename}_5).state) {
        it.print(0, 0, id(my_font), "$zone_5_name: ON");
        it.printf(20, 20, id(my_font), "%s", id(time_remaining).state.c_str());
      } else {
        it.print(0, 0, id(my_font), "");
      }
 #     it.printf(0, 20, id(my_font), "%s", id(time_remaining).state.c_str());
 #     it.printf(0, 30, id(my_font), "Serre: %s", id(${devicename}_3).state ? "ON" : "OFF");
 #     it.printf(0, 45, id(my_font), "Vijver: %s", id(${devicename}_4).state ? "ON" : "OFF");
 #     it.printf(0, 0, id(my_font), "Voor: %s", id(${devicename}_1).state ? "ON" : "OFF");
 #     it.printf(0, 30, id(my_font), "Serre: %s", id(${devicename}_3).state ? "ON" : "OFF");

####################################################
# Initialize the channels for the KC868-A6 6 channel relay board.   
####################################################
pcf8574:
  - id: "pcf8574_hub_out_1" # for output channel 1-8
    address: 0x24

  - id: "pcf8574_hub_in_1" # for input channel 1-8
    address: 0x22

####################################################
# Switch Control to restart the irrigation system.   
####################################################
switch:
  - platform: restart
    name: "Restart $devicename"
    id: restartit

# Switch for the NTC https://esphome.io/components/sensor/ntc.html, Prevent self heating by switching the voltage on a GPIO
##  - platform: gpio
##    pin: 
##      number: GPIO5 # Pin D1
##      inverted: false
##    id: ntc_vcc

####################################################
# Hidden I/O  Switches to control irrigation valve relays
####################################################
  - platform: gpio
    name: Relay Board Pin IN1
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_1
    on_turn_on:
      - lambda: |-
          id(zone1_last_run_global) = id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S");
      - text_sensor.template.publish:
          id: ${zone_1_last_run_id}
          state: !lambda 'return id(zone1_last_run_global);'
      - text_sensor.template.publish:
          id: valve_status
          state: "$zone_1_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin:
      pcf8574: pcf8574_hub_out_1 # D5
      number: 0
      mode: OUTPUT
      inverted: true



  - platform: gpio
    name: Relay Board Pin IN2
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_2
    on_turn_on:
      - lambda: |-
          id(zone2_last_run_global) = id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S");
      - text_sensor.template.publish:
          id: ${zone_2_last_run_id}
          state: !lambda 'return id(zone2_last_run_global);'

      - text_sensor.template.publish:
          id: valve_status
          state: "$zone_2_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin:
      pcf8574: pcf8574_hub_out_1 # D6
      number: 1
      mode: OUTPUT
    inverted: true
  - platform: gpio
    name: Relay Board Pin IN3
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_3
    on_turn_on:
      - lambda: |-
          id(zone3_last_run_global) = id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S");
      - text_sensor.template.publish:
          id: ${zone_3_last_run_id}
          state: !lambda 'return id(zone3_last_run_global);'
      - text_sensor.template.publish:
          id: valve_status
          state: "$zone_3_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin:
      pcf8574: pcf8574_hub_out_1 # D7
      number: 2
      mode: OUTPUT
      inverted: true
  - platform: gpio
    name: Relay Board Pin IN4
    restore_mode: RESTORE_DEFAULT_OFF
    internal: true
    id: ${devicename}_4
    on_turn_on:
      - lambda: |-
          id(zone4_last_run_global) = id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S");
      - text_sensor.template.publish:
          id: ${zone_4_last_run_id}
          state: !lambda 'return id(zone4_last_run_global);'
      - text_sensor.template.publish:
          id: valve_status
          state: "$zone_4_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 3
      mode: OUTPUT
      inverted: true

  - platform: gpio
    name: Relay Board Pin IN5
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_5
    on_turn_on:
      - lambda: |-
          id(zone5_last_run_global) = id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S");
      - text_sensor.template.publish:
          id: ${zone_5_last_run_id}
          state: !lambda 'return id(zone5_last_run_global);'
      - text_sensor.template.publish:
          id: valve_status
          state: "$zone_5_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin:
      pcf8574: pcf8574_hub_out_1 # D8
      number: 4
      mode: OUTPUT
    inverted: true
  - platform: gpio
    name: Relay Board Pin IN6
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: sprinkler_pump_sw
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status
          state: "Pump Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin:
      pcf8574: pcf8574_hub_out_1 # D8
      number: 5
      mode: OUTPUT
    inverted: true
###############################################
# Binary Sensor.
###############################################
binary_sensor:
  - platform: homeassistant
    # prevent deep sleep - Needs further investigation on usefullness
    id: prevent_deep_sleep
    name: "$upper_devicename Prevent Deep Sleep"
    entity_id: input_boolean.prevent_deep_sleep  
################################################################
# Hidden I/O  inputs to control irrigation valve relays localy #
################################################################
  - platform: gpio
    name: "a6-input1"
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    on_press:
      then:
        - sprinkler.start_single_valve:
            id: $devicename
            valve_number: 0 
    pin:
      pcf8574: pcf8574_hub_in_1 # D7
      number: 0
      mode: INPUT
      inverted: true
  - platform: gpio
    name: "a6-input2"
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    on_press:
      then:
        - sprinkler.start_single_valve:
            id: $devicename
            valve_number: 1 
    pin:
      pcf8574: pcf8574_hub_in_1 # D7
      number: 1
      mode: INPUT
      inverted: true
  - platform: gpio
    name: "a6-input3"
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    on_press:
      then:
        - sprinkler.start_single_valve:
            id: $devicename
            valve_number: 2
    pin:
      pcf8574: pcf8574_hub_in_1 # D7
      number: 2
      mode: INPUT
      inverted: true
  - platform: gpio
    name: "a6-input4"
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    on_press:
      then:
        - sprinkler.start_single_valve:
            id: $devicename
            valve_number: 3
    pin:
      pcf8574: pcf8574_hub_in_1 # D7
      number: 3
      mode: INPUT
      inverted: true
  - platform: gpio
    name: "a6-input5"
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    on_press:
      then:
        - sprinkler.start_single_valve:
            id: $devicename
            valve_number: 4
    pin:
      pcf8574: pcf8574_hub_in_1 # D7
      number: 4
      mode: INPUT
      inverted: true
  - platform: gpio
    name: "a6-input6"
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    on_press:
      then:
        - sprinkler.shutdown: $devicename
    pin:
      pcf8574: pcf8574_hub_in_1 # D7
      number: 5
      mode: INPUT
      inverted: true
# -----------------------------end---------------------------------------------------

🎯 Conclusie

Dit project combineert krachtige hardware, flexibele software en slimme automatisering. Het resultaat is een irrigatiesysteem dat volledig autonoom werkt, energie bespaart en perfect integreert in een moderne smart home‑omgeving.

Loading

Automatische Jaloezie Aansturing

Automatische Jaloezie Aansturing

DIY Automatische Jaloezie Aansturing met ESP32, Steppermotor en 3D‑Geprinte Behuizing

Wil je je houten jaloezieën automatiseren zonder dure commerciële systemen?
In dit project bouwen we een volledig lokaal werkende jaloezie‑controller met:

  • een ESP32
  • een 28BYJ‑48 steppermotor
  • een ULN2003 driver
  • micro‑switches
  • een 3D‑geprinte behuizing
  • ESPHome + Home Assistant

Hieronder vind je alle onderdelen, de montage, foto’s van het project, schema’s en de volledige ESPHome‑YAML.


Benodigde onderdelen

Mechanisch

Elektronica


Fotoreportage van het project

Foto 1 – Tandwielmechanisme
Close‑up van het interne tandwiel van het jaloeziemechanisme. Dit is het deel waar de motor op aangrijpt.

Foto 2 – Steppermotor + tandwieloverbrenging
De 28BYJ‑48 motor gekoppeld aan het grote tandwiel van de jaloezie.

Foto 3 – Motor in montageframe
De motor zit stevig geklemd in de 3D‑geprinte houder.

Foto 4 – ULN2003 driverboard
Het driverboard dat de steppermotor aanstuurt.

Foto 5 – ESP32 module
De compacte ESP32 D1 mini die in de behuizing past.

Foto 6 – ESP32 bedrading
De bedrading naar de ULN2003 driver.

Foto 7 – Eindschakelaars
De micro‑switches voor handmatige bediening.

Foto 8 – Complete montage
Alle onderdelen gemonteerd in de 3D‑geprinte behuizing.


Elektrisch schema

[ESP32 D1 Mini]
 ├─ 5V  ───────────────→  [ULN2003] VCC
 ├─ GND ───────────────→  [ULN2003] GND
 ├─ GPIO22 ────────────→  [ULN2003] IN1
 ├─ GPIO21 ────────────→  [ULN2003] IN2
 ├─ GPIO17 ────────────→  [ULN2003] IN3
 └─ GPIO16 ────────────→  [ULN2003] IN4

[ULN2003]
 └─ Motor connector → 28BYJ‑48 Steppermotor

[ESP32 D1 Mini]
 ├─ GPIO26 ───────────→  Micro‑switch CW (NO)
 └─ GPIO18 ───────────→  Micro‑switch CCW (NO)

Micro‑switches:
 ├─ COM → GND
 └─ NO  → GPIO (26 of 18)

Mechanisch schema

[3D-geprinte behuizing]
 ├─ Kamer 1: Steppermotor (28BYJ‑48)
 │     └─ Motoras grijpt in het tandwiel van het jaloeziemechanisme
 │
 ├─ Kamer 2: ULN2003 driverboard
 │     └─ Motorstekker direct aangesloten
 │
 ├─ Kamer 3: ESP32 D1 Mini
 │     └─ JST-connector naar driverboard
 │
 └─ Zijkant: Micro-switch CW & CCW
       ├─ Roller raakt de handmatige bedieningshendel
       └─ Functie: handmatige bediening (links/rechts)

Overzichtsschema

[ESP32 D1 Mini]
        │
        ▼
[ULN2003 Driverboard]
        │
        ▼
[28BYJ‑48 Steppermotor]
        │
        ▼
[Jaloezie tandwielmechanisme]

Handmatige bediening:
[Micro-switch CW] ←─┐
[Micro-switch CCW] ←┘

Montage – stap voor stap

1. Print de behuizing

Alle onderdelen passen met support printen. PLA of PETG werkt prima.

2. Monteer de steppermotor

Schuif de motor in de houder en zorg dat het tandwiel goed grijpt.

3. Plaats de ULN2003 driver

Klik het board in de sleuf en verbind de motorstekker.

4. Monteer de ESP32

Verbind IN1–IN4 met de driver en sluit 5V + GND aan.

5. Monteer de micro‑switches

Deze worden gebruikt voor handmatige CW/CCW bediening.

6. Sluit alles aan

ESP32 → ULN2003 → motor → micro‑switches.

7. Upload de ESPHome firmware

Gebruik de YAML hieronder.

8. Automatische kalibratie

Bij elke reboot:

  • motor draait naar open‑positie
  • positie wordt op 0 gezet
  • motor draait naar half‑positie
  • positie wordt opnieuw op 0 gezet

ESPHome YAML (algemene versie)

# ============================================================
# JALOEZIE 
# Inclusief:
# - Absolute kalibratie bij opstart
# - 10s stabilisatietijd
# - Half-target instelbaar in Home Assistant (persistent)
# - Realtime positie & percentage
# - Software eindstops
# - Handmatige bediening (vasthouden = draaien)
# - Versie 20260220.1
# ============================================================
substitutions:
  name: stepper_blind
  blindname: "blind"
  friendly_name: "jaloezie"
  stepperid: ${name}

  calib_open_target: "36000"
  calib_half_target: "-18000"

  min_position: "-14000"
  max_position: "14000"

esphome:
  name: ${name}
  friendly_name: "${friendly_name}"

  on_boot:
    priority: -10
    then:
      - delay: 10s

      - lambda: |-
          if (id(calib_half_target_global) == 0) {
            id(calib_half_target_global) = ${calib_half_target};
          }

      - stepper.set_target:
          id: $stepperid
          target: ${calib_open_target}

      - wait_until:
          condition:
            lambda: 'return id($stepperid).current_position == ${calib_open_target};'

      - stepper.report_position:
          id: $stepperid
          position: 0
      - stepper.set_target:
          id: $stepperid
          target: 0

      - stepper.set_target:
          id: $stepperid
          target: !lambda return id(calib_half_target_global);

      - wait_until:
          condition:
            lambda: 'return id($stepperid).current_position == id(calib_half_target_global);'

      - stepper.report_position:
          id: $stepperid
          position: 0
      - stepper.set_target:
          id: $stepperid
          target: 0

esp32:
  board: esp32dev
  framework:
    type: esp-idf

globals:
  - id: calib_half_target_global
    type: int
    restore_value: true

stepper:
  - platform: uln2003
    id: $stepperid
    pin_a: GPIO22
    pin_b: GPIO21
    pin_c: GPIO17
    pin_d: GPIO16
    max_speed: 500 steps/s
    sleep_when_done: true


cover:
  - platform: template
    name: $blindname
    id: ${blindname}
    has_position: true
    device_class: blind

    open_action:
      - stepper.set_target:
          id: $stepperid
          target: ${max_position}

    close_action:
      - stepper.set_target:
          id: $stepperid
          target: ${min_position}

    stop_action:
      - stepper.set_target:
          id: $stepperid
          target: !lambda return id($stepperid).current_position;

    # SET POSITION action verkeerd om
#    position_action:
#      - lambda: |-
#          float target = (1.0 - pos) * (float(${max_position}) - float(${min_position})) + float(${min_position});
#          id($stepperid).set_target((int)target);

    position_action:
      - lambda: |-
          // percentage van HA → stepper positie (nu omgedraaid)
          float target = pos * (float(${max_position}) - float(${min_position})) + float(${min_position});
          id($stepperid).set_target((int)target);


    # Cover slider in HA → 0–1.0
    lambda: |-
      float percent = id(position_percent).state;
      return percent / 100.0;


sensor:
  - platform: template
    name: "$blindname Position"
    id: position
    accuracy_decimals: 0
    update_interval: 200ms
    lambda: |-
      return (int) id($stepperid).current_position;
    filters:
      - delta: 10

  - platform: template
    name: "$blindname Percentage"
    id: position_percent
    unit_of_measurement: "%"
    accuracy_decimals: 0
    update_interval: 200ms
    lambda: |-
      float pos = id($stepperid).current_position;
      return (int)((1.0 - ((pos - ${min_position}) / (float(${max_position}) - float(${min_position}))))) * 100.0;
    filters:
      - delta: 1.0

number:
  - platform: template
    name: "${blindname} Calib Half Target"
    id: calib_half_target_number
    min_value: -50000
    max_value: 50000
    step: 100
    restore_value: true
    on_value:
      then:
        - lambda: |-
            id(calib_half_target_global) = (int)x;

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

logger:
ota:
api:

Conclusie

Met deze 3D‑geprinte behuizing, goedkope componenten en ESPHome heb je een
professionele jaloezie‑automatisering die volledig lokaal werkt en perfect integreert met Home Assistant.

Is er iets niet duidelijk, laat het gerust weten.

Loading

Slim omgaan met TTS in Home Assistant

Slim omgaan met TTS in Home Assistant

Slim omgaan met TTS in Home Assistant: één script voor al je speakers

Wie met Home Assistant werkt, weet dat apparaten soms van naam veranderen. Zeker wanneer je Music Assistant gebruikt, kunnen media‑players regelmatig wisselen of opnieuw worden aangemaakt. Het gevolg: automations die ineens niet meer werken omdat een entity niet meer bestaat.

Gelukkig is er een elegante oplossing: gebruik één centraal script voor je TTS‑berichten. Daarmee hoef je nooit meer tientallen automations na te lopen wanneer een speaker verandert. Je past het alleen in het script aan en alles werkt weer.

In deze blog leg ik uit hoe je dat doet.


Waarom een script gebruiken voor TTS?

Veel mensen zetten TTS‑acties rechtstreeks in hun automations. Dat werkt prima, totdat:

  • een speaker een andere entity‑naam krijgt
  • je overstapt op een andere TTS‑engine
  • je een nieuwe speaker wilt gebruiken
  • Music Assistant een device opnieuw aanmaakt

Met een centraal script voorkom je dat probleem. Je automations verwijzen alleen naar het script, en het script bepaalt welke speaker en welke TTS‑engine gebruikt worden.


Stap 1: Maak een helper voor je standaard speaker (optioneel maar handig)

Ga naar:

Instellingen → Apparaten & Services → Helpers → Input Text

Maak een nieuwe helper:

  • Naam: default_tts_player
  • Waarde: bijvoorbeeld media_player.home_mini_groep

Deze helper kun je later eenvoudig aanpassen via de UI, zonder YAML.


Stap 2: Maak het TTS‑script

Dit script bevat een invoerveld voor de tekst die uitgesproken moet worden. Daardoor verschijnt er in de automation‑editor automatisch een tekstveld.

script:
  tts_to_default_speaker:
    alias: "TTS naar standaard speaker"
    fields:
      message:
        name: Bericht
        description: De tekst die uitgesproken moet worden
        required: true
        selector:
          text:
    sequence:
      - service: tts.speak
        data:
          cache: true
          media_player_entity_id: "{{ states('input_text.default_tts_player') }}"
          message: "{{ message }}"
        target:
          entity_id: tts.google_nl_com

Je kunt hier later zonder problemen aanpassen:

  • welke speaker gebruikt wordt
  • welke TTS‑engine je wilt gebruiken
  • extra functies zoals volume instellen of muziek pauzeren

Zolang de scriptnaam hetzelfde blijft, blijven al je automations werken.


Stap 3: Gebruik het script in je automations

In je automation kies je simpelweg:

service: script.tts_to_default_speaker

En je krijgt automatisch een veld Bericht, waarin je bijvoorbeeld kunt zetten:

{{ generated_text.data }}

Of gewoon een vaste tekst.


Waarom dit zoveel fijner werkt

  • Je hoeft nooit meer automations te openen als een speaker verandert
  • Je kunt eenvoudig wisselen tussen TTS‑engines
  • Je kunt het script uitbreiden zonder automations te breken
  • Alles blijft overzichtelijk en centraal beheerd

Voor iedereen die regelmatig met TTS werkt in Home Assistant is dit een enorme kwaliteitsverbetering.


Uitbreiden? Dat kan altijd

Later kun je dit script eenvoudig uitbreiden met:

  • meerdere speakers
  • automatische selectie van beschikbare speakers
  • volumeregeling
  • pauzeren en hervatten van muziek
  • fallback‑speakers

Maar de basis blijft hetzelfde: één script, alle automations blij.

Heb je ideeën voor een vervolgblog, zoals TTS naar meerdere speakers of dynamische volumeregeling? Laat het me weten.

Loading

Een Fool‑Proof Pushover Meldingssysteem in Home Assistant

Een Fool‑Proof Pushover Meldingssysteem in Home Assistant

Stap‑voor‑stap handleiding + volledig script

In een slim huis draait alles om betrouwbaarheid. Een melding die te laat komt, of helemaal niet, kan het verschil maken tussen op tijd reageren of iets belangrijks missen. Daarom wilde ik mijn meldingssysteem in Home Assistant naar een hoger niveau tillen. Niet alleen een simpel berichtje, maar een volledig geautomatiseerde, foutloze Pushover‑melding — mét prioriteiten, camerabeelden, geluiden en een automatische titel.

Tijdens het bouwen ontdekte ik dat veel standaardoplossingen nét niet doen wat je wilt. File‑selectors werken niet altijd, prioriteit 2 vereist extra parameters, en bijlagen moeten aan strikte voorwaarden voldoen. Daarom besloot ik een eigen script te maken dat:

  • altijd werkt
  • geen fouten toestaat
  • automatisch slimme keuzes maakt
  • en flexibel genoeg is voor elke situatie

In deze handleiding laat ik stap voor stap zien hoe je dit script zelf kunt toevoegen aan Home Assistant. Aan het einde vind je het volledige script dat je direct kunt gebruiken. Hiermee heb je een professioneel, fool‑proof meldingssysteem dat je smart home een stuk slimmer maakt.


Waarom een eigen Pushover‑script?

De standaard Pushover‑integratie is prima, maar beperkt. Ik wilde:

  • een melding sturen met geluid naar keuze
  • prioriteit 2 gebruiken voor urgente meldingen
  • een afbeelding meesturen uit mijn cameramappen
  • een automatische titel wanneer ik er zelf geen invul
  • een systeem dat nooit faalt, ook niet als ik een veld vergeet

Stap 1 — Voorbereiding

1.1 Zorg dat Pushover werkt in Home Assistant

Je moet al een notify‑service hebben, zoals:

notify.entity_id

Zie Pushover - Home Assistant

Pas dit in het script aan als jouw service anders heet.

1.2 Maak een map voor je camerabeelden

Gebruik de map:

/media/cam_captures/

Plaats daar je afbeeldingen, bijvoorbeeld:

  • 1_tuin.jpg
  • 1_voordeur.jpg
  • 2_tuin.jpg
  • 2_voordeur.jpg
  • 3_tuin.jpg
  • 3_voordeur.jpg
  • 4_tuin.jpg
  • 4_voordeur.jpg
  • poort.jpg
  • tuin.jpg
  • voordeur.jpg

Stap 2 — Script toevoegen in Home Assistant

  1. Ga naar Instellingen → Automatiseringen & Scènes → Scripts
  2. Klik op “+ Script toevoegen”
  3. Kies “Script bewerken in YAML”
  4. Verwijder alles wat er staat
  5. Plak het volledige script hieronder
  6. Sla het script op

Stap 3 — Het volledige Pushover‑script

Dit script is fool‑proof:

  • Bericht mag niet leeg zijn
  • Prioriteit heeft standaard waarde 0
  • Titel wordt automatisch gegenereerd (dagnaam + datum + tijd)
  • Bijlagen komen uit een dropdown
  • Priority 2 werkt correct met retry/expire
pushover_melding:
  alias: "Pushover melding"
  fields:
    message:
      name: Bericht
      selector:
        text:
      default: ""
    title:
      name: Titel
      selector:
        text:
      default: ""
    sound:
      name: Geluid
      selector:
        select:
          options:
            - pushover
            - bike
            - bugle
            - cashregister
            - classical
            - cosmic
            - falling
            - gamelan
            - incoming
            - intermission
            - magic
            - mechanical
            - pianobar
            - siren
            - spacealarm
            - tugboat
            - alien
            - climb
            - persistent
            - echo
            - updown
            - vibrate
            - none
    priority:
      name: Prioriteit
      selector:
        select:
          options:
            - "-2"
            - "-1"
            - "0"
            - "1"
            - "2"
      default: "0"
    url:
      name: URL
      selector:
        text:
    url_title:
      name: URL titel
      selector:
        text:
    attachment_file:
      name: Bijlage kiezen (uit /media/cam_captures/)
      selector:
        select:
          options:
            - 1_tuin.jpg
            - 1_voordeur.jpg
            - 2_tuin.jpg
            - 2_voordeur.jpg
            - 3_tuin.jpg
            - 3_voordeur.jpg
            - 4_tuin.jpg
            - 4_voordeur.jpg
            - poort.jpg
            - tuin.jpg
            - voordeur.jpg

  sequence:
    - condition: template
      value_template: "{{ message is string and message | trim != '' }}"

    - choose:
        - conditions: >
            {{ priority | int == 2 and attachment_file is string and attachment_file != '' }}
          sequence:
            - service: notify.entity_id
              data:
                message: "{{ message }}"
                title: "{{ title | default(
                  (now().strftime('%A')
                    | replace('Monday','maandag')
                    | replace('Tuesday','dinsdag')
                    | replace('Wednesday','woensdag')
                    | replace('Thursday','donderdag')
                    | replace('Friday','vrijdag')
                    | replace('Saturday','zaterdag')
                    | replace('Sunday','zondag')
                  ) ~ ' ' ~ now().strftime('%d-%m-%Y %H:%M')
                ) }}"
                data:
                  sound: "{{ sound }}"
                  priority: 2
                  retry: 30
                  expire: 300
                  url: "{{ url }}"
                  url_title: "{{ url_title }}"
                  attachment: "/media/cam_captures/{{ attachment_file }}"

        - conditions: "{{ priority | int == 2 }}"
          sequence:
            - service: notify.home_assistant_peter
              data:
                message: "{{ message }}"
                title: "{{ title | default(
                  (now().strftime('%A')
                    | replace('Monday','maandag')
                    | replace('Tuesday','dinsdag')
                    | replace('Wednesday','woensdag')
                    | replace('Thursday','donderdag')
                    | replace('Friday','vrijdag')
                    | replace('Saturday','zaterdag')
                    | replace('Sunday','zondag')
                  ) ~ ' ' ~ now().strftime('%d-%m-%Y %H:%M')
                ) }}"
                data:
                  sound: "{{ sound }}"
                  priority: 2
                  retry: 30
                  expire: 300
                  url: "{{ url }}"
                  url_title: "{{ url_title }}"

        - conditions: >
            {{ priority | int != 2 and attachment_file is string and attachment_file != '' }}
          sequence:
            - service: notify.home_assistant_peter
              data:
                message: "{{ message }}"
                title: "{{ title | default(
                  (now().strftime('%A')
                    | replace('Monday','maandag')
                    | replace('Tuesday','dinsdag')
                    | replace('Wednesday','woensdag')
                    | replace('Thursday','donderdag')
                    | replace('Friday','vrijdag')
                    | replace('Saturday','zaterdag')
                    | replace('Sunday','zondag')
                  ) ~ ' ' ~ now().strftime('%d-%m-%Y %H:%M')
                ) }}"
                data:
                  sound: "{{ sound }}"
                  priority: "{{ priority | int }}"
                  url: "{{ url }}"
                  url_title: "{{ url_title }}"
                  attachment: "/media/cam_captures/{{ attachment_file }}"

      default:
        - service: notify.home_assistant_peter
          data:
            message: "{{ message }}"
            title: "{{ title | default(
              (now().strftime('%A')
                | replace('Monday','maandag')
                | replace('Tuesday','dinsdag')
                | replace('Wednesday','woensdag')
                | replace('Thursday','donderdag')
                | replace('Friday','vrijdag')
                | replace('Saturday','zaterdag')
                | replace('Sunday','zondag')
              ) ~ ' ' ~ now().strftime('%d-%m-%Y %H:%M')
            ) }}"
            data:
              sound: "{{ sound }}"
              priority: "{{ priority | int }}"
              url: "{{ url }}"
              url_title: "{{ url_title }}"

Stap 4 — Script testen

  1. Open het script in Home Assistant
  2. Vul een bericht in (verplicht)
  3. Laat de titel leeg om de automatische titel te testen, dus vinkje weg halen
  4. Kies een geluid
  5. Kies een prioriteit
  6. Selecteer eventueel een bijlage
  7. Klik op Uitvoeren

Je ontvangt nu een nette Pushover‑melding met:

  • jouw bericht
  • automatische titel (dagnaam + datum + tijd)
  • gekozen geluid
  • juiste prioriteit
  • optioneel een camerabeeld

Conclusie

Met dit script heb je een professioneel, foutloos en flexibel meldingssysteem dat perfect integreert met Home Assistant. Het is ontworpen om altijd te werken, ongeacht welke velden je wel of niet invult. Ideaal voor alarmmeldingen, camerabeelden, deurbelnotificaties en alle andere situaties waarin je snel en betrouwbaar geïnformeerd wilt worden.

In een automation kun je nu bij Add action, script.pushover_melding kiezen en invullen.

Loading

AB8SS integreren met Home Assistant via ESPHome: een complete gids

AB8SS integreren met Home Assistant via ESPHome: een complete gids

Veel gebruikers van de AB8SS audio‑switch lopen vroeg of laat tegen hetzelfde probleem aan: het apparaat werkt betrouwbaar, maar de seriële aansturing is… eigenzinnig. Het protocol verwacht exacte byte‑reeksen, echo’s, en reageert alleen correct als je de juiste state‑machine gebruikt. Standaard integraties werken daarom vaak niet of slechts half.

In deze blog laat ik zien hoe je de AB8SS volledig en stabiel kunt integreren met Home Assistant via ESPHome, inclusief een robuuste YAML‑configuratie die alle protocol‑eigenaardigheden netjes afhandelt.

Deze oplossing is ontwikkeld, getest en verfijnd met echte hardware — en werkt 100% betrouwbaar.

Waarom de AB8SS lastig is om aan te sturen

De AB8SS gebruikt een seriële interface met een aantal bijzonderheden:

  • Elke opdracht moet exact worden opgebouwd uit 4 bytes
  • Het apparaat stuurt een echo terug dat byte‑voor‑byte moet worden gevalideerd
  • Pas daarna volgt een status‑antwoord
  • Het apparaat accepteert geen nieuwe opdrachten zolang de vorige niet volledig is afgerond
  • Foutafhandeling is noodzakelijk om vastlopers te voorkomen

Veel implementaties falen omdat ze deze stappen niet strikt volgen.

Daarom hebben we een state‑machine gebouwd die:

  • elke byte valideert
  • timeouts afhandelt
  • commando’s in een wachtrij zet
  • de AB8SS nooit overspoelt
  • altijd in sync blijft met het apparaat

De oplossing: een robuuste ESPHome‑configuratie

De YAML‑configuratie die we hebben ontwikkeld bevat:

  • Een UART‑configuratie met correcte baudrate en framing
  • Een state‑machine die echo’s en antwoorden valideert
  • Een command‑queue zodat Home Assistant meerdere opdrachten kan sturen
  • Sensors die de actuele status van alle zones en bronnen teruggeven
  • Switches en selects om zones en inputs te bedienen
  • Volledige foutafhandeling en automatische recovery

Deze configuratie maakt de AB8SS net zo betrouwbaar als een native Home Assistant‑integratie.

Hoe het werkt (conceptueel)

  1. Home Assistant stuurt een opdracht (bijv. “Zone 3 → Input 2”).
  2. ESPHome zet het commando in een wachtrij.
  3. De state‑machine stuurt de 4 bytes naar de AB8SS.
  4. De AB8SS stuurt een echo terug → ESPHome valideert elke byte.
  5. Daarna stuurt de AB8SS een statusbericht.
  6. De state‑machine verwerkt het antwoord en werkt de entiteiten bij.
  7. Pas daarna wordt het volgende commando verstuurd.

Dit voorkomt dat de AB8SS “out of sync” raakt — een veelvoorkomend probleem.

De YAML‑configuratie

Klik deze link om de yaml file te downloaden

Hoe de state‑machine werkt

De kern van de oplossing is een compacte maar krachtige state‑machine die:

1. Een commando uit de queue haalt

Bijvoorbeeld: 050001FA* → Zone 1 aan

2. Een ATTENTION‑byte (!) stuurt

De AB8SS antwoordt met ! als hij klaar is.

3. Elke byte van het commando stuurt

En wacht op de echo.

4. Echo valideert

Als de echo niet klopt → retry Na 5 mislukte pogingen → abort

5. Statusframe verwerkt

Bijvoorbeeld: 84xxxxxx* → bitmask van alle zones + input

6. Home Assistant‑entiteiten bijwerkt

Elke zone wordt direct geüpdatet.

7. Automatisch een statuspoll plant

Zodat Home Assistant altijd synchroon blijft.

Home Assistant entiteiten

De YAML maakt automatisch de volgende entiteiten aan:

Zones (switches)

  • Zone 1 t/m Zone 8 → aan/uit

Inputselectie (switch)

  • Input A/B

Debug‑modus

  • Logt alle RX/TX bytes in realtime

Waarom deze implementatie uniek is

Deze oplossing:

  • werkt met echte echo‑validatie
  • heeft retry‑logica
  • voorkomt dat de AB8SS “out of sync” raakt
  • gebruikt een command‑queue zodat Home Assistant meerdere opdrachten tegelijk kan sturen
  • verwerkt statusframes en houdt Home Assistant realtime up‑to‑date
  • bevat timeouts en foutafhandeling
  • is volledig ESPHome‑native (geen custom component nodig)

Dit is de meest complete en betrouwbare AB8SS‑integratie die momenteel beschikbaar is.

Installatie‑stappen

Flash een ESP32 met ESPHome

Sluit de AB8SS aan via UART op de ESP32
Ik gebruik hiervoor een RS232 naar TTL module, zoals bijvoorbeeld deze.
En een D1 Mini Live ESP32.

  • TX → RX
  • RX → TX
  • GND → GND

Upload de YAML

Voeg het apparaat toe aan Home Assistant

Bedien zones en inputs direct vanuit HA

Gebruik debug‑modus om communicatie te monitoren

Conclusie

De AB8SS is een fantastisch apparaat, maar het protocol vraagt om een zorgvuldige implementatie. Met deze ESPHome‑configuratie kun je de AB8SS volledig integreren in Home Assistant, zonder instabiliteit of half werk.

Deze oplossing is:

  • robuust
  • getest
  • onderhoudsvrij
  • volledig lokaal
  • makkelijk uit te breiden

State‑machine diagram voor de AB8SS‑implementatie

               ┌──────────────────────────┐
               │          IDLE            │
               │  - Geen actief commando  │
               │  - Queue check           │
               │  - Poll timer check      │
               └───────────┬──────────────┘
                           │
                           ▼
               ┌──────────────────────────┐
               │     ATTENTION_SEND       │
               │  TX: "!"                 │
               │  Deadline = now + 100ms  │
               └───────────┬──────────────┘
                           │
                           ▼
               ┌──────────────────────────┐
               │     ATTENTION_WAIT       │
               │  Wacht op RX: "!"        │
               │  Timeout → ABORT         │
               └───────────┬──────────────┘
                           │
                           ▼
               ┌──────────────────────────┐
               │        CMD_SEND          │
               │  TX: current_cmd[pos]    │
               │  Deadline = 5–10ms       │
               └───────────┬──────────────┘
                           │
                           ▼
               ┌──────────────────────────┐
               │        CMD_WAIT          │
               │  Wacht op echo           │
               │  Echo OK → pos++         │
               │  Echo NOK → "-" + ABORT  │
               └───────────┬──────────────┘
                           │
                           ▼
               ┌──────────────────────────┐
               │          DONE            │
               │  - Command klaar         │
               │  - Poll in queue         │
               │  - next_allowed = +50ms  │
               └───────────┬──────────────┘
                           │
                           ▼
               ┌──────────────────────────┐
               │          ABORT           │
               │  - Retry tot 5x          │
               │  - Daarna drop           │
               │  - next_allowed = +300ms │
               └──────────────────────────┘

Behuizing

De ESP32 en uart zijn eventueel in een 3D geprinte behuizing te plaatsen

Loading

Home Assistant + Logitech Harmony: De Ultieme Integratie (Volledige Handleiding)

Home Assistant + Logitech Harmony: De Ultieme Integratie (Volledige Handleiding)

 

De Logitech Harmony Hub blijft een van de krachtigste universele afstandsbedieningen, ook al wordt hij niet meer actief ontwikkeld. In deze handleiding laat ik zien hoe je de Harmony Hub volledig dynamisch integreert in Home Assistant.

We bouwen:

  • dropdowns per apparaat met alle commando’s
  • automatische activiteitenlijst
  • directe uitvoering van commando’s bij selectie
  • één enkele automation die alle apparaten ondersteunt
  • volledig dynamisch ingelezen uit de Harmony‑config

Deze methode is stabiel, onderhoudsvrij en werkt zonder custom componenten.

 

1. Harmony JSON exporteren

Ga in Home Assistant naar:

Instellingen → Integraties → Harmony Hub → Download config

Sla het bestand op in:

Code
 
config/harmony_5872655.conf
 

2. Helpers aanmaken per apparaat

Maak in Home Assistant per Harmony‑device een input_select helper aan. Laat de opties leeg — die vullen we automatisch.

Voorbeeld helpers:

Code
 
input_select.harmony_commands_av_serre_2
input_select.harmony_commands_av_woonkamer_1
input_select.harmony_commands_blue_ray_speler
input_select.harmony_commands_dvr_formuler
input_select.harmony_commands_dvr_vu
input_select.harmony_commands_eetkamer_3
input_select.harmony_commands_lg_70up7006lb
input_select.harmony_commands_marmitek_av_switch
input_select.harmony_commands_muziekserver_avedio_links
input_select.harmony_commands_sat_dm8000
 

3. Automation: Helpers automatisch vullen met commando’s

Deze automation leest je Harmony‑config in en vult alle helpers met de juiste commando’s.

yaml
 
alias: Harmony – Fill Per‑Device Helpers
triggers:
- event: start
trigger: homeassistant
- minutes: /10
trigger: time_pattern
actions:
- action: file.read_file
data:
file_name: harmony_5872655.conf
file_encoding: JSON
response_variable: harmony_data
- variables:
harmony: "{{ harmony_data.data }}"
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_av_serre_2
options: |
{{ [''] + harmony.Devices['AV-Serre 2'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_av_woonkamer_1
options: |
{{ [''] + harmony.Devices['AV-Woonkamer 1'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_blue_ray_speler
options: |
{{ [''] + harmony.Devices['Blue-Ray speler'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_dvr_formuler
options: |
{{ [''] + harmony.Devices['DVR Formuler'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_dvr_vu
options: |
{{ [''] + harmony.Devices['DVR Vu+'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_eetkamer_3
options: |
{{ [''] + harmony.Devices['Eetkamer 3'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_lg_70up7006lb
options: |
{{ [''] + harmony.Devices['LG 70UP7006LB'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_marmitek_av_switch
options: |
{{ [''] + harmony.Devices['Marmitek AV Switch'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_muziekserver_avedio_links
options: |
{{ [''] + harmony.Devices['Muziekserver Avedio Links'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_northern_lichtbediening
options: >
{{ [''] + harmony.Devices['Northern International
Lichtbediening'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_sat_dm8000
options: |
{{ [''] + harmony.Devices['SAT DM8000'].commands }}
- action: input_select.set_options
data:
entity_id: input_select.harmony_commands_homeseer
options: |
{{ [''] + harmony.Devices['homeseer'].commands }}
 

4. Eén automation die ALLE commando’s verstuurt

Zodra je een commando kiest in een dropdown, wordt het direct uitgevoerd.

yaml
 
alias: Harmony – Send Command From Any Device Helper
triggers:
- entity_id:
- input_select.harmony_commands_av_serre_2
- input_select.harmony_commands_av_woonkamer_1
- input_select.harmony_commands_blue_ray_speler
- input_select.harmony_commands_dvr_formuler
- input_select.harmony_commands_dvr_vu
- input_select.harmony_commands_eetkamer_3
- input_select.harmony_commands_lg_70up7006lb
- input_select.harmony_commands_marmitek_av_switch
- input_select.harmony_commands_muziekserver_avedio_links
- input_select.harmony_commands_sat_dm8000
trigger: state
actions:
- action: file.read_file
data:
file_name: harmony_5872655.conf
file_encoding: JSON
response_variable: harmony_data
- variables:
harmony: "{{ harmony_data.data }}"
device_id: "{{ harmony.Devices[device_name].id }}"
- target:
entity_id: remote.huiskamer
data:
device: "{{ device_id }}"
command: "{{ command }}"
action: remote.send_command
- action: input_select.select_option
data:
entity_id: "{{ changed_helper }}"
option: ""
variables:
changed_helper: "{{ trigger.entity_id }}"
device_name: |
{% set map = {
'input_select.harmony_commands_av_serre_2': 'AV-Serre 2',
'input_select.harmony_commands_av_woonkamer_1': 'AV-Woonkamer 1',
'input_select.harmony_commands_blue_ray_speler': 'Blue-Ray speler',
'input_select.harmony_commands_dvr_formuler': 'DVR Formuler',
'input_select.harmony_commands_dvr_vu': 'DVR Vu+',
'input_select.harmony_commands_eetkamer_3': 'Eetkamer 3',
'input_select.harmony_commands_lg_70up7006lb': 'LG 70UP7006LB',
'input_select.harmony_commands_marmitek_av_switch': 'Marmitek AV Switch',
'input_select.harmony_commands_muziekserver_avedio_links': 'Muziekserver Avedio Links',
'input_select.harmony_commands_sat_dm8000': 'SAT DM8000'
} %} {{ map[changed_helper] }}
command: "{{ states(changed_helper) }}"
 

5. Activiteiten dropdown automatisch vullen

yaml
 
alias: Harmony – Update Activity Helper
trigger:
  - platform: homeassistant
    event: start
  - platform: time_pattern
    minutes: "/10"

action:
  - action: file.read_file
    data:
      file_name: harmony_5872655.conf
      file_encoding: JSON
    response_variable: harmony_data

  - variables:
      harmony: "{{ harmony_data.data }}"

  - action: input_select.set_options
    data:
      entity_id: input_select.harmony_activity
      options: >
        {{ harmony.Activity.keys() | list + ['PowerOff'] }}
 

6. Activiteit starten bij selectie

yaml
 
alias: Harmony – Start Activity On Selection
trigger:
  - platform: state
    entity_id: input_select.harmony_activity

action:
  - action: file.read_file
    data:
      file_name: harmony_5872655.conf
      file_encoding: JSON
    response_variable: harmony_data

  - variables:
      harmony: "{{ harmony_data.data }}"
      activity_name: "{{ states('input_select.harmony_activity') }}"
      activity_id: >
        {% for id, name in harmony.Activities.items() %}
          {% if name == activity_name %}
            {{ id }}
          {% endif %}
        {% endfor %}

  - service: remote.turn_on
    target:
      entity_id: remote.huiskamer
    data:
      activity: "{{ activity_id }}"
 

7. Activiteit stoppen bij PowerOff

yaml
 
alias: Harmony – Stop Activity On Selection
trigger:
  - platform: state
    entity_id: input_select.harmony_activity
    to: "PowerOff"

action:
  - service: remote.turn_off
    target:
      entity_id: remote.huiskamer
 

Resultaat

Met deze setup heb je:

  • volledig dynamische Harmony‑integratie
  • dropdowns per apparaat met alle commando’s
  • directe uitvoering bij selectie
  • één automation voor alle devices
  • automatische activiteitenlijst
  • start/stop activiteiten via dropdown

Dit is de meest complete en onderhoudsvrije Harmony‑integratie voor Home Assistant.

Loading

Let’s Encrypt

Let’s Encrypt

Via HACS Home Assistant Community Store) vind je de Integration om Letsencrypt te installeren.

Ik wil alleen Let’s Encrypt gebruiken, omdat ik al een domein naam voor Home Assistant heb.

Na het installeren liep ik tegen het probleem aan, dat bij het uitvoeren er geen verbinding gemaakt kon worden met de map waar de domein controle plaats vindt.

Ik had de volgende poorten in mijn router doorverwezen naar het IP adres van HA:
poort 8123 doorverwezen naar poort 8123
poort 80 doorverwezen naar poort 8123 en
poort 443 doorverwezen naar poort 8123.

De addon blijkt na gestart te zijn, zelf een webserver op te zetten op poort 80 om de verificatie te kunnen doen.
Maar die ging via mijn router dus naar poort 8123.
Daarom ging het mis.
Na poort 80 door te verwijzen naar poort 80 ging het goed en waren files voor het certificaat geïnstalleerd.
Er werd keurig aangegeven waar ze staan:

Certificate is saved at: /data/letsencrypt/live/ohhpcgsm.nl/fullchain.pem
Key is saved at: /data/letsencrypt/live/ohhpcgsm.nl/privkey.pem

In Configuration.yaml deze regels onder http: ingevuld, maar na een herstart van HA werkte het niet.
Het blijkt, dat de addon de bestanden naar ergens anders kopieert en moet dit in configuration.yaml staan:


http:
   ssl_certificate: /ssl/fullchain.pem
   ssl_key: /ssl/privkey.pem


HA weer herstart en nu werkt https:\
Let wel op, dat je dit nu voortaan overal moet gaan gebruiken.
Dus ook in de app op je telefoon.
Of op een pc, waar je eerst het ip adres gebruikte!

Het certificaat is maar 60 dagen geldig.
Om deze te vernieuwen moet je de add-on weer starten en gaat deze kijken of er vernieuwd moet worden.
Is dat niet nodig, dan staat er dit in de log:
Certificate not yet due for renewal; no action taken.
Je kunt hiervoor een automation aanmaken in bijvoorbeeld
home-assistant/automation/system.yaml

  • alias: “System – Let’s Encrypt Renewal”
    trigger:
    platform: time
    at: ’01:25:00′
    action:
    • service: hassio.addon_restart
      data:
      addon: core_letsencrypt


Dit script staat op github.

Verder kun je ook nog een entity maken die aangeeft wanneer je certificaat verloopt:


https://www.home-assistant.io/integrations/cert_expiry

Er is ook een blueprint om dit automatisch te doen:

https://community.home-assistant.io/t/blueprint-for-automatic-renewal-of-a-lets-encrypt-certificate/300533



Loading

Zonne-energie

Zonne-energie

3 zonnepanelen aangeschaft.
Deze worden met een omvormer (Soladin 600) rechtstreeks op het stopkontakt aangesloten.
De maximale opbrengst is ongeveer 500 Watt.
De Soladin 600 is aangesloten op de HomeServer om de opgewekte energie uit te kunnen lezen.

Dat kan met een programma, genaamd Masterlog.
Dit programma schrijft de metingen in een SQL database.
Geprobeerd om deze door een .VB schript uit te lezen, maar dat gaat niet lukken. Er is een password nodig om de database te kunnen openen en die weet ik niet.

Volgende poging is om rechtstreeks de serieele verbinding uit te lezen.
Dit werkt momenteel gedeeltelijk, maar nog niet zoals ik wil.
Dus druk bezig mijn kennis van uitlezen van seriele poorten in .VB te verbeteren!

Loading