Skip to content

Building Products

Daniel Frenkel edited this page Jul 24, 2026 · 1 revision

Building Products on valar-core

How finished devices — like the Ropener (curtain opener) and Glasscalibur (window opener) — are built on these boards without forking the firmware. Use the same pattern for your own device.

The rule: shared firmware exists once and is referenced, never copied (see docs/FIRMWARE-POLICY.md).

valar-core  (shared firmware in this repo — Wi-Fi, API, OTA, TMC2209, buttons, tuning)
   ├── catalog boards: VAL3000 / VAL3100 / VAL3101  (thin wrappers in boards/)
   ├── Ropener        (product repo — adds curtain cover, homing, gear math)
   └── Glasscalibur   (product repo — adds window cover; its own PCB)

Pull valar-core as a remote package

Your product config lives in your own repo and includes the core by git tag:

packages:
  valar_core:
    url: https://github.com/Valar-Systems/valar-motion
    ref: v0.2.0                     # pin a tag — never float on main
    files: [common/valar-core.yaml]
    refresh: 1d

Pin ref: to a release tag so a core change never silently alters your build; bump it deliberately and re-test. Full working examples: docs/examples/ropener-val3100.yaml and docs/examples/glasscalibur.yaml.

The substitutions contract

Your config supplies the board identity and pin map via substitutions — chip (esp_board), the TMC2209 pins (pin_uart_tx/rx, pin_enn, pin_diag, pin_index), buttons (pin_btn1/2, pin_btn_wifi), names, and ota_repo/ota_asset/fw_version. Copy them from the matching wrapper in boards/ — see the pinout tables for what's on each board.

Custom PCB? A product on its own board (like Glasscalibur) still uses valar-core — supply your own pin map, and override the stepper hardware constants if they differ, e.g. stepper_rsense: "220 mOhm". Note that a different sense resistor changes the IRUN→amps mapping documented in the Web UI Reference (max motor current is 0.325 / (Rsense + 0.02) / √2 A RMS).

What the core gives you for free

Wi-Fi + fallback hotspot + captive portal + Improv, the native Home Assistant API, both OTA paths with rollback safety, the grouped web UI, the TMC2209 stepper (id: driver), three debounced button entities, all the tuning controls and diagnostics from the Web UI Reference, and persisted settings.

Adding your product layer

A cover entity (curtain, window, blind) is usually the heart of it — a cover: that maps position to driver step targets. See the Ropener example for the minimal version.

Button behaviour: the core's buttons are deliberately bare — pins, debounce, and ids (btn1, btn2, btn3) with no actions attached (even Button 3's "Wi-Fi reset" name is only a label in the generic build). Your product attaches gestures with !extend:

binary_sensor:
  - id: !extend btn1
    on_click:
      - cover.open: my_cover

Stall response: the core's default on_stall stops the motor and zeroes position. A product that homes with StallGuard (like the Ropener) overrides this hook with its own homing/position logic.

Scheduling: opt into the shared daily open/close schedule (fixed times or sunrise/sunset) by adding common/scheduling.yaml to your packages files: list. The mixin decides when; your product defines two scripts that decide how:

script:
  - id: schedule_open
    then: [ ... open your cover ... ]
  - id: schedule_close
    then: [ ... close your cover ... ]

It adds its own "Schedule" group to the web UI (documented in the Web UI Reference).

OTA identity: set ota_repo to your product repo and give ota_asset a stable name — your field units will fetch <ota_asset>.ota.bin from your latest release forever. See OTA Updates.

Homing recipes

  • StallGuard homing (any board): drive toward the hard stop at moderate speed, let the stall zero the position, back off. Tune first — see StallGuard Tuning.
  • Encoder homing (VAL3101): read the AS5600 for absolute position that survives power loss — no homing move needed within one shaft revolution, and it catches missed steps during travel. See the encoder section.

Clone this wiki locally