Tag: ESPHome

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: "irrigatie: Lowest price vijver bijvullen"
description: ""
triggers:
  - trigger: time
    at: sensor.vijver_bijvullen_start_time
  - value_template: "{{ states('sensor.zonneplan_cheapest_time') == now().strftime('%H:%M') }}"
    trigger: template
    enabled: false
conditions:
  - condition: and
    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:
  - choose:
      - conditions:
          - condition: state
            entity_id: switch.irrigation_serre_switch
            state:
              - "on"
        sequence:
          - wait_for_trigger:
              - entity_id:
                  - switch.irrigation_serre_switch
                to:
                  - "off"
                trigger: state
            continue_on_timeout: false
          - delay:
              hours: 0
              minutes: 0
              seconds: 30
              milliseconds: 0
  - action: number.set_value
    target:
      entity_id: number.irrigation_vijver
    data:
      value: "5"
  - action: switch.turn_on
    target:
      entity_id: switch.irrigation_vijver_switch
    data: {}
mode: single


📄 Volledige ESPHome‑configuratie

Hieronder staat de volledige YAML‑configuratie van het irrigatiesysteem.


# 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.
# 20260331_V06Peterpc
  # sprinkler tijden bij reboot terug zetten naar bewaarde waarden in flash.
  # laatst gestart waarde bij install op huidige tijd zetten in plaats van unknown
# 20260401_V01Peterpc
  # de switch in HA gaat bij inschakelen via HA aan, even uit en weer aan, omdat het de status van de valve switch in de esp bij houdt.
  # dat is niet handig als je die in een automation als trigger wil gebruiken.
  # daarom is er een HA switch toegevoegd, die aan blijft en een status sensor voor de zone.
  # de valve switch is ook nog zichtbaar met zijn aan, uit, aan eigenschap, omdat je die niet op internal kan zetten in ESPHome.  
###############################################################
# KC868-A6 HARDWARE MAP — Irrigation Controller Reference
# 
# I2C BUS (ESSENTIEEL — HARD-WIRED):
#   SDA = GPIO4
#   SCL = GPIO15
#
# I2C DEVICES:
#   PCF8574 OUT  → 0x24  (Relays)
#   PCF8574 IN   → 0x22  (Buttons)
#   SSD1306 OLED → 0x3C  (Display)
#
# PCF8574 OUT (0x24) — RELAYS:
#   Pin 0 → Relay 1 → Zone 1
#   Pin 1 → Relay 2 → Zone 2
#   Pin 2 → Relay 3 → Zone 3
#   Pin 3 → Relay 4 → Zone 4
#   Pin 4 → Relay 5 → Zone 5
#   Pin 5 → Relay 6 → Pump
#   Pin 6 → Relay 7 → Free
#   Pin 7 → Relay 8 → Free
#
# PCF8574 IN (0x22) — BUTTONS:
#   Pin 0 → Button 1 → Start Zone 1
#   Pin 1 → Button 2 → Start Zone 2
#   Pin 2 → Button 3 → Start Zone 3
#   Pin 3 → Button 4 → Start Zone 4
#   Pin 4 → Button 5 → Start Zone 5
#   Pin 5 → Button 6 → Shutdown All
#   Pin 6 → Free
#   Pin 7 → Free
#
# DISPLAY (SSD1306 128x64):
#   I2C Address: 0x3C
#   Shared bus with both PCF8574 chips
#
# POWER:
#   ESP32 → 5V input → onboard regulator → 3.3V logic
#   Relays → 5V
#   PCF8574 → 3.3V
#   Display → 3.3V
#
# NOTE:
#   - I2C pins MUST be SDA=4, SCL=15 for KC868-A6.
#   - Wrong pins = no relays, no buttons, no display.
#   - This map is hardware-accurate for your irrigation setup.
###############################################################


###############################################
# V06-FULL-CLEAN — Irrigation Controller
# Universeel, persistent, substitutions-driven
###############################################



