Repository navigation
v0.4.1
MyHOME Integration Release v0.4.1
Installation
HACS (recommended)
- Add this repository as a custom repository in HACS
- Install the MyHOME integration
- Restart Home Assistant
Manual installation
- Download
myhome.zipfrom the assets below - 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) - 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/closingfrom their own status frames (states 11-14); set_cover_positionto the value a moving cover was passing through did nothing and the cover ran on; it now stops there;- a full
open_cover/close_coverdid not re-calibrate: the actuator's end-stop frame froze the stale estimate instead of snapping it to0/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;
invertednow also mirrors the level of an advanced actuator, so direction flags and position agree;- a
slat_timeon anadvancedcover could make the wholemyhome.yamlfail 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: falsebroke 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 namesadvanced: trueas the missing key.
- 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
- 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: astoppedframe 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_coverthe 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 acover.set_cover_positionor 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.
- 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
- Covers, an advanced actuator whose
stoppedframe 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; iconwas documented as a common key but ignored by sensors, binary sensors, climate zones and the scenario-control event entity, andicon_onby binary sensors; they now work,icon_ongiven withouticonincluded (the entity's default icon is used while off, the Lock/Unlock buttons keep their fixed icons, and the validator knowsicon_onon a binary sensor, so it no longer reports it as an unknown key). On apowerorenergymetericonapplies to every entity of the meter, not only to the main one;entity_nameon aclass: powermeter now renames the Power entity;- in a plant with a central unit, every nameless zone was called "Central unit"; only the bare
#0is,#0#5is "Zone 5" again; - a thermo zone written with a leading zero (
zone: '01', orwhere: '01') is normalised to the form the bus uses, so its frames reach the entity. It used to be keyed4-01while every frame for that zone arrives as4-1: the climate entity was created, was available and stayedunknownfor 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 keyed4-1like its frames, is detected as a duplicate of the unpadded spelling and shares the zone's device like an unpadded probe; - a
sensorof classtemperaturewhose address resolves to zone0—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 stayedunknownfor ever while its readings were delivered to the central unit'sclimateentity — the same silent dead entity the zero-padding fix above removes. The central unit is aclimate:device (zone: "#0") and never a probe, so there is nothing to normalise these to; - a
climatezone paired with a temperaturesensoron 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'sentity_name; hvac_actionon aheat: true, cool: truezone could freeze as soon as the central unit reported actuator status (dimension 20) instead of valve status (dimension 19): atunknownwhen such a frame arrived first, and atidlefrom 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 publishidlewhile the valve was open — precisely when the valve is modulating;- an explicit
keepalive_minutesequal 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 neither3nor4) reports itsSensorattribute verbatim instead of being split at a meaningless place.
- a platform section written as a YAML list or a scalar (
- 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 aconfig_file_is_default_locationflag saying whether it sits where the integration would look for it anyway, and an entry that never set the option reported asmyhome.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
climateand a temperature probe of its own (WHERE above 99) as aclass: temperaturesensor. 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 thetemperatureproperty out ofmyhome_device_discoveredaltogether; 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, notbutton, inmyhome_device_discovered, and a device family with nomyhome.yamlsection (alarm devices) reportsplatform: nullinstead ofbinary_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 wholemyhome.yamlfail to load. Every WHO 4 frame whose WHERE is0(a plant-wide mode change, the plant temperature, the central unit's own actuator status) is now recognised as the central unit and suggested asclimate: {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:
0is the general address (every light, or every shutter, of the plant),00,1-9and100are the areas (100is area 10) and#1-#255are 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, orlight: {where: '100'}, which the configuration schema refuses outright — and one refused block does not break one device, it makes the wholemyhome.yamlfail 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 of11and keeps the interface apart, and discovery read the WHERE only. The suggested block therefore had nointerface:key, so it drove the wrong actuator (light.turn_onsent*1*1*11##) and its entity never updated; and because both devices got the same identity, a plant with an actuator11on the main bus and one at11#4#3had one of the two silently never announced and never suggested. Discovery now reads the interface where the rest of the integration reads it, writesinterface:into the suggestion, and identifies the device the wayvalidate.pydoes. Thediscovered_devicepayload ofmyhome_device_discoveredcarries the interface too (unpadded,nullon the main bus), and theunique_idof a device behind one now includes it ({mac}-1-11#4#03) — which is also the formdiscovered_devicesreports inmyhome_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, deletemyhome_discovered.yamlbefore your next run: the old file may hold two blocks for the same physical device, one without aninterface: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 intomyhome_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 moreinstead of showing ten names next to a count of eleven; - discovery: when
myhome_discovered.yamlcould not be parsed, the run kept the file untouched and wrote its suggestions to amyhome_discovered.yaml.newsibling — but the closingDiscovery 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 areas00and100did 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 inmyhome.yamlwas 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.zipbefore writing the new version intomanifest.json, then committed the bump in a commit no tag pointed at. HACS readsmanifest.jsonfrom 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.jsonand 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. Everyuses: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
ruffandpyteston every push and pull request (.github/workflows/tests.yml), with the rule set pinned inruff.tomland the test tooling pinned inrequirements_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 toactions/checkout@v5and cancel a run that a newer push has superseded. strings.jsonas 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.gitattributespinning LF line endings, so the CRLF-to-LF rewrite of the five files it caught (__init__.py,config_flow.py,manifest.json,LICENSEandrelease.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_minuteswritten inmyhome.yaml, undersensor_defaults:(aliasenergy:) 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 andentity_idof the unpadded spelling (4-1). The old entity never received a frame — it was permanentlyunknown— 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_2suffix (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 oldentity_idhas 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 1binary_sensorwith a class other thanmotion, aclimatezone withheat: falseandcool: false, and a CENscenario_controlwhosewhereis 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 windowseconds 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 attemptsinstead ofdropped 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_workersa 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_sensorwithwho: "9", not as aswitch. The switch platform only acceptswho: "1", so copying the old suggestion intomyhome.yamlblocked the whole setup. - A WHO 9 auxiliary binary sensor no longer claims to be
offbefore it has any news. The bus never answers a status request for an auxiliary channel, so there was nothing behind thatoff: the entity now startsunknownand restores its last known state across a restart or a reload. An automation triggered onoffat startup will no longer fire. - Timing keys (
shutter_run,slat_time,opening_time,closing_time) written on anadvancedcover 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 negativewhere: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 thegatewayfield of every service shows00: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_eventtrigger withpushbutton_short_release/pushbutton_long_press. - The dead
invalid_portandgateway_vanishedtranslation keys were removed (neither was reachable:invalid_porthad no assignment at all, and thegateway_vanishedbranch sat behind the form's own schema validation), and so was an unload helper that could never cancel anything (the session close already does).