Skip to content
Open
29 changes: 29 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Regression tests

on:
push:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
tests:
name: Home Assistant ${{ matrix.homeassistant }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
homeassistant: ["2026.9.3", "2026.10.1"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v6
with:
python-version: "3.14"
- name: Install Home Assistant
run: python -m pip install "homeassistant==${{ matrix.homeassistant }}"
- name: Run regression tests
env:
UV_CACHE_DIR: ${{ runner.temp }}/uv-cache
run: python -m unittest discover -s tests -v
185 changes: 146 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,52 @@
# BleBox smartWindowBox – Home Assistant Integration

A **custom Home Assistant integration** for the **BleBox smartWindowBox** ([API level 20180604](https://technical.blebox.eu/openAPI_smartWindowBox/openAPI_smartWindowBox_20180604.html)).

This integration provides:

* ✅ Cover entity (open / close / stop)
* ✅ Emulated **set position (0–100%)**
* ✅ Favorite position support
* ✅ Next step support
* ✅ Rain sensor as binary sensor
* ✅ Cover and rain sensor grouped under one BleBox device
* ✅ Real-time movement metadata
* ✅ Progress %, remaining time & direction tracking
* ✅ Proper Home Assistant entity services with UI selectors

This fork includes position-scale fixes, native endpoint movement, faster status
updates, interruptible position control, and improved error handling. The original
project is [Sjnaaf/bleBoxSmartWindowBox](https://github.com/Sjnaaf/bleBoxSmartWindowBox).

## Installation (HACS)

1. Open **HACS** in Home Assistant.
2. Go to **Integrations**.
3. Click the **⋮** (top right) → **Custom repositories**.
4. Add this repository URL:

`https://github.com/Sjnaaf/bleBoxSmartWindowBox`
`https://github.com/pepijnnichting/bleBoxSmartWindowBox`

5. Choose **Category: Integration**.
6. Click **Add**.
7. Search for **BleBox smartWindowBox** in HACS and install it.
8. Restart Home Assistant.
9. Add the integration via: **Settings → Devices & Services → Add integration**.

---
---

# BleBox smartWindowBox – Home Assistant Integration
## Features

A fully featured **custom Home Assistant integration** for the **BleBox smartWindowBox** ([API level 20180604](https://technical.blebox.eu/openAPI_smartWindowBox/openAPI_smartWindowBox_20180604.html)).
### Device information

This integration provides:

* ✅ Cover entity (open / close / stop)
* ✅ Emulated **set position (0–100%)**
* ✅ Favorite position support
* ✅ Next step support
* ✅ Rain sensor as binary sensor
* ✅ Real-time movement metadata
* ✅ Progress %, remaining time & direction tracking
* ✅ Proper Home Assistant entity services with UI selectors
The cover and rain sensor belong to one BleBox device in **Settings → Devices &
Services**. Its identity is based on the device ID returned by the BleBox API.
The device page shows the manufacturer, model, available hardware and firmware
versions, and a link to the device's local web interface. The cover uses Home
Assistant's `window` device class.

---

## Features
Existing entity unique IDs are preserved, so the update does not require recreating
automations or removing the configured integration.

### Cover Entity

Expand All @@ -42,15 +55,28 @@ The smartWindowBox motor is exposed as a standard Home Assistant **cover entity*
* `Open`
* `Close`
* `Stop`
* `Set position (0=open, 100=closed)`
* `Set position (0=closed, 100=open)`

Since the official BleBox API does **not** support direct position setting, this integration emulates it by:
For intermediate positions, the integration emulates position setting because the BleBox API does **not** support direct position setting:

1. Sending open or close command
2. Polling device state
3. Stopping the motor once the target position is reached

This method is precise and tuned for real-world motor timing.
Fully closed (0%) and fully open (100%) use the native close/open commands. The device completes its calibrated travel without an early stop based on a reported percentage.

All exposed position attributes use the Home Assistant scale (0% closed, 100% open). `device_position` retains the raw BleBox scale (0% open, 100% closed). Existing automations using numeric positions must invert their targets and comparisons (`100 - old_position`) when upgrading.

A new cover command cancels any previous intermediate-position controller before
sending the new command. Stop, favorite, next-step, and new position requests all
take precedence over the old target. Cancelling an active position-control
service or unloading the integration also cancels the controller and attempts
to stop the motor.

If position control loses contact with the device or exceeds its travel timeout,
it attempts a stop and reports an error. A stop cannot be guaranteed while the
device is unreachable; the original error is preserved if the stop attempt also
fails.

---

Expand All @@ -71,6 +97,39 @@ While the window is moving (open, close, favorite, or manual movement), the foll

These update live during movement.

When the motor changes direction or target, progress starts from the newly
observed position and the elapsed time and direction-specific travel estimate
are reset.

Device state is refreshed every second while a motor is moving and every five
seconds while idle. Commands trigger an immediate refresh. Intermediate-position
control also publishes each direct device reading to Home Assistant.

### Apple Home / HomeKit Bridge

Use Home Assistant 2026.10 or newer when exposing the cover through HomeKit
Bridge. In 2026.9.3 the bridge can retain an old target position during movement
started outside Apple Home, causing iOS to display the opposite direction even
when Home Assistant reports the correct direction. The bridge correction is in
Home Assistant; updating this custom integration alone does not fix that issue.

If a movement stops early from Apple Home, enable both loggers temporarily in
Home Assistant **Developer Tools → Actions**:

```yaml
action: logger.set_level
data:
custom_components.blebox_smartwindowbox: debug
homeassistant.components.homekit: debug
```

Reproduce one movement and inspect the logs for the requested Home Assistant
position, native command, and stop reason (target reached, cancellation,
timeout, or connection failure). Include the HomeKit lines immediately before
the stop when reporting the issue. Debug logging does not change motor behavior.
After collecting the logs, run the same action with both levels set to `warning`
or restart Home Assistant to restore the configured logging levels.

---

### Rain Sensor
Expand Down Expand Up @@ -103,13 +162,13 @@ The integration registers proper entity services with UI selectors:
| `blebox_smartwindowbox.next_step` | Perform next-step movement |
| `blebox_smartwindowbox.set_position` | Move to specific position (0–100) |

These appear in **Developer Tools → Services**.
These appear in **Developer Tools → Actions**.

---

# Installation

## Method 1 — Manual Installation (Recommended)
## Manual Installation

1. Download or clone this repository.
2. Copy the folder:
Expand All @@ -133,6 +192,8 @@ config/
├── __init__.py
├── api.py
├── binary_sensor.py
├── brand/
│ └── icon.png
├── config_flow.py
├── const.py
├── coordinator.py
Expand All @@ -148,6 +209,17 @@ config/

* **Settings → System → Restart**

## Updating

Download the latest integration files through HACS, or replace the complete
`custom_components/blebox_smartwindowbox/` folder for a manual installation. Restart
Home Assistant afterward. Existing integration entries can remain configured.

If upgrading from the original position scale, review automations and scripts
with numeric position targets: Home Assistant now uses **0% closed, 100% open**.
Invert old values with `100 - old_position`. Native open, close, stop, and favorite
actions keep their meaning.

---

# Setup in Home Assistant
Expand Down Expand Up @@ -192,8 +264,8 @@ You can:
Position scale:

```
0 = Fully Open
100 = Fully Closed
0 = Fully Closed
100 = Fully Open
```

---
Expand All @@ -203,7 +275,7 @@ Position scale:
Go to:

```
Developer Tools → Services
Developer Tools → Actions
```

Select:
Expand Down Expand Up @@ -236,7 +308,7 @@ The integration will:

* Move in correct direction
* Track progress
* Stop exactly at the target
* Stop when the reported position reaches the target within the configured tolerance

---

Expand All @@ -260,22 +332,28 @@ These update live while the window moves.

# Tuning & Advanced Settings

Movement precision is controlled by:
The defaults are defined in `custom_components/blebox_smartwindowbox/const.py`:

```python
POSITION_TOLERANCE = 1
POLL_INTERVAL_SEC = 0.20
POLL_INTERVAL_SEC = 0.35
EXTRA_STOP_DELAY_SEC = 0.15
MOVING_UPDATE_INTERVAL_SECONDS = 1
UPDATE_INTERVAL_SECONDS = 5
```

Lower polling interval increases precision but increases API traffic.
Intermediate-position control polls every 0.35 seconds and uses a tolerance of
one percentage point. After reaching the target, a second stop is sent after
0.15 seconds to reduce coasting. A newer command cancels the old controller,
including a pending second stop.

Default polling interval while moving:
Regular status polling runs every second during movement and every five seconds
while idle. Fully open and fully closed requests use native commands and do not
use the intermediate-position stop loop.

```python
POLL_INTERVAL_SEC = 0.20
```

This value has been tuned for reliable real-world accuracy.
These are source-code constants, not options in the integration UI. Lower polling
intervals increase API traffic. Physical accuracy depends on calibration, response
time, and motor coasting.

---

Expand All @@ -295,21 +373,50 @@ This value has been tuned for reliable real-world accuracy.

* BleBox smartWindowBox
* API level 20180604
* Single motor configuration (current version)
* Validated with a single-motor configuration
* Development checks passed on Home Assistant 2026.9.3 and 2026.10.1
* Home Assistant 2026.10 or newer recommended for HomeKit Bridge

---

# Known Limitations

* Position control is emulated (not native in API)
* Intermediate-position control is emulated; endpoint movement uses native commands
* Accuracy depends on motor calibration
* Rapid repeated commands may slightly affect precision

---

# Development checks

With Home Assistant installed in the active Python environment, run the
regression tests from the repository root:

```sh
python -m unittest discover -s tests -v
```

These cover position conversion, native endpoints, intermediate movements,
command replacement and cancellation, network errors, timeouts, polling intervals,
and progress resets after a direction or target change. API tests use a local HTTP
server; other device responses are simulated. Physical travel still needs to be
verified on a real device. GitHub Actions runs these tests on Home Assistant
2026.9.3 and 2026.10.1 with Python 3.14 for pushes and pull requests, alongside
Hassfest validation. The suite also checks actual Home Assistant device/entity
registration and identity preservation after reloading the integration.

## Version 0.1.0

This version includes the corrected Home Assistant position scale, native endpoint
commands, faster status updates, interruptible position control, improved error
handling, movement-progress resets, shared device metadata, and the `window`
device class. Upgrade instructions and the position-scale migration are described
above.

---

# Future Improvements (Ideas)

* HACS support
* Multi-motor support
* Dedicated progress sensor entity
* Configurable precision via UI
Expand All @@ -319,4 +426,4 @@ This value has been tuned for reliable real-world accuracy.

# License

MIT License
MIT License
9 changes: 6 additions & 3 deletions custom_components/blebox_smartwindowbox/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ async def _get_json(self, path: str) -> Any:
async with self._session.get(url, timeout=self._timeout) as resp:
if resp.status != 200:
raise BleBoxApiError(f"GET {path} failed: HTTP {resp.status}")
return await resp.json(content_type=None)
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
data = await resp.json(content_type=None)
if not isinstance(data, dict):
raise BleBoxApiError(f"GET {path} failed: expected a JSON object")
return data
except (aiohttp.ClientError, asyncio.TimeoutError, ValueError) as e:
raise BleBoxApiError(f"GET {path} failed: {e}") from e

async def device_state(self) -> DeviceInfo:
Expand All @@ -61,4 +64,4 @@ async def window_extended_state(self) -> dict[str, Any]:
return await self._get_json("/api/window/extended/state")

async def send_motor_command(self, channel: int, command: str) -> dict[str, Any]:
return await self._get_json(f"/s/{channel}/{command}")
return await self._get_json(f"/s/{channel}/{command}")
3 changes: 2 additions & 1 deletion custom_components/blebox_smartwindowbox/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class BleBoxRainBinarySensor(CoordinatorEntity[BleBoxCoordinator], BinarySensorE

def __init__(self, coordinator: BleBoxCoordinator, entry_id: str, sensor_id: int) -> None:
super().__init__(coordinator)
self._attr_device_info = coordinator.device_info
self._entry_id = entry_id
self._sensor_id = sensor_id
self._attr_unique_id = f"{entry_id}_rain_{sensor_id}"
Expand Down Expand Up @@ -65,4 +66,4 @@ def extra_state_attributes(self) -> dict[str, Any]:
"trend": s.get("trend"),
"elapsedTimeS": s.get("elapsedTimeS"),
"iconSet": s.get("iconSet"),
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion custom_components/blebox_smartwindowbox/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
PLATFORMS = ["cover", "binary_sensor"]

UPDATE_INTERVAL_SECONDS = 5
MOVING_UPDATE_INTERVAL_SECONDS = 1

# Motor commands
CMD_UP = "u" # moves toward position 0 (open) on your device
Expand All @@ -18,4 +19,4 @@
# Emulated position control tuning
POSITION_TOLERANCE = 1 # stop when within 1%
POLL_INTERVAL_SEC = 0.35 # fast polling while moving to target
EXTRA_STOP_DELAY_SEC = 0.15 # optional second stop to reduce coasting
EXTRA_STOP_DELAY_SEC = 0.15 # optional second stop to reduce coasting
Loading