################################################################################
# WAARSCHUWING:                                                                #
#   Bij een firmware-installatie worden alle opgeslagen waarden                #
#   (globals, sprinkler settings, durations) teruggezet naar deze defaults.    #
#   Pas deze waarden hier aan als je andere standaardtijden wilt.              #
################################################################################  
substitutions:
  friendly_name: "Irrigation Controller"

  zone_1_name: Voortuin
  zone_1_last_run_id: voortuin_last_run
  zone_1_valve_id: zone1_duration
  default_zone_1_duration: "30"

  zone_2_name: Achtertuin
  zone_2_last_run_id: achtertuin_last_run
  zone_2_valve_id: zone2_duration
  default_zone_2_duration: "3"

  zone_3_name: Serre
  zone_3_last_run_id: serre_last_run
  zone_3_valve_id: zone3_duration
  default_zone_3_duration: "3"

  zone_4_name: Vijver
  zone_4_last_run_id: vijver_last_run
  zone_4_valve_id: zone4_duration
  default_zone_4_duration: "5"

  zone_5_name: Spare
  zone_5_last_run_id: zone5_last_run
  zone_5_valve_id: zone5_duration
  default_zone_5_duration: "15"

# hieronder niets meer aanpassen!
  software_version: 20260401_V01Peterpc
  sensor_update_frequency: 1s
  log_level: debug
  esphome_name: irrigation
  esphome_platform: ESP32
  esphome_board: esp32dev
  esphome_comment: Five Valve Irrigation Controller
  esphome_project_name: jaya.IrrigationController
  esphome_project_version: ${software_version}
  devicename: irrigation_controller
  upper_devicename: "Irrigation Controller"
  uom: Min

###############################################
# ESPHome Core
# Define Project Details 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:
      # Reset status
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"

      # Convert seconds → minutes
      - sprinkler.set_multiplier:
          id: ${devicename}
          multiplier: 60

      # Restore persistent durations into sprinkler
      - lambda: |-
          id(${devicename}).set_valve_run_duration(0, id(dur_0));
          id(${devicename}).set_valve_run_duration(1, id(dur_1));
          id(${devicename}).set_valve_run_duration(2, id(dur_2));
          id(${devicename}).set_valve_run_duration(3, id(dur_3));
          id(${devicename}).set_valve_run_duration(4, id(dur_4));

    # --- Last-run timestamps initialiseren ---
      - lambda: |-
          auto now = id(homeassistant_time).now();
          if (now.is_valid()) {
            std::string ts = now.strftime("%Y-%m-%d %H:%M:%S");

            if (id(zone1_last_run_global).empty())
              id(zone1_last_run_global) = ts;

            if (id(zone2_last_run_global).empty())
              id(zone2_last_run_global) = ts;

            if (id(zone3_last_run_global).empty())
              id(zone3_last_run_global) = ts;

            if (id(zone4_last_run_global).empty())
              id(zone4_last_run_global) = ts;

            if (id(zone5_last_run_global).empty())
              id(zone5_last_run_global) = ts;
          }
    # --- Last-run sensors publiceren ---
      - 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 Hardware
###############################################

esp32:
  board: ${esphome_board}
  framework:
    type: esp-idf

###############################################
# WiFi
###############################################

wifi:
  ssid: !secret wifi_ssid24
  password: !secret wifi_password

  ap:
    ssid: "${esphome_name} Fallback Hotspot"
    password: !secret esphome_ap_password

logger:
  level: ${log_level}

<<: !include common/ota.yaml

# Enable Web server.
web_server:
  port: 80

###############################################
# Time Sync + Auto Restart
###############################################

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

###############################################
# GLOBALS — Persistent Durations + Last Run to flash
###############################################

globals:
  # Last run text sensors
  - 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\""

  # Persistent run durations (G1)
  - id: dur_0
    type: int
    restore_value: true
    initial_value: ${default_zone_1_duration}

  - id: dur_1
    type: int
    restore_value: true
    initial_value: ${default_zone_2_duration}

  - id: dur_2
    type: int
    restore_value: true
    initial_value: ${default_zone_3_duration}

  - id: dur_3
    type: int
    restore_value: true
    initial_value: ${default_zone_4_duration}

  - id: dur_4
    type: int
    restore_value: true
    initial_value: ${default_zone_5_duration}
###############################################
# TEXT SENSORS — General Info + Last Run
###############################################

