Skip to content

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 07 Sep 22:07
· 366 commits to master since this release

MyHOME Integration Release v0.4.1

Installation

HACS (recommended)

  1. Add this repository as a custom repository in HACS
  2. Install the MyHOME integration
  3. Restart Home Assistant

Manual installation

  1. Download myhome.zip from the assets below
  2. Extract it into custom_components/myhome/ in your Home Assistant
    configuration directory (the archive holds the integration's files
    themselves, with no top-level folder)
  3. Restart Home Assistant

Changelog

A stabilisation release: no new feature and no new configuration key. Six rounds of independent code review (each round re-reviewing the fixes of the previous one, with every fix pinned by a test that fails without it) went over the gateway session, the cover model, the platforms, the validator, discovery, the flows and the docs. The suite runs in CI on every push. Read Changed before upgrading: a few configurations that could never work are now refused at load, and one entity-identity effect is listed there.

Fixed

  • Covers, found by the 2026-09-07 code review (all reproduced with tests):
    • an advanced actuator's real position could be overwritten by the time-based estimate after a plain "opening"/"closing" frame; advanced covers now only track the direction from those frames, and they finally report opening / closing from their own status frames (states 11-14);
    • set_cover_position to the value a moving cover was passing through did nothing and the cover ran on; it now stops there;
    • a full open_cover / close_cover did not re-calibrate: the actuator's end-stop frame froze the stale estimate instead of snapping it to 0 / 100. It snaps now, once three quarters of the expected run have elapsed; earlier stops are still honoured as real stops;
    • the last position is restored before the first status request instead of racing it;
    • inverted now also mirrors the level of an advanced actuator, so direction flags and position agree;
    • a slat_time on an advanced cover could make the whole myhome.yaml fail to load. The key does nothing on an advanced actuator (it has no tilt phase), but it was still cross-checked against the run times, and that check ran before the warning that explains the key is inert there;
    • an actuator that reports its own position but was left at the default advanced: false broke its own entity on the first such frame: the timed estimate was stopped, the direction was set again without restarting it, and the cover read Opening at a percentage that never moved for the best part of two minutes, with nothing in the log to explain it — and no later frame could repair it. Position frames are now ignored on a cover declared basic, the estimate keeps running, and the debug log names advanced: true as the missing key.
  • Covers, the frames we cannot tell apart from our own commands:
    • after a timed run short enough to finish before the gateway's late copy of our movement command arrives (a small tilt target on a short slat_time), that copy could start a phantom run to the end stop. Only a frame that can actually be the gateway echoing our own command is ignored now: a stopped frame after a movement we commanded, or a copy of the movement our own stop interrupted. A movement in any other direction is honoured immediately, a press in the same direction is recovered by a status re-read about two seconds later, and a stop Home Assistant could not even send — its command queue was full, or the connection was closing — now changes nothing at all, neither the repeat window nor the estimated position, on both of the two paths that send a stop. Without this a shutter driven from the wall could run fully open while Home Assistant reported it closed, until the next command from Home Assistant;
    • a cover.stop_cover the gateway could not take — its command queue was full, or the connection was closing — used to freeze the position half way and keep reporting it for good, because the actuator's own stop frame at the end of the physical run re-froze the same stale value. The shutter goes on running in that case, so the estimate now goes on running with it. The stop that ends a cover.set_cover_position or a tilt run follows the same rule, and had one failure of its own: it used to leave the position sitting on the target the shutter never reached, while the shutter itself ran on to the end stop. That run is now carried on to the end stop in the model too, so the actuator's own frame at the end of it puts the position back in step instead of confirming a wrong one. That now holds for short runs as well: a run that finished before the gateway repeated the command which started it used to be ended by that repeat, so a small tilt or a nudge of the position slider still froze on a value the shutter had already left behind. Keeping that repeat window has a cost of its own, and it is now paid: a real stop arriving in the same second and a half — a keypad press, an obstacle — cannot be told apart from the repeat either, so it is still ignored, but the actuator is then asked what it is doing and its answer ends the run about two seconds late. Before, such a stop was swallowed with no follow-up at all and the shutter was published as fully open while it stood still half way.
  • Covers, an advanced actuator whose stopped frame is lost: it no longer stays Opening / Closing for ever. After the longest configured travel time plus 30 s the actuator's status is re-read, and the direction is dropped only if nothing answers within the time a single command may really take. That is not the Command timeout option alone: the request may have to re-open a connection to the gateway first — there is one command connection per gateway, shared by everything Home Assistant sends to it, and it is closed after a minute in which nothing at all was sent — and the whole attempt is retried once, so the wait is twice the connection timeout plus twice the command timeout plus a two-second margin — about 42 seconds with the defaults — and it grows with the Command timeout option. An actuator whose real run is longer than that timer is therefore not reported as closed (or open) in the middle of it, waking every automation watching for it. An ordinary scene — a dozen commands acknowledged in well under a second — is comfortably inside that. What is not covered is a queue whose backlog outlasts the wait: queued commands are dropped only after the Command queue TTL option, sixty seconds by default, and holding Opening for a whole minute after a genuinely lost frame would be worse than the problem. The reported position is never estimated.
  • Sensors, binary sensors and climate, found by the same review:
    • a platform section written as a YAML list or a scalar (light: [...]) is reported as a normal validation error with its key path instead of crashing the setup with a traceback;
    • icon was documented as a common key but ignored by sensors, binary sensors, climate zones and the scenario-control event entity, and icon_on by binary sensors; they now work, icon_on given without icon included (the entity's default icon is used while off, the Lock/Unlock buttons keep their fixed icons, and the validator knows icon_on on a binary sensor, so it no longer reports it as an unknown key). On a power or energy meter icon applies to every entity of the meter, not only to the main one;
    • entity_name on a class: power meter now renames the Power entity;
    • in a plant with a central unit, every nameless zone was called "Central unit"; only the bare #0 is, #0#5 is "Zone 5" again;
    • a thermo zone written with a leading zero (zone: '01', or where: '01') is normalised to the form the bus uses, so its frames reach the entity. It used to be keyed 4-01 while every frame for that zone arrives as 4-1: the climate entity was created, was available and stayed unknown for ever, with nothing in the log but a debug line. The same mismatch hid a duplicate zone from the duplicate check and stopped a zone from sharing its device with the WHO 4 temperature probe on the same zone. A WHO 4 temperature sensor written the same way (where: '01') had the identical defect and is normalised with it: it is keyed 4-1 like its frames, is detected as a duplicate of the unpadded spelling and shares the zone's device like an unpadded probe;
    • a sensor of class temperature whose address resolves to zone 0 — where: '0', '00', '100', '200'…'900', '1000' — is now refused with the key path of the device instead of loading. The bus reports every one of those frames under the central unit's key (4-#0), so the entity was created, was named, was available and stayed unknown for ever while its readings were delivered to the central unit's climate entity — the same silent dead entity the zero-padding fix above removes. The central unit is a climate: device (zone: "#0") and never a probe, so there is nothing to normalise these to;
    • a climate zone paired with a temperature sensor on the same zone lost the zone's name to the probe: the shared device keeps the climate name and the probe name becomes the sensor's entity_name;
    • hvac_action on a heat: true, cool: true zone could freeze as soon as the central unit reported actuator status (dimension 20) instead of valve status (dimension 19): at unknown when such a frame arrived first, and at idle from the first actuator off/on pair onwards — the ordinary duty cycle of a thermostat. Only a frame that actually carries a direction, or one on a heat-only / cool-only zone, or one reporting "not active", now settles the attribute; otherwise it is still derived from the temperature. On a plant that reports the valve status too, an actuator frame that only says "on" no longer overrules the direction the valve frame had just reported, which used to publish idle while the valve was open — precisely when the valve is modulating;
    • an explicit keepalive_minutes equal to the built-in default (125) was overridden by the Default instant-power keep-alive option; the file value now always wins;
    • a non-numeric Default instant-power keep-alive option, only reachable through a hand-edited entry, took the whole sensor platform down, the four gateway diagnostic entities included; it now falls back to the configured value with a warning, like the other tunables;
    • a WHO 1 illuminance sensor behind an F422 interface never received its interface and matched no reply;
    • a dimmer switched on with transition: kept a stale brightness;
    • the sensor refresh tasks are cancelled when the entity is removed, so a pending one cannot outlive a config entry reload;
    • the energy throttle resolves a sensor key whichever way its bus interface is spelled (31#4#3 / 31#4#03), like the frame dispatcher;
    • a WHO 25 dry contact whose WHERE does not have the <type><number> shape (it starts with neither 3 nor 4) reports its Sensor attribute verbatim instead of being split at a meaningless place.
  • Device triggers, blueprints, flows and diagnostics:
    • a device trigger on a device that is not a scenario control (a light, a cover, the gateway) is now refused with a readable error; it used to be accepted and the automation showed as on without ever firing. The shipped blueprints' device picker now only offers scenario controls;
    • the per-device diagnostics download no longer contains the device's name / entity_name, the HMAC password frame is redacted like the other negotiation frames, and the configuration file is reported by name only, never with its directory — with a config_file_is_default_location flag saying whether it sits where the integration would look for it anyway, and an entry that never set the option reported as myhome.yaml, in the default location;
    • the options flow no longer turns a gateway configured without a password into one with an empty password (which OWNd reports as "invalid password" instead of asking for one);
    • discovery: a thermoregulation zone is suggested as climate and a temperature probe of its own (WHERE above 99) as a class: temperature sensor. Before, the classifier tested an attribute OWNd does not have, so every probe became a zone. A probe wired as a zone's main sensor is indistinguishable from the zone on the bus and is reported as a zone; the first WHO 4 frame decides and is never revised. The same wrong attribute kept the temperature property out of myhome_device_discovered altogether; a WHO 4 device now really publishes its reading;
    • discovery: the WHO 25 "scan" entry never left the machine (OWNd cannot build a general dry-contact status request, and there is none on the bus); the entry is gone and the docs now say that dry contacts and keypads are seen only when they emit a frame during the run;
    • discovery: scenario controls report platform: event, not button, in myhome_device_discovered, and a device family with no myhome.yaml section (alarm devices) reports platform: null instead of binary_sensor, which the schema rejects;
    • discovery suggested a thermoregulation central unit as climate: {zone: '0'}, a value the configuration schema refuses — pasting it did not break one device, it made the whole myhome.yaml fail to load. Every WHO 4 frame whose WHERE is 0 (a plant-wide mode change, the plant temperature, the central unit's own actuator status) is now recognised as the central unit and suggested as climate: {zone: '#0'}, the form the schema accepts;
    • discovery treated the plant-wide addresses of a lighting or automation frame as if they were devices. On WHO 1 and WHO 2 the bus spells them in the WHERE: 0 is the general address (every light, or every shutter, of the plant), 00, 1-9 and 100 are the areas (100 is area 10) and #1-#255 are the groups; only the group form was dropped. Any plant-wide or area button press during a run therefore produced a suggested device: light: {where: '0'}, an entity whose "turn on" switches on every lamp in the house and that can never show a state, or light: {where: '100'}, which the configuration schema refuses outright — and one refused block does not break one device, it makes the whole myhome.yaml fail to load, so every device of that gateway disappears. These frames are exactly the ones the gateway already refuses to hand to any entity; both now ask the same question;
    • a device on a private riser behind an F422 local bus interface was discovered as the main-bus device with the same address: the library reports *1*1*11#4#3## with a WHERE of 11 and keeps the interface apart, and discovery read the WHERE only. The suggested block therefore had no interface: key, so it drove the wrong actuator (light.turn_on sent *1*1*11##) and its entity never updated; and because both devices got the same identity, a plant with an actuator 11 on the main bus and one at 11#4#3 had one of the two silently never announced and never suggested. Discovery now reads the interface where the rest of the integration reads it, writes interface: into the suggestion, and identifies the device the way validate.py does. The discovered_device payload of myhome_device_discovered carries the interface too (unpadded, null on the main bus), and the unique_id of a device behind one now includes it ({mac}-1-11#4#03) — which is also the form discovered_devices reports in myhome_discovery_completed. A frame carrying an F422 interface outside the 0-15 range (…#4#16) is now ignored rather than announced as the main-bus device with the same WHERE: such a frame should not exist on real hardware, and no suggestion is better than one that drives the wrong actuator. If you ran discovery before this version and your plant has an F422 local bus interface, delete myhome_discovered.yaml before your next run: the old file may hold two blocks for the same physical device, one without an interface: key, written by the earlier version, which is wrong, and one with it, which is right. That file is only ever added to, never cleaned up, so both survive and both are accepted by the schema. Nothing is lost by deleting it — a run rewrites it;
    • discovery: the line a run logs about devices it could not suggest counted every previous run as well — three runs reported "3 device(s)" for one keypad. It now reports what that run saw, and in two clauses rather than one: a CEN / CEN+ scenario control really can be declared by hand, under scenario_control:, while a burglar-alarm device belongs to a family this integration has no section for anywhere. The old line sent a reader hunting for an alarm chapter that has never existed. It no longer names a CEN / CEN+ keypad you have already declared, either: such a control can never be written into myhome_discovered.yaml, and it was reported as one to "declare by hand" on every run, months after it was configured — it is now looked up under the key it is really stored by (cenplus-<object> / cen-<where>). Each device is named by its full bus address, interface included (bus_cen_scenario_control@11#4#3), so a keypad on the main bus and one on a private riser are no longer the same name twice; and a list cut at ten names ends with , ... and N more instead of showing ten names next to a count of eleven;
    • discovery: when myhome_discovered.yaml could not be parsed, the run kept the file untouched and wrote its suggestions to a myhome_discovered.yaml.new sibling — but the closing Discovery finished: N suggestion(s) (M new) written to … line still named the original file, contradicting the warning just above it and sending the user to a file that had not changed. That line now names the file the suggestions were actually written to.
  • Gateway, sessions and setup, found by the same review:
    • one unexpected exception inside the command sending loop used to kill the command path for the life of the process: the loop now survives it, the command is dropped and counted, and the worker backs off;
    • a command session whose open() was cancelled while the worker was being shut down leaked the socket instead of closing it — gateways only hold a handful of concurrent sessions;
    • a crash inside OWNd's session negotiation escaped as an unhandled exception; it is now a reconnectable session error like every other connection failure;
    • a command lost to an authentication failure was not counted: it now shows up in the Commands dropped diagnostic entity like the other lost commands;
    • the rate-limited log keys are bounded, and a NACK line is keyed by WHO/WHERE instead of by the whole frame, so a busy plant could no longer grow that table one entry per distinct frame;
    • an area lighting frame (*1*0*3##) re-requested the status of the decoded area number instead of the WHERE the frame carried, so *#1*10## asked actuator A=1 PL=0 rather than area 10, and the lights of areas 00 and 100 did not follow a physical area button;
    • the idle watchdog no longer arms itself when its probe could not even be queued (a full or closed command queue): reconnecting the monitor cannot help, so it simply retries on the next poll;
    • a platform that refuses to unload is now logged as an error instead of passing unnoticed; the sockets are already closed at that point, so the entry has to be reloaded.
  • A key called platforms: written at gateway level in myhome.yaml was reported as unknown and ignored, and was not ignored: it replaced the integration's own list of platforms, so every light, cover, sensor and button of that gateway failed to be created and the integration entry ended in Failed to set up, with only 'str' object has no attribute 'get' in the log to go on. The key is now genuinely ignored, and a platform list of the wrong shape can no longer take a gateway down.
  • The release job tagged and packaged the previous version number: it created the tag and built myhome.zip before writing the new version into manifest.json, then committed the bump in a commit no tag pointed at. HACS reads manifest.json from the tag, so a release made with that job would have installed one version while Home Assistant reported the one before it. The bump now runs first, is asserted and committed, and the tag and the zip cover it. No shipped release is affected: every existing tag was made by hand with the bump already committed, so nothing needs re-installing.
  • The release job's "tag already exists" guard asked only the local checkout, so on a checkout without the tags it would have bumped manifest.json and pushed that commit to the branch before dying on the tag push. The guard now asks the remote and keeps the local check for a tag made but never pushed. Every uses: of the job must also name a version tag or a commit SHA, which the suite checks, so a job holding the repository token can never run a third party's moving branch.

Added

  • CI runs ruff and pytest on every push and pull request (.github/workflows/tests.yml), with the rule set pinned in ruff.toml and the test tooling pinned in requirements_test.txt; the suite used to run only on a developer's machine. It now also covers the gateway failure surface (refused commands, general/area/group WHO 2 frames, the complete CEN/CEN+ press table, the idle-probe window, the listening loop's catch-all, the reconnect backoff cap), the session error paths, service-call refusals against their own translation key, the translation files, discovery and the release job. The two older workflows (hassfest.yml, validate.yml) moved to actions/checkout@v5 and cancel a run that a newer push has superseded.
  • strings.json as the source of the translations.
  • requirements_test.txt (the one dependency list CI and the development docs install), scripts/release_notes.py (the CHANGELOG section of a version, unwrapped for the GitHub release page) and a .gitattributes pinning LF line endings, so the CRLF-to-LF rewrite of the five files it caught (__init__.py, config_flow.py, manifest.json, LICENSE and release.yml) cannot happen again file by file.

Changed

  • The Probe window option description now says what the watchdog does: the connection is rebuilt only when nothing arrived on the monitor session and the gateway acknowledged no status request — the probe or any other — on the command session. The Default instant-power keep-alive description says which values it gives way to: any keepalive_minutes written in myhome.yaml, under sensor_defaults: (alias energy:) as much as on the sensor itself.
  • A climate zone or a temperature sensor written with a zero-padded address (zone: '01', where: '01') now gets the device key, unique_id, device and entity_id of the unpadded spelling (4-1). The old entity never received a frame — it was permanently unknown — so nothing that worked is renamed; the old, empty entity and device are pruned on the first load. The new entity is created before that prune, so it takes the old id with a _2 suffix (climate.my_zone → climate.my_zone_2); the freed id can be given back to it from Settings → Devices & services → Entities. Either way, an automation that referenced the old entity_id has to be pointed at the new one. If the file happens to contain both spellings of the same zone — a padded entry written first and an unpadded one added later, which is what debugging the old bug produced — they are now the same device, so the duplicate check refuses the file and the whole gateway does not load (every entity of that gateway, not only the two sensors) until one of the two entries is removed. The message names both YAML keys and both spellings as written.
  • Binary sensors are named after their device ("Window Contact", not "Window Contact Window"): they are now the main entity of their device like every other platform. Entity ids and history are unaffected; only the displayed name changes.
  • Four configurations that used to load are now rejected with the key path of the offending device, because they could never work: interface: on a WHO 4/9/18/25 sensor (their frames never carry it), a WHO 1 binary_sensor with a class other than motion, a climate zone with heat: false and cool: false, and a CEN scenario_control whose where is outside 1-2047.
  • A CEN scenario control used to accept trigger types only CEN+ can fire (rotate_cw_slow, pushbutton_long_press_repeat); such a trigger now fails validation. It never fired; it used to fail quietly.
  • A device trigger whose button number is outside the protocol's range (CEN+ buttons are 1-32, CEN buttons are 0-31) is now refused when the automation loads, like a wrong event type. It used to validate and never fire.
  • The idle watchdog no longer reconnects a gateway that answered the probe. It used to close and rebuild the event session whenever the probe produced nothing on the monitor within Probe window seconds. Some gateways answer on the command session without mirroring the reply onto the monitor, and those were reconnected every idle watchdog + probe window seconds for no reason: an ACK on the command port now re-arms the watchdog instead (it need not be the probe's own ACK), and only silence on the monitor together with no status-request ACK on the command session reconnects. The monitor socket itself is still guarded by TCP keepalive. The log line says exactly what was checked, each half with the window it was measured over — "nothing on the monitor for N s and no status request acknowledged on the command session in the last M s", N being the monitor's silence and M the time since the probe went out — at least the Probe window option, rounded up to the next wake-up of the watchdog — instead of claiming the gateway answered nothing while it was demonstrably ACKing the user's lights, or letting one trailing duration read as if it qualified both halves.
  • The warning for a command the gateway would not take now reads dropped after 2 attempts instead of dropped after two attempts: the count is formatted from the constant that bounds the retry loop, so raising that constant can no longer make the message lie. A log filter matching the old wording has to be updated.
  • The Number of concurrent command sessions option is capped at 4 (gateways hold only a handful of concurrent sessions), and the options form now offers 1-4 rather than 1-10, explained in the dialog itself (Default 1 (1-4)). An entry saved with more than 4 opens 4; the form opens on that clamped number — the one the integration is actually running — and the first save writes it down, and the diagnostics download reports the same clamped number, so it agrees with handler.sending_workers a few lines below it. A non-numeric value left by a hand-edited entry falls back to 1 with a warning instead of failing the setup.
  • Discovery suggests a WHO 9 auxiliary channel as a binary_sensor with who: "9", not as a switch. The switch platform only accepts who: "1", so copying the old suggestion into myhome.yaml blocked the whole setup.
  • A WHO 9 auxiliary binary sensor no longer claims to be off before it has any news. The bus never answers a status request for an auxiliary channel, so there was nothing behind that off: the entity now starts unknown and restores its last known state across a restart or a reload. An automation triggered on off at startup will no longer fire.
  • Timing keys (shutter_run, slat_time, opening_time, closing_time) written on an advanced cover now come with a warning that names only the keys actually written in the file, and says of each one whether it bounds the direction safety timer or does nothing at all. The warning used to call them all "ignored", which stopped being true when that timer landed.
  • The error for an unquoted where: now echoes the value you actually wrote instead of an actuator address you never typed, and no longer promises that quoting it will make it valid — on a light, a switch or a cover a 3- or 5-digit address is refused either way. A negative where: gets its own message instead of being told to quote itself.
  • The diagnostics download no longer publishes the config entry's title, which is reported as **REDACTED**. It defaults to " Gateway", but Home Assistant lets a user rename an entry from the integrations page, and a renamed gateway commonly carries a household, street or family name — and this is the one file users are told to attach to a public issue. The gateway model is still reported, from the entry data.
  • The placeholder addresses the UI shows are neutral examples: the manual gateway form suggests 192.168.1.35, and the gateway field of every service shows 00:03:50:AA:BB:CC.
  • The two shipped CEN+ blueprints now say what happens if a CEN control is picked in their device selector (a CEN keypad sends the short press at the start of every press, so a hold acts twice) and point CEN users at the myhome_cen_event trigger with pushbutton_short_release / pushbutton_long_press.
  • The dead invalid_port and gateway_vanished translation keys were removed (neither was reachable: invalid_port had no assignment at all, and the gateway_vanished branch sat behind the form's own schema validation), and so was an unload helper that could never cancel anything (the session close already does).