Changelog

Version 9.3.0

  • Add an opt-in Conclave push client (AferoBridgeV1(…, enable_conclave=True)) that connects to conclave-stream1.afero.net:443 over TLS after the first REST discovery poll. attr_change and status_change events update the same cached models REST polling uses; REST remains the source of truth for discovery, writes, and slow reconciliation.

  • Handle Conclave public / invalidate inventory frames: kind=remove emits RESOURCE_DELETED for the matching metadevice (and split clones); kind=add with target=metadevices fetches the metadevice over REST and emits RESOURCE_ADDED. Other invalidate kinds (update, …) are ignored for now.

  • TokenData.to_session_dict / from_session_dict serialize tokens for local tooling. scripts/afero_login.py and scripts/afero_bridge.py (with scripts/afero.yaml.example) create and reuse a session file without baking filesystem paths into the bridge.

  • aioafero.v1.conclave.ConclaveFrameFileHandler / attach_frame_capture buffer Conclave frame JSON to an NDJSON file via a custom logging.Handler (in-memory buffer, spill when full, flush on harvest). afero_bridge.py wires this via capture_frames in afero.yaml.

  • Decoded Conclave application frames are logged as JSON on aioafero.v1.conclave.frames at DEBUG. Enabling debug on aioafero (as Home Assistant does via the integration loggers entry) is enough; optional file handlers are only for offline captures (see docs/user/conclave.rst).

  • New subpackage aioafero.v1.conclave (access, protocol, frames, semantics, events, client) exposing ConclaveClient, ConclaveAccess, frame decoding, attribute semantics, and push-to-cache helpers.

  • AferoBridgeV1 and AferoBridgeV1.open accept a new keyword-only enable_conclave flag (default False); bridge.conclave exposes the running client when enabled and bridge.close() stops it.

  • Conclave connection lifecycle events on bridge.events: CONCLAVE_CONNECTING, CONCLAVE_CONNECTED, CONCLAVE_DISCONNECTED, and CONCLAVE_RECONNECTED. ConclaveClient.status / connected mirror the live session.

