Metadata-Version: 2.4
Name: warema-wms-jog
Version: 0.1.0
Summary: Drive WAREMA WMS blinds/awnings from a USB stick that reads them but won't move them — jog (hold-to-run) drive, no WebControl pro needed. Home Assistant MQTT cover + CLI.
Author: Vladimir Yakunin
License: MIT
Project-URL: Homepage, https://github.com/vyakunin/warema-wms-jog
Project-URL: Repository, https://github.com/vyakunin/warema-wms-jog
Project-URL: Issues, https://github.com/vyakunin/warema-wms-jog/issues
Keywords: warema,wms,home-assistant,mqtt,awning,blinds,cover,jog
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Home Automation
Classifier: Operating System :: POSIX :: Linux
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyserial>=3.5
Requires-Dist: paho-mqtt>=2.0
Dynamic: license-file

# warema-wms-jog — drive WAREMA WMS blinds/awnings from a USB stick that "won't move" them

If you control WAREMA WMS blinds with the **WMS USB stick** (via the
[`santam85/addon-warema-wms`](https://github.com/santam85/addon-warema-wms) Home Assistant
add-on, the [`Marc-Berg/wms2mqtt`](https://github.com/Marc-Berg/wms2mqtt) fork, or the
[`warema-wms-venetian-blinds`](https://www.npmjs.com/package/warema-wms-venetian-blinds) npm
library) and you've hit the wall where **the stick reads positions fine but the blind never
moves** — this is for you. You do **not** need the ~€200 WMS WebControl pro.

## The problem (sound familiar?)

People keep reporting the same thing:

- the add-on discovers the device, position shows up in Home Assistant, **but open/close
  does nothing** — the log just shows `wmsRetry blindMoveToPos` → `wmsTimeout`;
- it's usually blamed on USB3 RF interference or weak range — and sometimes that's real —
- but on many installs **reads work perfectly while only *commands* are ignored**, which is
  not a range problem.

The cause on those installs is **the command, not radio and not authorization**. The stick's
absolute **move-to-position** (`blindMoveToPos`, `7070 03`) is protocol-ACKed (`7071…`) but the
motor never moves. This shows up especially on **awnings**, which generally have no intermediate
positioning the way venetian blinds do — the actuator simply doesn't act on a go-to-position
command.

> **Not an authorization/enrollment problem** (an earlier version of this README claimed that —
> it was wrong). The network-key transfer already authorizes the stick: it reads positions *and*
> jog drives the motor. Jog (`7070 06/07`) and absolute (`7070 03`) are the **same stick → same
> actuator → same auth context**; only the subcommand differs. If the stick were an unauthorized
> transmitter, jog wouldn't work either. So the stick is authorized — this awning actuator just
> doesn't execute absolute positioning. (For venetian blinds, `blindMoveToPos` does work over the
> same stick — that's the library's normal path.)

## The fix: drive by JOG (hold-to-run)

When absolute moves no-op, the motor's **jog / hold-to-run** command — the basic up/down the
calibration tools stream while you hold a direction — drives it:

| Telegram | Effect |
|---|---|
| `{R06<snr>7070 07 FFFFFFFF}` streamed ~7×/s | drive one direction |
| `{R06<snr>7070 06 FFFFFFFF}` streamed ~7×/s | drive the other direction |
| `{R06<snr>7070 01 FFFFFFFF...}` | **STOP** |

It's **dead-man**: the motor runs only while frames keep arriving (~5–7 Hz), and an explicit
stop frame halts it. Stream `06`/`07` to drive, poll position, send `01` to stop — and a
read-only stick becomes a fully drivable one. Verified 2026-06 on a WMS-MM balcony awning whose
absolute moves no-op'd; jog drove it cleanly in both directions (confirmed by position
read-back **and** by eye). See [`docs/PROTOCOL.md`](docs/PROTOCOL.md) for the byte-level evidence.

## ⚠️ Safety — read before you drive

Jog has **no soft-limit enforcement**. In particular the **extend / "open" direction of an
awning may have NO hard stop** — keep streaming and the fabric runs off the end. The retract /
"close" direction usually has a hard mechanical stop at fully-closed.

- **Never run-to-limit on the extend side.** Bound every open by **time AND/OR position**, poll
  the position mid-travel, and stop in time.
- If your end positions were ever mis-taught (e.g. a botched calibration), redo them on the
  handheld first.
- Test from a known position, watch the blind, keep a panic-stop ready
  (`warema-wms-jog --stop-only`).

## Two ways to use it

Install from PyPI:

```bash
pip install warema-wms-jog
```

It ships two console commands: **`warema-wms-cover`** (the HA cover service) and
**`warema-wms-jog`** (the CLI primitive). The modules are also importable as `warema_wms_cover`
and `warema_wms_jog`.

### 1. Home Assistant cover (recommended — battle-tested)

`warema-wms-cover` (module `warema_wms_cover`) is a self-contained service that owns the stick's
serial port, publishes an **MQTT-discovery `cover`** to Home Assistant, and maps
open / close / stop **and a position slider** to bounded jog runs. It drives toward the target,
polls position live, and stops with coast-anticipation so it lands on target — with a hard
**position cap** on the extend side so it can never run past your calibrated full-extend. It also
polls while idle so moves from your handheld remote are tracked, and emits a `remote_move` event
you can use as a manual-override trigger.

```bash
pip install warema-wms-jog
WMS_KEY=<your-network-key> WMS_SNR=<device-serial> \
MQTT_HOST=<broker> MQTT_USER=<u> MQTT_PASSWORD=<p> \
MAX_OPEN_RAW=<your-full-extend-raw> \
warema-wms-cover
```

Docker (e.g. as a Home Assistant compose service driving the stick):

```yaml
  warema:
    image: python:3-slim
    devices: ["/dev/ttyUSB0:/dev/ttyUSB0"]
    command: ["sh", "-c", "pip install -q warema-wms-jog && exec warema-wms-cover"]
    environment:
      - WMS_KEY=${WMS_KEY}
      - WMS_SNR=<device-serial>
      - MQTT_HOST=<broker>
      - MAX_OPEN_RAW=<your-full-extend-raw>
```

The stick's serial port must be free (stop the stock add-on/bridge first if it holds it). All
knobs are env vars — see the docstring at the top of `warema_wms_cover.py` (`WMS_PORT`, `WMS_BAUD`,
`WMS_CHAN`, `WMS_PAN`, `MAX_OPEN_RAW`, `OPEN_SECS`, `POS_LEAD`, …). Find your full-extend raw
value with the CLI tool below (`--getpos-only`) at the fully-open position.

### 2. CLI primitive / proof tool

`warema-wms-jog` (module `warema_wms_jog`) is a minimal injector to validate jog on your hardware
before wiring anything: it sends the network preamble, reads position, streams a **bounded**
jog burst, then stops.

```bash
pip install warema-wms-jog
# just read position (find your full-extend raw):
WMS_KEY=<key> warema-wms-jog --snr <serial> --getpos-only
# jog ~1s in one direction (watch it move!), then auto-stop:
WMS_KEY=<key> warema-wms-jog --snr <serial> --code 07 --ms 1000
# panic stop:
WMS_KEY=<key> warema-wms-jog --snr <serial> --stop-only
```

### 3. Patch for the node add-on/library

[`patches/addon-warema-wms-jog.patch`](patches/addon-warema-wms-jog.patch) adds the jog
primitive (`blindJog` encoder + `vnBlindJogStart`/`vnBlindJogStop`) to the vendored
`warema-wms-venetian-blinds` library inside the add-on, as a non-breaking addition. See
[`patches/README.md`](patches/README.md). Upstreamed as a PR to `Marc-Berg/wms2mqtt`.

## Where to find your network key / serial / PAN / channel

These come from your existing add-on setup — the add-on logs print `wms_key`, `wms_pan_id` and
`wms_channel` on first run after the handheld transfers network access data. **Never commit your
network key** — these tools read it only from the `WMS_KEY` env var (or, for the `warema-wms-jog` CLI,
from a capture trace), and redact it from any echo.

## Credit

Built on the reverse-engineering in
[`warema-wms-venetian-blinds`](https://www.npmjs.com/package/warema-wms-venetian-blinds) by
`aemm` (MIT) and the [`santam85/addon-warema-wms`](https://github.com/santam85/addon-warema-wms)
Home Assistant add-on by Marco Santarelli. The jog discovery + drivers here are MIT-licensed.

## License

MIT — see [LICENSE](LICENSE).
