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
2 changes: 1 addition & 1 deletion .github/release-notes/2.0.0b16.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ See the cover docs for the details and the known limits.
- **Setup**: when `myhome.yaml` has gateway sections but none under this gateway's MAC, a warning names the entry MAC and the section keys that match no configured gateway (#106).
- **Expose Home Assistant to the bus**: a declarative `expose` engine projects HA entities and the clock onto free SCS addresses: virtual WHO 1 / WHO 2 actuators that a wall control can drive (mirroring HA state back to its LEDs), WHO 4 virtual temperature probes, WHO 18 virtual energy meters and the WHO 13 bus clock. Custom actions run as Home Assistant scripts (#750).
- **Covers**: `class:` in `myhome.yaml` now reaches WHO 2 covers (a `curtain` is no longer shown as a shutter; #39); Venetian blind slat tilt as timed slat pulses (`slat_tilt`, `slat_time`) (#508); impulse garage and gate covers on WHO 1 relays with optional safety approval, door-entry strike locks and doorbell events (#667); native broadcast for general covers behind an interface (#679); a cover stop overtakes other queued frames (#694); cover kinematics split into pluggable strategies (#739).
- **Lighting**: 2-digit Area 0 point addresses `01`–`09` (#713); ON dimmers on a fresh install are probed for brightness support (#742); timed turn-on uses the OWNd builders (#659); lights the startup sweep already reported are not polled again (#701); area and group re-syncs only skip once every member has echoed, counting only the members' own echoes in the commanded state (#740, #749).
- **Lighting**: 2-digit Area 0 point addresses `01`–`09` (#713); `where: "0004"` in `myhome.yaml` is resolved to `04`, the spelling the gateways accept and report, with a one-time warning, and registry entries created under the 4-digit spelling are moved to it; the duplicate entity and device that bus discovery had created under `04` are removed, their area, labels, icon and custom name carried over to the configured ones where those have none, and a light that bus discovery had created at the address of a WHO 1 switch or impulse cover goes with its lock / unlock buttons; the `A` / `PL` state attributes of such a light, switch or cover read `0` / `4` instead of `00` / `04`, as on bus-discovered entities (#147; the `0809` of #135 is a point of area 8, not of area 0, and stays as typed); ON dimmers on a fresh install are probed for brightness support (#742); timed turn-on uses the OWNd builders (#659); lights the startup sweep already reported are not polled again (#701); area and group re-syncs only skip once every member has echoed, counting only the members' own echoes in the commanded state (#740, #749).
- **Thermoregulation (WHO 4)**: each zone asks for its actuators (dimension 20) after the startup sweep and after a reconnect, so `hvac_action` is known at start; the new `actuators` attribute lists them (#25, #44); zone humidity is not polled on F450, F453, F453AV, F454, H4684, MH200, MH200N and MH202, where the emulated firmware did not forward `*#4*<WHERE>*60##` (own-firmware-oracle `findings/thermo-zone-requests.md`, emulated firmware; an OWNd profile flag `supports_heating_humidity_request` overrides the table); season frames, a generic setpoint mode and the central unit's season (#657, #725); holiday and vacation program attributes (#689); zone humidity remembered and queried at startup (#712); F461 zones polled, AUTO on standalone zones, fan coils per season, season kept across restarts (#714, #715); `hvac_action` derived from the season when the actuator carries no heating flag (#721); standalone default follows the plant (#698); 10 s command timeouts on the F455 removed (#675); migrated to the `native_*` temperature API (#665).
- **Sound system**: tuner frequency, preset and RDS read from WHO 22, with optional per-frequency pictures (#695); sound source commands and wall transport steps (#651); amplifiers no longer woken by volume status reports (#670); one decision engine for the power state (#683); an idle decoder can be claimed from a parked group (#723).
- **Scenarios**: WHO 25 Scenario Plus commands and events (#671); WHO 17 scene events (#656).
Expand Down
10 changes: 9 additions & 1 deletion custom_components/myhome/cover_calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,18 @@ async def async_stop_cover_calibration(hass: Any = None, gateway_mac: str | None


def _stored_calibration(config_entry: Any, device_id: str) -> dict[str, Any] | None:
"""Return the persisted calibration for a cover, if any."""
"""Return the persisted calibration for a cover, if any.

Records are keyed by the canonical device id (`04`, `04#4#01`); a record a
migration could not move is still found under its 4-digit spelling (#147).
"""
options = getattr(config_entry, "options", None) or {}
stored = options.get(CONF_COVER_TRAVEL_TIMES) or {}
entry = stored.get(str(device_id))
if entry is None:
base, sep, rest = str(device_id).partition("#")
if len(base) == 2 and base[0] == "0" and base[1] != "0":
entry = stored.get(f"00{base}{sep}{rest}")
return dict(entry) if isinstance(entry, dict) else None


Expand Down
76 changes: 73 additions & 3 deletions custom_components/myhome/legacy_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@
CONF_BUS_INTERFACE,
CONF_EXPOSE,
CONF_FILE_PATH,
CONF_MEMBERS,
CONF_PLATFORMS,
CONF_WHERE,
CONF_WHO,
CONF_ZONE,
DOMAIN,
LOGGER,
PLATFORMS,
)
from .repairs import async_create_legacy_yaml_imported_issue
from .where_grammar import canonical_point_address


def _read_legacy_yaml(
Expand Down Expand Up @@ -108,6 +112,55 @@ def _restore_shared_devices(
return restored


def canonicalise_imported_platforms(
snapshot: dict[str, dict[str, Any]],
) -> tuple[dict[str, dict[str, Any]], list[str]]:
"""The stored copy with WHO 1 / WHO 2 point addresses in their canonical spelling (#147).

A copy taken by an earlier beta holds ``where: "0004"`` under the key ``1-0004``,
the spelling the validator rewrites to ``04`` today; applied as stored it would
recreate the ``0004`` entity next to the migrated ``04`` one on every boot. The
``where``, the ``members`` and the device key are rewritten in a new copy (the
stored one is not touched). When the copy already has a device under the canonical
key, that one stays and the 4-digit twin is dropped with a WARNING. Returns the new
copy and one ``plat: old -> new`` line per rewritten device.
"""
rewritten: list[str] = []
result: dict[str, dict[str, Any]] = {}
for plat, devices in snapshot.items():
new_devices: dict[str, Any] = {}
for d_id, cfg in devices.items():
who = str(cfg.get(CONF_WHO, "")) if isinstance(cfg, dict) else ""
if who not in ("1", "2") or not isinstance(cfg.get(CONF_WHERE), str):
new_devices[d_id] = cfg
continue
where = cfg[CONF_WHERE]
canonical = canonical_point_address(where)
members = cfg.get(CONF_MEMBERS)
new_members = (
[canonical_point_address(str(m)) for m in members] if isinstance(members, list) else members
)
if canonical == where and new_members == members:
new_devices[d_id] = cfg
continue
new_cfg = {**cfg, CONF_WHERE: canonical}
if new_members != members:
new_cfg[CONF_MEMBERS] = new_members
old_prefix = f"{who}-{where}"
new_id = f"{who}-{canonical}{d_id[len(old_prefix):]}" if d_id.startswith(old_prefix) else d_id
if new_id != d_id and (new_id in devices or new_id in new_devices):
LOGGER.warning(
"Imported copy of myhome.yaml, %s: dropping '%s' (where '%s'), the copy already "
"has '%s' under the canonical spelling",
plat, d_id, where, new_id,
)
continue
new_devices[new_id] = new_cfg
rewritten.append(f"{plat}: {d_id} -> {new_id}")
result[plat] = new_devices
return result, rewritten


def migrate_legacy_yaml_to_entry_options(
hass: HomeAssistant,
entry: ConfigEntry,
Expand All @@ -120,7 +173,8 @@ def migrate_legacy_yaml_to_entry_options(
keeps its latest content rather than the one of the upgrade. Returns whether the
options changed.
"""
snapshot = _snapshot(yaml_platforms)
# The validator already spells point addresses canonically; this guards the copy.
snapshot, _ = canonicalise_imported_platforms(_snapshot(yaml_platforms))
previous = entry.options.get(CONF_MIGRATED_YAML_PLATFORMS)
if snapshot == previous or (previous is None and not any(snapshot.values())):
return False
Expand All @@ -139,13 +193,29 @@ def migrate_legacy_yaml_to_entry_options(


def _apply_imported_copy(
hass: HomeAssistant,
entry: ConfigEntry,
configured_platforms: dict[str, dict[str, dict[str, Any]]],
) -> None:
"""Fall back to the copy of myhome.yaml imported into the entry options."""
"""Fall back to the copy of myhome.yaml imported into the entry options.

A copy with 4-digit area-0 points is moved to the canonical spelling before it
is applied, and the moved copy is stored, so the registry migration that follows
and the platforms see one and the same address (#147).
"""
imported = entry.options.get(CONF_MIGRATED_YAML_PLATFORMS)
if not isinstance(imported, dict) or not any(imported.values()):
return
imported, rewritten = canonicalise_imported_platforms(imported)
if rewritten:
hass.config_entries.async_update_entry(
entry, options={**entry.options, CONF_MIGRATED_YAML_PLATFORMS: imported}
)
LOGGER.info(
"Imported copy of myhome.yaml for gateway %s: %d point address(es) moved to the "
"canonical spelling (%s)",
entry.data.get(CONF_MAC), len(rewritten), "; ".join(rewritten),
)
_apply_yaml_platforms(_restore_shared_devices(imported), configured_platforms)
LOGGER.info(
"Using the imported copy of myhome.yaml for gateway %s: the file is missing, "
Expand Down Expand Up @@ -215,7 +285,7 @@ async def load_legacy_myhome_yaml(
if not await _load_live_yaml(
hass, entry, configured_platforms, platforms, configured_expose
):
_apply_imported_copy(entry, configured_platforms)
_apply_imported_copy(hass, entry, configured_platforms)


async def _load_live_yaml(
Expand Down
Loading
Loading