Version 9.2.1

  • Represent split clones with SplitDeviceId (device.split / resource.split). str(split) is the synthetic id; polls and writes use parent_id so names such as light-sensor-enabled (Defiant 17000046) do not hit a synthetic {id}-light path that Afero rejects with HTTP 400 (Hubspace-Homeassistant #254)

Version 9.2.0

  • Add DehumidifierController (bridge.dehumidifiers) for the dehumidifier device class (Vissani VAD50PS1AWTS). Exposes power, mode, current and target humidity, plus Fan Speed and Pump selects. Error states remain on bridge.devices.

Version 9.1.0

  • PortableACController exposes eco-mode as a select (Eco Mode). The unit already reported it; it was the only writable portable-AC function with no mapping, and engaging it on the unit can stop the compressor with no other state change.

Version 9.0.1

  • Pause per-device state polls after repeated HTTPForbidden responses (common when a device was removed but is still cached until the next discovery). Mark all tracked resources for that parent (including split entities) unavailable, stop requesting updates after three consecutive failures until the next successful discovery poll, and clear tracking when the device is removed. Consecutive Forbidden counts still reset on a successful state fetch before the pause threshold.

Version 9.0.0

Breaking changes:

  • AferoBridgeV1 no longer accepts a password; pass username and refresh_token (and optional bearer token).

  • AferoBridgeV1 and AferoAuth require an aiohttp.ClientSession at construction (AferoBridgeV1.open may create one when omitted).

  • Removed AferoBridgeV1.otp_login; use v1.AferoAuth.for_login and submit_otp instead.

  • AferoAuth is decoupled from the bridge and uses aiohttp.ClientSession directly. Use AferoAuth.for_login(session, username, password) for credential login; login() and submit_otp() return TokenData. Use AferoAuth(session, username, refresh_token, token=..., token_expiration=...) for runtime auth.

  • AferoAuth is exported on aioafero.v1 only (not top-level aioafero).

  • Renamed module aioafero.anonomyize_data to aioafero.anonymize_data.

  • AferoBridgeV1 supports async with (initialize on enter, close on exit).

  • Added await AferoBridgeV1.open(...) to construct a bridge and wait for the first discovery poll.

  • AferoAuth clears _password after the credential POST (including on OTP and auth failure).

  • Redact OpenID authorization codes in debug logs; register account IDs before assignment.

  • Bridge API request kwargs logged at DEBUG only (method and URL remain at INFO).

  • Bearer token expiration uses API expires_in minus a 2-second buffer (fallback when omitted).

  • Redact PKCE code_verifier in challenge debug logs.

  • Removed implicit StreamHandler from AferoBridgeV1; configure logging in the host app.

  • Dependency floors: aiohttp>=3.14.3, beautifulsoup4>=4.12.0 (aiohttp 3.14.3 addresses published CVEs).

Other:

  • CI security scanning: Bandit (pre-commit), pip-audit (tox -e audit), CodeQL, Dependabot alerts/updates.

Version 8.0.0

Breaking

  • Remove Light.dual_channel, Light.color_brightness, and Light.white_brightness. Use Light.channels (LightChannel), Light.is_dual_channel, channel_brightness(), and channel_on() instead (Hubspace-Homeassistant #160)

  • Resource update events for per-channel brightness use the channels key (not color_brightness / white_brightness)

  • channel_brightness() returns float | None (was effectively int | None)

  • Dual-channel fixtures no longer expose per-channel brightness as Light.numbers entries; use channels / set_state instead. Effect sequence speed remains numbers[("speed", "color-sequence")] (display name Effect Speed)

  • Controller set_state optional fields are keyword-only after device_id (fan, light, switch, valve, exhaust fan, security system / keypad / sensor; thermostat already was). switch / valve turn_on / turn_off take instance as keyword-only as well

  • Every feature dataclass now requires keyword-only function_class and function_instance (including explicit None). Renames func_class / func_instance / TargetTemperatureFeature.instance

  • Feature api_value is value-only; Afero envelopes are built by the serializer from feature-owned identity

  • Remove get_afero_instance_for_state, controller ITEM_MAPPING, and StandardMixin.instances / get_instance outbound inference, along with the unused AferoFeatures registry

  • Preserve observed identities when initializing and updating devices (including fan fan-power and split-light zones) (Hubspace-Homeassistant #240)

  • Rename get_afero_state_from_feature keyword parameters to the full function_class / function_instance / current_value names

Added / behavior

  • Dual-channel RGB+WW fixtures stay one combined light; color/white toggles stay on Light.channels (not Switch clones). True multi-zone (main/trim) lights still split normally

  • LightController.set_state(..., channel=) toggles or dims a dual-channel instance (color / white) for integrations that present two light entities or channel switches. Turning a channel off moves color-mode to the remaining channel when one is still on

  • Resolve color-mode to mixed when setting RGB or white while another channel is already on

  • dataclass_to_afero resolves Put features against the matching cached instance (dict-backed fields and dual-channel light channels) so unchanged channel toggles/dimming are not re-sent (aioafero.v1.models.update_comparison)

  • Preserve effect-owned function classes during plural emission and ensure color-temperature values without a prefix remain valid numeric strings

  • Resolve plural color-sequence state independently of API row ordering and match function definitions by exact functionInstance (including None)

  • Skip fan direction updates when the fan is off (Hubspace API quirk)

7.0.10

  • MITM capture test bed for Hubspace client traffic: docker/mitmweb/ compose, scripts/mitmweb.sh / inject-mitm-ca.sh, and docs/mitm/ (install + daily workflow). WireGuard mode, WSL adb, Windows emulator; based on Hubspace-Homeassistant #198.

Version 7.0.9

  • Fix portable AC power switch toggles sending functionInstance: "power" from the split entity id; the device expects null (Hubspace-Homeassistant #234)

Version 7.0.8

  • Preserve color-mode category hints (e.g. no-brightness on night-light)

  • Add Light.color_mode_has_hint

  • When enabling no-brightness modes while off, select the mode before powering on and abort turn-on if that PUT fails; omit brightness for those modes; never attach a color-mode change (including force_white_mode) to turn-off (Hubspace-Homeassistant #232)

  • Ignore unsolicited fields on device-state PUT responses (e.g. power echoed on a mode-only update) so cached/HA state does not flicker

Version 7.0.7

  • Revert dual-channel single-light handling introduced in 7.0.5 (flushmount / RGBCW color+white split routing restored). That release was not consumed by the Hubspace Home Assistant integration.

  • Retain the 7.0.6 device-state poll units fix for Fahrenheit (Hubspace-Homeassistant #231)

Version 7.0.6

  • Pass units on device-state polls so Fahrenheit temperatures do not revert to Celsius after the first discovery (Hubspace-Homeassistant #231)

Version 7.0.5

  • Do not split dual-channel RGB+WW fixtures (color / white brightness zones); keep RGBCW strips, flushmounts, and similar devices as one light with shared color controls

  • Prefer primary brightness for dual-channel lights and send dimming PUTs via DimmingFeature.func_instance

  • Route RGB/white/CCT brightness PUTs to the color or white channel on dual-channel fixtures; track per-channel levels on the light model for integrations (Hubspace-Homeassistant #160)

  • Detect dual-channel fixtures from function/capability definitions, not only live state values

  • Merge polled device states into cache so partial poll responses do not drop dual-channel metadata

  • Add Light.is_dual_channel and Light.channel_brightness() for downstream integrations

  • Remove LCN3002LM-01 WH-specific split routing; main/trim and other true multi-zone lights still split normally

  • Fix AferoDevice list fields to use default_factory=list instead of default=list

Version 7.0.4

  • Re-send color-mode: white when setting white on no-CCT split-light zones, even if the cached mode is already white

Version 7.0.3

  • Fix an issue where lastUpdateTime was not being set correctly

Version 7.0.2

  • Fix split-light polling to query parent metadevice ids once instead of synthetic split ids

  • Cache split light clones separately so parent state is not overwritten

  • Ignore other instances’ power states when applying inbound updates to split lights

  • Merge partial API state responses into cached device state instead of replacing it

  • Use instance-scoped color_modes on split lights so capability flags match each zone

  • Route white-only control on white-only zones through color-mode: white

  • Ignore inbound color-temperature updates for lights that have no CCT function

Version 7.0.1

  • Fix an issue for split lights

Version 7.0.0

  • Break: AferoBridgeV1.fetch_data has been replaced with AferoBridgeV1.fetch_discovery_data

  • Implement AferoBridgeV1.fetch_all_device_states that queries all known device states

  • Reduce the total bandwidth during polling by querying only device state

Version 6.2.0

  • Lights now split on a more generic model vs one based on model

Version 6.1.2

  • Switch light speed from select to number

Version 6.1.1

  • Fix issue where speed could not be adjusted for lights

Version 6.1.0

  • Add speed control for lights

Version 6.0.1

  • Fix an issue where DeviceController would reuse existing devices if it was within the same memory space.

Version 6.0.0

  • Enable the bridge to request fahrenheit and celsius

Version 5.6.1

  • Fix an issue where async_block_until_done stated all tasks were done when they were not

Version 5.6.0

  • Enable OTP login workflow

Version 5.5.0

  • Add support for air swing on portable ACs

Version 5.4.1

  • Revert hvac_mixin as it caused downstream issues

Version 5.4.0

  • Fix security sensors not tracking

  • Ensure binary_sensors are correctly updated

  • Better handling for security sensors

  • Set models for sensors

  • Add security-system-keypad support

Version 5.3.0

  • Device states are set from the API response rather than the sent states

Version 5.2.3

  • Fix an issue where the Security Sensor name was not being used

  • Fix an issue where Security Sensors would reference the incorrect key during an update

Version 5.2.2

  • Fix an issue where auth would not retry on failure

Version 5.2.1

  • Fix an issue where checking refresh token could cause an AttributeError

Version 5.2.0

  • Enable version polling once every 6 hours

Version 5.1.1

  • Fix an issue where switches would not send the correct ID during a change

Version 5.1.0

  • Add support for white LEDs

Version 5.0.0

  • BREAK: Exhaust Fan device split

  • BREAK: Portable AC switch split

  • Lights can now create switches for ‘toggles’

  • Convert to ruff from black

Version 4.2.0

  • Add support for Light LCN3002LM-01 WH

Version 4.1.1

  • Fix an issue where split devices would not send the correct ID to Afero

  • Fix an issue where siren-action was not sent when adjusting alarm state

Version 4.1.0

  • Add support for Security Systems

  • Add a callback when setting device states

Version 4.0.1

  • Fix an issue where DeviceController did not receive split devices

Version 4.0.0

  • API BREAK: Update DeviceController to receive raw polled data so it can accurately

    determine parent devices with split devices

  • Implement the ability for a device to split into multiple devices

    for easier control

  • Implement the ability to have a callback when setting states so

    split devices can properly update

  • Implement the ability for numbers to have a custom display name rather than

    the aggregate name

Version 3.3.5

  • Fix an issue where auth could produce a UHE

Version 3.3.4

  • Fix an issue in thermostats where they incorrectly stated support for auto

Version 3.3.3

  • Fix an issue in thermostats where the target_temperature could cause a UHE

Version 3.3.2

  • Fix an issue in thermostats where the target_temperature always returned celsius

Version 3.3.1

  • Enable switch for Portable AC

  • Fix an issue where setting temps did not follow the step

  • Fix an issue where portable AC’s timer did not turn it on

Version 3.3.0

  • Add Portable AC device class

Version 3.2.2

  • Solidify bridge API so Home Assistant tests do not call any private methods

Version 3.2.1

  • Fix a regression around battery sensor not showing

Version 3.2.0

  • Fully implement exhaust fans

Version 3.1.1

  • Add a secret that was missing

Version 3.1.0

  • Hide secrets in logs by default

Version 3.0.2

  • Fix an issue where thermostats would set the incorrect target temperature if the mode changed at the same time

Version 3.0.1

  • Fix an issue where thermostats would state invalid modes

Version 3.0.0

  • API Break: Binary Sensors / Sensors may no longer be included under the Device Resource

  • Binary Sensors / Sensors are now included with the most logical parent

Version 2.0.1

  • Add support for thermostats

Version 2.0.1

  • Add support for determining if “white” is a supported option for lights

Version 2.0.0

  • Migration from aiohubspace to aioafero to support the Aefro IoT Cloud

Version 1.2.0

  • Enable auth to reuse a previously generated token

Version 1.1.3

  • Fix an issue where devices could be properly identified

Version 1.1.2

  • Fix an issue where water valves were showing as fans

Version 1.1.1

  • Fix an issue where 500’s could stop polling

Version 1.1.0

  • Added an event type for invalid auth during token refresh

  • Added a check to ensure the token is valid during refresh time. If invalid, the event invalid_auth is emitted.

Version 1.0.4

  • Add additional logging around issues when querying Hubspace API

Version 1.0.3

  • Fixed an issue where a new device could be generated prior to an element

Version 1.0.2

  • Fixed an issue where an updated sensor could use an incorrect value

Version 1.0.1

  • Fixed an issue where passwords could be logged to debug logs

Version 1.0.0

  • Solidify API

  • Fix an issue where the loop would break during collection

  • Increase code coverage

Version 0.7.0

  • Add support for glass-doors

Version 0.6.4

  • Fix an issue where locks were not being managed by LockController

  • Fix an issue with Fans not correctly setting presets

  • Less greedy updates - Only forward updates if something has changed on the resource

  • Create additional unit tests to ensure functionality

Version 0.6.3

  • Fix an issue with Binary sensors to ensure the state is obvious

Version 0.6.2

  • Fix an issue with fan’s preset not correctly identifying its state

Version 0.6.1

  • Fix an issue with binary sensors to ensure they return True / False

Version 0.6.0

  • Add the ability to send raw states to Hubspace and have the tracked device update

Version 0.5.1

  • Fixed an issue where the account ID would not be set during a partial initialization

Version 0.5.0

  • Only emit updates to subscribers if values have changed

  • Fixed an issue where the logger was always in debug

Version 0.4.1

  • Adjusted logic for how HubspaceDevice modified models

  • Fixed an issue around Device initialization

Version 0.4.0

  • Added tracking for BLE and MAC addresses

  • Added binary sensors

Version 0.3.7

  • Fixed an issue around subscribers with deletion

Version 0.3.6

  • Fixed an issue around switches not properly subscribing to updates

  • Fixed an issue where Hubspace could return a session reauth token when preparing a new session

  • Added models for HPSA11CWB and HPDA110NWBP

Version 0.3.0

  • Fixed an issue around subscribers with deletion

Version 0.2

  • Added support for Binary Sensors

  • Fixed an issue where a dimmer switch could not be dimmed

Version 0.2

  • Added support for Sensors

Version 0.1

  • Initial implementation

  • Rename from hubspace_async to aiohubspace

  • Utilize the concept of a bridge instead of raw connection