text_sensor:
  - platform: version
    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"

  ###############################################
  # Time Remaining (dynamic)
  ###############################################
  - 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 / 86400;
      seconds %= 86400;
      int hours = seconds / 3600;
      seconds %= 3600;
      int minutes = seconds / 60;
      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;

  ###############################################
  # Progress %
  ###############################################
  - platform: template
    id: progress_percent
    name: "${upper_devicename} Progress %"
    update_interval: ${sensor_update_frequency}
    icon: "mdi:progress-clock"
    lambda: |-
      int valve = id(${devicename}).active_valve().value_or(0);
      int total = id(${devicename}).valve_run_duration_adjusted(valve);
      int remaining = id(${devicename}).time_remaining_active_valve().value_or(0);
      if (total == 0) return std::string("0%");
      int pct = round((total - remaining) * 100 / total);
      return std::to_string(pct) + "%";

  ###############################################
  # Valve Status
  ###############################################
  - platform: template
    id: valve_status
    name: "${upper_devicename} Status"
    update_interval: never
    icon: "mdi:information-variant"

  ###############################################
  # Board Type
  ###############################################
  - platform: template
    id: espboard_type
    icon: "mdi:developer-board"
    name: "${esphome_name} ESPBoard"
    lambda: |-
      return esphome::optional(std::string("${esphome_board}"));

  ###############################################
  # Last Run Sensors (dynamic via substitutions)
  ###############################################

  - 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);


###############################################
# SENSORS — Uptime + WiFi Signal
###############################################

sensor:
  - platform: uptime
    name: "${upper_devicename} Uptime"

  - platform: wifi_signal
    name: "${upper_devicename} WiFi Signal"
    update_interval: 60s
###############################################
# NUMBER ENTITIES — Persistent Run Durations
###############################################

number:

  ###############################################
  # Zone 1 — ${zone_1_name}
  ###############################################
  - platform: template
    id: ${zone_1_valve_id}
    name: "${zone_1_name}"
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: ${uom}
    icon: "mdi:timer-outline"
    mode: box
    lambda: |-
      return float(id(dur_0));
    set_action:
      - lambda: |-
          id(dur_0) = int(x);
          id(${devicename}).set_valve_run_duration(0, id(dur_0));

  ###############################################
  # Zone 2 — ${zone_2_name}
  ###############################################
  - 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
    lambda: |-
      return float(id(dur_1));
    set_action:
      - lambda: |-
          id(dur_1) = int(x);
          id(${devicename}).set_valve_run_duration(1, id(dur_1));

  ###############################################
  # Zone 3 — ${zone_3_name}
  ###############################################
  - 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
    lambda: |-
      return float(id(dur_2));
    set_action:
      - lambda: |-
          id(dur_2) = int(x);
          id(${devicename}).set_valve_run_duration(2, id(dur_2));

  ###############################################
  # Zone 4 — ${zone_4_name}
  ###############################################
  - 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
    lambda: |-
      return float(id(dur_3));
    set_action:
      - lambda: |-
          id(dur_3) = int(x);
          id(${devicename}).set_valve_run_duration(3, id(dur_3));

  ###############################################
  # Zone 5 — ${zone_5_name}
  ###############################################
  - 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
    lambda: |-
      return float(id(dur_4));
    set_action:
      - lambda: |-
          id(dur_4) = int(x);
          id(${devicename}).set_valve_run_duration(4, id(dur_4));

  ###############################################
  # Repeat Cycles
  ###############################################
  - 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;'
###############################################
# SPRINKLER CONTROLLER — Persistent + Clean
###############################################

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 — Dynamic via substitutions
    ###############################################
    valves:
      - valve_switch: ${zone_1_name}
        enable_switch: "Enable ${zone_1_name}"
        pump_switch_id: sprinkler_pump_sw
        run_duration: 60s
        valve_switch_id: ${devicename}_1

      - valve_switch: ${zone_2_name}
        enable_switch: "Enable ${zone_2_name}"
        pump_switch_id: sprinkler_pump_sw
        run_duration: 60s
        valve_switch_id: ${devicename}_2

      - valve_switch: ${zone_3_name}
        enable_switch: "Enable ${zone_3_name}"
        pump_switch_id: sprinkler_pump_sw
        run_duration: 60s
        valve_switch_id: ${devicename}_3

      - valve_switch: ${zone_4_name}
        enable_switch: "Enable ${zone_4_name}"
        pump_switch_id: sprinkler_pump_sw
        run_duration: 60s
        valve_switch_id: ${devicename}_4

      - valve_switch: ${zone_5_name}
        enable_switch: "Enable ${zone_5_name}"
        pump_switch_id: sprinkler_pump_sw
        run_duration: 60s
        valve_switch_id: ${devicename}_5

