Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/docker-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ jobs:
flavor: latest=false
tags: |
type=semver,pattern={{version}},value=${{ github.event.release.tag_name }}
type=semver,pattern={{major}}.{{minor}},value=${{ github.event.release.tag_name }}
type=semver,pattern={{major}},value=${{ github.event.release.tag_name }}
type=raw,value=latest,enable=${{ startsWith(github.event.release.tag_name, 'v') && !contains(github.event.release.tag_name, '-') }}
type=semver,pattern={{major}}.{{minor}},value=${{ github.event.release.tag_name }},enable=${{ !github.event.release.prerelease && !contains(github.event.release.tag_name, '-') }}
type=semver,pattern={{major}},value=${{ github.event.release.tag_name }},enable=${{ !github.event.release.prerelease && !contains(github.event.release.tag_name, '-') }}
type=raw,value=latest,enable=${{ !github.event.release.prerelease && startsWith(github.event.release.tag_name, 'v') && !contains(github.event.release.tag_name, '-') }}
type=sha,prefix=sha-
labels: |
org.opencontainers.image.title=roborock-local-server
Expand Down
14 changes: 14 additions & 0 deletions docs/home_assistant.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ After the dashboard opens, complete the same post-start steps as the Docker inst

If you need the MITM protocol sync secret for the Roborock app flow, sign in to the admin page and open **Protocol Auth**. The dashboard shows the active `admin.session_secret`, so you do not need to inspect `/data/config.toml` manually.

### Opt-in Beta testing

The repository also provides **Roborock Local Server Beta** for prerelease
testing. Install it explicitly from the add-on store after its prerelease image
has been published. Stable installations remain on their stable image version.

Beta has a separate add-on slug and persistent data, so the first installation
requires its own configuration and cloud import. If reusing the same hostname
and ports, stop the stable add-on and disable its start-on-boot setting while
testing. Later Beta versions use the normal Home Assistant update flow.

