Docs · Home Assistant
Home Assistant Integration
TiltForge works with Home Assistant two ways. Pick based on whether you want to keep the stock firmware or run ESPHome.
Option 1 — Stock firmware + REST (no reflash)
Your TiltForge unit already exposes a local REST API. Home Assistant can drive it directly — nothing to flash.
- +File:
tiltforge-home-assistant.yaml - +How: replace
tiltforge-5110with your device's 4-character name, paste intoconfiguration.yaml, restart HA. - +Behavior: polling-based. Manual cord-pulls appear in HA within a few seconds.
- +Best for: anyone who wants the stock experience — works with the standard and Matter-capable units, alongside Matter on the Matter-capable unit.
tiltforge-home-assistant.yamlyaml
# =============================================================================# TiltForge — Home Assistant integration (stock firmware, no reflash)# =============================================================================# Turns a shipped TiltForge unit into a native Home Assistant cover entity# using its built-in REST endpoints. Nothing changes on the device.## This is POLLING-based: HA reads /state every 5 s, so a manual cord-pull shows# up in HA within a few seconds (not instant). For instant/real-time HA, use# the ESPHome path instead (tiltforge-esphome.yaml).## SETUP:# 1. Replace every "tiltforge-5110" below with YOUR device's name — the# 4-character code shown during setup (also the setup Wi-Fi network name).# 2. Paste into configuration.yaml (or a package/included file).# 3. Restart Home Assistant.## NOTE on .local: HA resolves tiltforge-XXXX.local on most systems. If your HA# host can't do mDNS, replace the hostname with the device's IP address (find# it in your router, or via the device's `i` serial command).## NOTE on "position": TiltForge is physically a slat-TILT device. Variant A# (below, active) maps that tilt onto HA's position slider — the cleanest# single-control UX. Variant B represents it as slat-tilt instead. Use ONE.# ============================================================================= # --- Shared: REST commands (used by both variants) ---rest_command: tiltforge_5110_open: url: "http://tiltforge-5110.local/open" method: GET tiltforge_5110_close: url: "http://tiltforge-5110.local/close" method: GET tiltforge_5110_stop: url: "http://tiltforge-5110.local/stop" method: GET tiltforge_5110_set: url: "http://tiltforge-5110.local/tilt?pct={{ pct }}" method: POST # --- Shared: poll /state for current position (0=closed .. 100=open) ---sensor: - platform: rest name: tiltforge_5110_pct resource: "http://tiltforge-5110.local/state" value_template: "{{ value_json.pct }}" scan_interval: 5 # =============================================================================# VARIANT A — position slider (RECOMMENDED, active)# Clean single slider + open/close/stop. Maps tilt onto HA position.# =============================================================================cover: - platform: template covers: tiltforge_5110: friendly_name: "Living Room Blind" device_class: blind position_template: "{{ states('sensor.tiltforge_5110_pct') | int(0) }}" open_cover: action: rest_command.tiltforge_5110_open close_cover: action: rest_command.tiltforge_5110_close stop_cover: action: rest_command.tiltforge_5110_stop set_cover_position: action: rest_command.tiltforge_5110_set data: pct: "{{ position }}" # =============================================================================# VARIANT B — slat-tilt control (ALTERNATIVE)# Represents the device as tilt: open/close/stop buttons + a tilt slider.# To use this instead of Variant A, delete the "cover:" block above and# uncomment the one below.# =============================================================================# cover:# - platform: template# covers:# tiltforge_5110:# friendly_name: "Living Room Blind"# device_class: blind# tilt_template: "{{ states('sensor.tiltforge_5110_pct') | int(0) }}"# open_cover:# action: rest_command.tiltforge_5110_open# close_cover:# action: rest_command.tiltforge_5110_close# stop_cover:# action: rest_command.tiltforge_5110_stop# set_cover_tilt_position:# action: rest_command.tiltforge_5110_set# data:# pct: "{{ tilt }}" # -----------------------------------------------------------------------------# Older Home Assistant (pre-2024.8): replace every "action:" above with# "service:" — the two are equivalent; newer HA prefers "action:".# -----------------------------------------------------------------------------Option 2 — ESPHome (reflash, real-time)
Flash the ESPHome config to the XIAO ESP32-C6, replacing the TiltForge firmware. The device then appears in HA natively with instant, push-based updates and no HA-side YAML.
- +File:
tiltforge-esphome.yaml - +How: set your Wi-Fi (or use the captive-portal hotspot) and the two AS5600 calibration constants, then flash with the ESPHome dashboard / CLI.
- +Behavior: real-time. Uses ESPHome's native AS5600 component and the ESPHome API — no polling.
- +Best for: makers already living in ESPHome who want the tightest HA integration. Trade-off: you leave TiltForge firmware behind (no Matter, no TiltForge branded web UI). Reflash the TiltForge binary to return.
tiltforge-esphome.yamlyaml
# =============================================================================# TiltForge — ESPHome reference configuration# =============================================================================# For makers who want to run their TiltForge on ESPHome instead of the stock# TiltForge firmware. Flashing this REPLACES the TiltForge firmware on the# XIAO ESP32-C6. The device then appears natively in Home Assistant as a cover# (real-time, no polling) via the ESPHome API — no YAML needed on the HA side.## Hardware assumed (TiltForge Rev C):# XIAO ESP32-C6 | AS5600 encoder on I2C | L9110 H-bridge | N20 motor## Pin mapping (XIAO C6 silk label -> ESP32-C6 GPIO):# D5 -> GPIO23 Motor IA D8 -> GPIO19 I2C SDA# D6 -> GPIO16 Motor IB D9 -> GPIO20 I2C SCL## BEFORE FLASHING — set two things:# 1. Your Wi-Fi in secrets.yaml (wifi_ssid / wifi_password), OR use the# "TiltForge Setup" captive-portal hotspot on first boot.# 2. The two calibration constants below (raw_closed / raw_open) to the# AS5600 raw values at your blind's closed and open endpoints. Easiest# way: flash as-is, watch the "Raw angle" sensor in HA while you pull the# cord to each end, then fill in the numbers and re-flash.## NOTE: This is a validated-structure reference, not bench-tested on every# blind. If Open/Close run the wrong way, swap motor_ia <-> motor_ib (either in# wiring or by swapping the two output ids below). Tune the stop tolerance if# it overshoots/undershoots.# ============================================================================= substitutions: name: "tiltforge" friendly_name: "TiltForge Blind" # --- CALIBRATION: AS5600 raw counts (0-4095) at each mechanical endpoint --- raw_closed: "0" # raw angle at FULLY CLOSED raw_open: "2048" # raw angle at FULLY OPEN # --- Stop tolerance (fraction of full travel, 0.02 = 2%) --- pos_tolerance: "0.02" esphome: name: ${name} friendly_name: ${friendly_name} esp32: board: esp32-c6-devkitc-1 # generic C6; "seeed_xiao_esp32c6" also works framework: type: esp-idf # esp-idf recommended for ESP32-C6 logger: api: # native HA connection (real-time, no polling) ota: - platform: esphome wifi: ssid: !secret wifi_ssid password: !secret wifi_password ap: ssid: "TiltForge Setup" # fallback captive portal on first boot / no wifi captive_portal: # --- I2C bus for the AS5600 (TiltForge routes it to D8/D9) ---i2c: sda: GPIO19 # XIAO D8 scl: GPIO20 # XIAO D9 frequency: 100kHz # --- AS5600 magnetic encoder (native ESPHome component, 2024.2+) ---as5600: sensor: - platform: as5600 raw_position: name: "Raw angle" id: as5600_raw update_interval: 100ms # --- L9110 H-bridge outputs ---# Truth table (TiltForge): IA=on/IB=off -> open ; IA=off/IB=on -> close ;# both off -> stop (coast).output: - platform: gpio pin: GPIO23 # XIAO D5 = Motor IA id: motor_ia - platform: gpio pin: GPIO16 # XIAO D6 = Motor IB id: motor_ib globals: - id: target_pos type: float restore_value: no initial_value: '0.5' - id: is_moving type: bool restore_value: no initial_value: 'false' # --- Motor helper scripts ---script: - id: motor_open # drive toward OPEN then: - output.turn_off: motor_ib - output.turn_on: motor_ia - id: motor_close # drive toward CLOSED then: - output.turn_off: motor_ia - output.turn_on: motor_ib - id: motor_halt then: - output.turn_off: motor_ia - output.turn_off: motor_ib - lambda: 'id(is_moving) = false;' # --- Template cover: position slider + open/close/stop ---cover: - platform: template name: ${friendly_name} id: tiltforge_cover device_class: blind has_position: true # Current position (0.0 closed .. 1.0 open) derived from the AS5600. lambda: |- float span = ${raw_open} - ${raw_closed}; if (span == 0.0f) return 0.0f; float p = (id(as5600_raw).state - (float)${raw_closed}) / span; if (p < 0.0f) p = 0.0f; if (p > 1.0f) p = 1.0f; return p; open_action: - lambda: 'id(target_pos) = 1.0f; id(is_moving) = true;' - script.execute: motor_open close_action: - lambda: 'id(target_pos) = 0.0f; id(is_moving) = true;' - script.execute: motor_close stop_action: - script.execute: motor_halt position_action: - lambda: |- float span = ${raw_open} - ${raw_closed}; float cur = (id(as5600_raw).state - (float)${raw_closed}) / span; id(target_pos) = pos; id(is_moving) = true; if (pos > cur) { id(motor_open).execute(); } else { id(motor_close).execute(); } # --- Closed-loop stop: halt the motor once the encoder reaches target ---interval: - interval: 100ms then: - if: condition: lambda: 'return id(is_moving);' then: - lambda: |- float span = ${raw_open} - ${raw_closed}; float cur = (id(as5600_raw).state - (float)${raw_closed}) / span; if (cur < 0.0f) cur = 0.0f; if (cur > 1.0f) cur = 1.0f; if (fabs(cur - id(target_pos)) <= ${pos_tolerance}) { id(motor_halt).execute(); id(tiltforge_cover).position = cur; id(tiltforge_cover).publish_state(); }Which do I want?
| Stock + REST | ESPHome | |
|---|---|---|
| Reflash needed | — | ✓ |
| Update speed | Poll (~5 s) | Real-time |
| Matter (Apple/Google/Alexa)† | ✓ | — |
| HA-side config | Small YAML | None |
| Keeps TiltForge firmware | ✓ | — |
† Matter is available on the Matter-capable TiltForge unit.
Notes
- +
.localnames: HA usually resolvestiltforge-XXXX.local. If yours can't, use the device IP (from your router, or theiserial command). - +Tilt vs position: TiltForge is a slat-tilt device. The REST config maps that onto HA's position slider by default (cleanest UX); a slat-tilt variant is included in the file. ESPHome models position directly.