###############################################
# SWITCHES voor valves
###############################################

switch:
  - platform: template
    name: "${zone_1_name} switch"
    id: zone_1_switch
    #internal: true
    optimistic: true
    restore_mode: DISABLED
    turn_on_action:
      - sprinkler.start_single_valve:
          id: ${devicename}
          valve_number: 0
    turn_off_action:
      - sprinkler.shutdown:
          id: ${devicename}

  - platform: template
    name: "${zone_2_name} switch"
    id: zone_2_switch
    #internal: true
    optimistic: true
    restore_mode: DISABLED
    turn_on_action:
      - sprinkler.start_single_valve:
          id: ${devicename}
          valve_number: 1
    turn_off_action:
      - sprinkler.shutdown:
          id: ${devicename}

  - platform: template
    name: "${zone_3_name} switch"
    id: zone_3_switch
    #internal: true
    optimistic: true
    restore_mode: DISABLED
    turn_on_action:
      - sprinkler.start_single_valve:
          id: ${devicename}
          valve_number: 2
    turn_off_action:
      - sprinkler.shutdown:
          id: ${devicename}

  - platform: template
    name: "${zone_4_name} switch"
    id: zone_4_switch
    #internal: true
    optimistic: true
    restore_mode: DISABLED
    turn_on_action:
      - sprinkler.start_single_valve:
          id: ${devicename}
          valve_number: 3
    turn_off_action:
      - sprinkler.shutdown:
          id: ${devicename}

  - platform: template
    name: "${zone_5_name} switch"
    id: zone_5_switch
    #internal: true
    optimistic: true
    restore_mode: DISABLED
    turn_on_action:
      - sprinkler.start_single_valve:
          id: ${devicename}
          valve_number: 4
    turn_off_action:
      - sprinkler.shutdown:
          id: ${devicename}

###############################################
# SWITCHES — Restart Device
###############################################

  - platform: restart
    name: "Restart ${devicename}"
    id: restartit