See the [Beta setup and testing instructions](https://github.com/Python-roborock/local_roborock_server/blob/main/roborock_local_server_beta_addon/DOCS.md)
for installation, V2 checkpoints, and returning to stable.

### Add-on Behavior

- The add-on always runs the embedded MQTT broker and keeps the topic bridge enabled.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "roborock-local-server"
version = "1.0.2"
version = "1.1.0-rc1"
description = "private local Roborock server stack."
requires-python = ">=3.11,<3.14"
readme = "README.md"
Expand Down
8 changes: 8 additions & 0 deletions roborock_local_server_beta_addon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## 1.1.0-rc1

- Added an opt-in Beta add-on with its own configuration and persistent data. The existing stable add-on stays on 1.0.2.
- Added experimental RSA-4096 / SHA-384 public-key recovery for V2 `GET /region` onboarding requests observed on Saros a279.
- Preserve request version metadata, resume pending recovery after a server restart, and verify all captured samples before accepting a recovered V2 public key.
- Full V2 onboarding and MQTT connectivity still require hardware validation. The V2 unsupported onboarding status remains in place.
59 changes: 59 additions & 0 deletions roborock_local_server_beta_addon/DOCS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Roborock Local Server Beta

This is an opt-in prerelease for testing experimental changes. Version
`1.1.0-rc1` includes V2 public-key recovery verified against saved Saros a279
requests. Complete V2 onboarding and MQTT connectivity still need hardware tests.

The stable **Roborock Local Server** add-on remains on `1.0.2`. Installing or
updating that add-on does not select this prerelease. Beta is a separate add-on,
with its own settings, cloud import, keys, and persistent `/data` directory.

## First installation

1. Wait until the `v1.1.0-rc1` GitHub prerelease has published its container image.
2. Add the repository `https://github.com/Python-roborock/local_roborock_server`
in the Home Assistant add-on store if it is not already present, then refresh
the store and install **Roborock Local Server Beta**.
3. Back up the stable add-on if installed. If reusing its hostname and ports,
stop the stable add-on and turn off its **Start on boot** setting while testing.
The two add-ons use the same default ports (`555` and `8881`) and cannot run
together on those ports. Beta defaults to manual startup.
4. Enter the server hostname, ports, admin password, protocol email/PIN, and TLS
settings in Beta's configuration. Settings and private certificate files are
not automatically copied from the stable add-on; use certificate paths that
Beta can access, or configure certificate issuance for Beta.
5. Start Beta, open `https://YOUR_API_HOST:555/admin` using the configured port,
and perform cloud import so the test vacuum appears. Enable **Allow new app
logins, onboarding, and first-time vacuum connections**.
6. On a second computer with Wi-Fi, use the onboarding scripts from the
`v1.1.0-rc1` checkout and run:

```bash
uv run start_onboarding.py --server YOUR_API_HOST:555
```

Follow the model's Wi-Fi reset and hotspot prompts. The terminal tool allows
retrying while the server still marks V2 unsupported.

## What to check

The V2 query sample counter may remain zero because V2 uses header signatures.
Wait for **Public Key determined** in the admin dashboard, then perform another
pairing cycle and check for NC registration, authenticated MQTT traffic, status
updates, and a command response. A recovered key or HTTP 200 alone does not
establish successful onboarding. The unsupported banner remains in this build.

Keep the saved Beta data between attempts. Report the model, firmware, Beta
version, recovery state, and furthest connection stage reached. Keep original
logs locally and redact credentials before sharing excerpts.

## Later updates and returning to stable

Subsequent prereleases appear as updates to **Roborock Local Server Beta** after
the store refreshes. Update that entry normally to keep its settings and samples.

To return to stable, stop Beta and start the stable add-on. Restore its original
start-on-boot setting if desired. Stable's saved data is retained, but changes
made while testing Beta are not copied back. A vacuum paired to Beta may need
another onboarding cycle, and clients reconfigured for Beta may need to be
reconfigured for stable again.
62 changes: 62 additions & 0 deletions roborock_local_server_beta_addon/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Roborock Local Server Beta
version: "1.1.0-rc1"
slug: roborock_local_server_beta
description: Opt-in prerelease testing of the Roborock local server, including experimental V2 key recovery.
stage: experimental
url: "https://github.com/Python-roborock/local_roborock_server"
image: "ghcr.io/python-roborock/local_roborock_server"
startup: services
boot: manual
init: false
arch:
- amd64
- aarch64
ports:
555/tcp: 555
8881/tcp: 8881
ports_description:
555/tcp: Roborock HTTPS API
8881/tcp: Roborock MQTT TLS proxy
map:
- ssl:ro
- addon_config:rw
- all_addon_configs:ro
webui: "https://[HOST]:[PORT:555]/admin"
options:
stack_fqdn: "api-roborock.example.com"
https_port: 555
mqtt_tls_port: 8881
advertised_https_port: 0
advertised_mqtt_tls_port: 0
region: "us"
tls_mode: "provided"
tls_base_domain: ""
tls_email: ""
acme_server: "zerossl"
acme_eab_kid: ""
acme_eab_hmac_key: ""
cloudflare_token: ""
cert_file: ""
key_file: ""
admin_password: ""
protocol_login_email: ""
protocol_login_pin: ""
schema:
stack_fqdn: str
https_port: port
mqtt_tls_port: port
advertised_https_port: int
advertised_mqtt_tls_port: int
region: list(us|eu|cn|ru)
tls_mode: list(provided|cloudflare_acme)
tls_base_domain: str
tls_email: str
acme_server: list(zerossl|actalis)
acme_eab_kid: str
acme_eab_hmac_key: password
cloudflare_token: str
cert_file: str
key_file: str
admin_password: password
protocol_login_email: email
protocol_login_pin: password
2 changes: 1 addition & 1 deletion src/roborock_local_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

__all__ = ["__version__"]

__version__ = "1.0.2"
__version__ = "1.1.0-rc1"
34 changes: 29 additions & 5 deletions tests/test_version_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import tomllib
from pathlib import Path

from packaging.version import Version
import yaml

from roborock_local_server import __version__


Expand All @@ -17,8 +20,29 @@ def test_init_module_exports_single_version_literal() -> None:


def test_home_assistant_addon_version_matches_package_version() -> None:
addon_config = Path("roborock_local_server_addon/config.yaml").read_text(encoding="utf-8")
match = re.search(r'^version:\s*"([^"]+)"\s*$', addon_config, re.MULTILINE)
assert match is not None
assert match.group(1) == __version__

# Prereleases update only the opt-in Beta add-on. Stable keeps its last release.
directory = (
"roborock_local_server_beta_addon"
if Version(__version__).is_prerelease
else "roborock_local_server_addon"
)
addon = yaml.safe_load(Path(directory, "config.yaml").read_text(encoding="utf-8"))
assert addon["version"] == __version__


def test_stable_addon_never_selects_a_prerelease_image() -> None:
addon = yaml.safe_load(Path("roborock_local_server_addon/config.yaml").read_text(encoding="utf-8"))
version = Version(addon["version"])
assert not version.is_prerelease and not version.is_devrelease
assert addon["slug"] == "roborock_local_server"
assert addon["image"] == "ghcr.io/python-roborock/local_roborock_server"


def test_beta_addon_requires_a_separate_opt_in_installation() -> None:
stable = yaml.safe_load(Path("roborock_local_server_addon/config.yaml").read_text(encoding="utf-8"))
beta = yaml.safe_load(Path("roborock_local_server_beta_addon/config.yaml").read_text(encoding="utf-8"))
assert beta["slug"] != stable["slug"]
assert beta["name"] != stable["name"]
assert beta["stage"] == "experimental"
assert beta["boot"] == "manual"
assert Version(beta["version"]).is_prerelease
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.