ESPHome firmware for motorizing a cardboard roller blind with a NEMA-17 stepper
on the Valar VAL3000 board (ESP32-C3 + TMC2209). It is a fork of Valar
Systems' Ropener curtain firmware,
adapted for a D-shaft insert that direct-drives the blind tube (no rope, no
gear). Upstream Ropener is licensed under the Open Community License (OCL
v1) — a non-commercial, share-alike license — so this fork is distributed
under the same terms. See LICENSE.
| File | Purpose |
|---|---|
VAL3000/rollerblind-VAL3000.yml |
Per-board wrapper: ESP32-C3 chip variant + GPIO pin map, then includes the core. This is the file you build. |
common/rollerblind-core.yaml |
All device behaviour (board-independent). |
ESPHome isn't required to be installed locally — most people build from the ESPHome dashboard or Home Assistant add-on. From the CLI it's:
esphome run VAL3000/rollerblind-VAL3000.ymlFirst flash over USB (with Improv for Wi-Fi), then OTA from the ESPHome
dashboard thereafter. On first boot the device makes a rollerblind-XXXXXX
Wi-Fi hotspot / captive portal for network setup; find XXXXXX (MAC suffix) in
the serial boot log, then reach it at http://rollerblind-XXXXXX.local.
- Travel model: rope-cm → tube revolutions. Ropener measured travel as cm
of rope through an MK7 gear. This direct-drive blind has no gear, so travel is
set in tube revolutions via the "Travel (revolutions)" number
(
global_distance_rev, default 10 rev).global_distance_steps = revolutions × 1600. Thegear_distance_cmconstant and the "Centimeters" entity are gone. Caveat: as fabric winds onto the tube its diameter grows, so drop-per-rev isn't perfectly linear — intermediate 0..1 positions are approximate. - Rebrand. Device
rollerblind/ "Roller Blind", projectaplaceforfacts.roller-blind, coverdevice_class: shade, hostnamerollerblind-XXXXXX.local. Version reset to0.1.0-rb. - Removed the "Update Firmware (GitHub)" button (and its
http_requestOTA platform + client). That button pulled Valar's stock Ropener release and would overwrite this custom build. Update via the ESPHome dashboard/CLI OTA instead (ota: - platform: esphome, still present). - Position-aware standstill hold. Upstream idled in open freewheel (
ihold: 0), so the blind was unheld and crept/fell. This fork holds the motor at standstill with a reducedIHOLDviaapply_standstill_current, gated by a position band (Hold Band Min/Max). Default: band 0–100 % = hold everywhere,IHOLD 6— the config proven on this blind (no falls). A low 6/31 hold is a small fraction of run current, so standstill heat stays mild (and the vented bracket/shroud handle it). The band is a general mechanism: if a blind genuinely only sags over part of its travel you can narrow it to reclaim idle power at the free ends, where the code instead applies a coil-short passive brake (standstill_mode: coil_short_ls,ihold: 0). On this blind that outside-band branch is dormant (it back-drives everywhere). Gotcha, still relevant if you use coil-short: the slimcdkStandstillModeenum is mis-valued, socoil_short_hssilently truncates to NORMAL (no brake) —coil_short_lsis the string that writes a real coil-short. Caveat:IHOLDholds only while powered; a mains cut drops the blind (a mechanical / EM power-off brake is the only fix for that, not fitted).
Unchanged from Ropener (by design, per project decisions):
- StallGuard sensorless homing kept, plus manual limits (two-button hold = set home/0; "Travel (revolutions)" = open end). Both paths available.
- Direction — if open/close are reversed, flip the "Motor Direction" select (Normal ↔ Reversed).
- Home (position 0) — decide which end is "closed/0". Either tune StallGuard to home against a hard stop, or jog there and set home (hold both buttons 3 s).
- StallGuard retune —
SGTHRS/TCOOLTHRSdefault to 0 and must be tuned for this drivetrain: drive under load, watch the SG_RESULT sensor, setSGTHRS ≈ ½of the loaded reading. The rope-pull load signature does not carry over to a direct tube drive. - Travel — jog to fully open and set "Travel (revolutions)" so 100% maps to the open end.
- Speed / Acceleration / IRUN — tune for quiet, reliable motion under the blind's torque.
- RESOLVED — the blind back-drives at every position (fell at both fully open and
fully closed), so the hold band now defaults to full range (0–100 %) with
IHOLD 6, which holds it with no falls. The narrow-sag-band idea is retired. Remaining gap: this holds only while powered — a mains cut still drops it (would need a mechanical / EM power-off brake). - Tune StallGuard for the tube drive; pick the home end.
- Optional: rename the internal
ropener_coverentity id (left as-is to avoid a wide rename; it's not user-visible). - VAL3100 (ESP32-C6) wrapper not ported — this fork targets the VAL3000 only.