###############################################
# RELAY OUTPUTS — Hidden Valve Controls
# All dynamic via substitutions
###############################################

  ###############################################
  # Zone 1 Relay
  ###############################################
  - platform: gpio
    id: ${devicename}_1
    internal: true
    restore_mode: RESTORE_DEFAULT_OFF
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 0
      mode: OUTPUT
      inverted: true
    on_turn_on:
      - lambda: |-
          auto now = id(homeassistant_time).now();
          if (now.is_valid()) {
            id(zone1_last_run_global) = 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"


  ###############################################
  # Zone 2 Relay
  ###############################################
  - platform: gpio
    id: ${devicename}_2
    internal: true
    restore_mode: RESTORE_DEFAULT_OFF
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 1
      mode: OUTPUT
      inverted: true
    on_turn_on:
      - lambda: |-
          auto now = id(homeassistant_time).now();
          if (now.is_valid()) {
            id(zone2_last_run_global) = 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"

  ###############################################
  # Zone 3 Relay
  ###############################################


  - platform: gpio
    id: ${devicename}_3
    internal: true
    restore_mode: RESTORE_DEFAULT_OFF
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 2
      mode: OUTPUT
      inverted: true
    on_turn_on:
      - lambda: |-
          auto now = id(homeassistant_time).now();
          if (now.is_valid()) {
            id(zone3_last_run_global) = 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"


  ###############################################
  # Zone 4 Relay
  ###############################################
  - platform: gpio
    id: ${devicename}_4
    internal: true
    restore_mode: RESTORE_DEFAULT_OFF
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 3
      mode: OUTPUT
      inverted: true
    on_turn_on:
      - lambda: |-
          auto now = id(homeassistant_time).now();
          if (now.is_valid()) {
            id(zone4_last_run_global) = 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"


  ###############################################
  # Zone 5 Relay
  ###############################################
  - platform: gpio
    id: ${devicename}_5
    internal: true
    restore_mode: RESTORE_DEFAULT_OFF
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 4
      mode: OUTPUT
      inverted: true
    on_turn_on:
      - lambda: |-
          auto now = id(homeassistant_time).now();
          if (now.is_valid()) {
            id(zone5_last_run_global) = 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"

  ###############################################
  # Pump Relay
  ###############################################
  - platform: gpio
    id: sprinkler_pump_sw
    name: "Sprinkler Pump"
    internal: true
    restore_mode: RESTORE_DEFAULT_OFF
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 5
      mode: OUTPUT
      inverted: true
    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"

###############################################
# BUTTONS — Pause
###############################################

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}

###############################################
# PCF8574 I/O Expanders
###############################################

pcf8574:
  - id: pcf8574_hub_out_1
    address: 0x24

  - id: pcf8574_hub_in_1
    address: 0x22


###############################################
# BINARY SENSORS — Local Manual Inputs
###############################################

binary_sensor:

  - platform: homeassistant
    id: prevent_deep_sleep
    name: "${upper_devicename} Prevent Deep Sleep"
    entity_id: input_boolean.prevent_deep_sleep

  ###############################################
  # Local Button 1 → Start Zone 1
  ###############################################
  - platform: gpio
    name: "a6-input1"
    internal: true
    pin:
      pcf8574: pcf8574_hub_in_1
      number: 0
      mode: INPUT
      inverted: true
    on_press:
      - sprinkler.start_single_valve:
          id: ${devicename}
          valve_number: 0

  ###############################################
  # Local Button 2 → Start Zone 2
  ###############################################
  - platform: gpio
    name: "a6-input2"
    internal: true
    pin:
      pcf8574: pcf8574_hub_in_1
      number: 1
      mode: INPUT
      inverted: true
    on_press:
      - sprinkler.start_single_valve:
          id: ${devicename}
          valve_number: 1

  ###############################################
  # Local Button 3 → Start Zone 3
  ###############################################
  - platform: gpio
    name: "a6-input3"
    internal: true
    pin:
      pcf8574: pcf8574_hub_in_1
      number: 2
      mode: INPUT
      inverted: true
    on_press:
      - sprinkler.start_single_valve:
          id: ${devicename}
          valve_number: 2

  ###############################################
  # Local Button 4 → Start Zone 4
  ###############################################
  - platform: gpio
    name: "a6-input4"
    internal: true
    pin:
      pcf8574: pcf8574_hub_in_1
      number: 3
      mode: INPUT
      inverted: true
    on_press:
      - sprinkler.start_single_valve:
          id: ${devicename}
          valve_number: 3

  ###############################################
  # Local Button 5 → Start Zone 5
  ###############################################
  - platform: gpio
    name: "a6-input5"
    internal: true
    pin:
      pcf8574: pcf8574_hub_in_1
      number: 4
      mode: INPUT
      inverted: true
    on_press:
      - sprinkler.start_single_valve:
          id: ${devicename}
          valve_number: 4

  ###############################################
  # Local Button 6 → Shutdown All
  ###############################################
  - platform: gpio
    name: "a6-input6"
    internal: true
    pin:
      pcf8574: pcf8574_hub_in_1
      number: 5
      mode: INPUT
      inverted: true
    on_press:
      - sprinkler.shutdown: ${devicename}

###############################################
# status‑sensor voor de zone
###############################################

  - platform: template
    id: zone_1_active
    name: "${zone_1_name} Actief"
    device_class: running
    lambda: |-
      return id(${devicename}_1).state;
    #update_interval: 1s
  - platform: template
    id: zone_2_active
    name: "${zone_2_name} Actief"
    device_class: running
    lambda: |-
      return id(${devicename}_2).state;
    #update_interval: 1s
  - platform: template
    id: zone_3_active
    name: "${zone_3_name} Actief"
    device_class: running
    lambda: |-
      return id(${devicename}_3).state;
    #update_interval: 1s
  - platform: template
    id: zone_4_active
    name: "${zone_4_name} Actief"
    device_class: running
    lambda: |-
      return id(${devicename}_4).state;
    #update_interval: 1s
  - platform: template
    id: zone_5_active
    name: "${zone_5_name} Actief"
    device_class: running
    lambda: |-
      return id(${devicename}_5).state;
    #update_interval: 1s

###############################################
# I2C BUS
###############################################

i2c:
  sda: 4
  scl: 15
  scan: true
  id: bus_a


###############################################
# FONTS
###############################################

font:
  - file: "common/fonts/Roboto-Regular.ttf"
    id: font_small
    size: 12

  - file: "common/fonts/Roboto-Regular.ttf"
    id: font_medium
    size: 16

  - file: "common/fonts/Roboto-Regular.ttf"
    id: font_large
    size: 20


###############################################
# DISPLAY — SSD1306 128x64
###############################################

display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    id: irrigation_display
    address: 0x3C
    rotation: 0
    lambda: |-
      // Tijd bovenaan
      auto now = id(homeassistant_time).now();
      if (now.is_valid()) {
        it.strftime(0, 0, id(font_medium), "%H:%M:%S", now);
      } else {
        it.printf(0, 0, id(font_medium), "--:--:--");
      }

      // Active valve
      auto active = id(${devicename}).active_valve().value_or(-1);
      if (active >= 0) {
        std::string zone_name;
        if (active == 0) zone_name = "${zone_1_name}";
        if (active == 1) zone_name = "${zone_2_name}";
        if (active == 2) zone_name = "${zone_3_name}";
        if (active == 3) zone_name = "${zone_4_name}";
        if (active == 4) zone_name = "${zone_5_name}";

        it.printf(0, 18, id(font_small), "Active: %s", zone_name.c_str());
      } else {
        it.printf(0, 18, id(font_small), "Active: None");
      }

      // Time remaining
      it.printf(0, 32, id(font_small), "Remaining: %s",
        id(time_remaining).state.c_str());

      // Progress %
      it.printf(0, 46, id(font_small), "Progress: %s",
        id(progress_percent).state.c_str());
# -----------------------------end---------------------------------------------------

📄 automation:alle 5 zones automatisch UIT

Hij werkt op basis van:
sensor.irrigation_controller_time_remaining
zodra die 0 seconden bereikt
zet hij alle stateless zone‑switches uit


alias: "Irrigatie : Zet zones uit wanneer sproeien klaar is"
description: ""
triggers:
  - trigger: state
    entity_id:
      - binary_sensor.irrigation_voortuin_actief
    to:
      - "off"
    for:
      hours: 0
      minutes: 0
      seconds: 30
    from:
      - "on"
    id: voortuin
  - trigger: state
    entity_id:
      - binary_sensor.irrigation_achtertuin_actief
    to:
      - "off"
    for:
      hours: 0
      minutes: 0
      seconds: 30
    from:
      - "on"
    id: achtertuin
  - trigger: state
    entity_id:
      - binary_sensor.irrigation_serre_actief
    to:
      - "off"
    for:
      hours: 0
      minutes: 0
      seconds: 30
    from:
      - "on"
    id: serre
  - trigger: state
    entity_id:
      - binary_sensor.irrigation_vijver_actief
    to:
      - "off"
    for:
      hours: 0
      minutes: 0
      seconds: 30
    from:
      - "on"
    id: vijver
  - trigger: state
    entity_id:
      - binary_sensor.irrigation_spare_actief
    to:
      - "off"
    for:
      hours: 0
      minutes: 0
      seconds: 30
    from:
      - "on"
    id: spare
conditions: []
actions:
  - if:
      - condition: trigger
        id:
          - voortuin
    then:
      - target:
          entity_id:
            - switch.irrigation_voortuin_switch
        action: switch.turn_off
        data: {}
    alias: voortuin
  - if:
      - condition: trigger
        id:
          - achtertuin
    then:
      - target:
          entity_id:
            - switch.irrigation_achtertuin_switch
        action: switch.turn_off
        data: {}
    alias: achtertuin
  - alias: serre
    if:
      - condition: trigger
        id:
          - serre
    then:
      - target:
          entity_id:
            - switch.irrigation_serre_switch
        action: switch.turn_off
        data: {}
  - if:
      - condition: trigger
        id:
          - vijver
    then:
      - target:
          entity_id:
            - switch.irrigation_vijver_switch
        action: switch.turn_off
        data: {}
    alias: vijver
  - if:
      - condition: trigger
        id:
          - spare
    then:
      - target:
          entity_id: switch.irrigation_spare_switch
        action: switch.turn_off
        data: {}
    alias: spare
mode: restart



🎯 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

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

Translate »