Repository navigation
fix(climate): do not poll humidity (dimension 60) where the emulated firmware did not forward it #793
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(climate): do not poll humidity (dimension 60) where the emulated firmware did not forward it #793
Changes from all commits
4cbefbc
12e2634
61603fd
c1fa945
ad631e7
0541022
fe7d91f
fee4bf6
28dfe04
5c9c9e1
b0bc3d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -172,8 +172,51 @@ def command_session_default(model: str | None) -> int: | |
| "F461": {20: "*#4*#0*20##"}, | ||
| "MYHOMESERVER1": {20: "*#4*#0*20##"}, | ||
| } | ||
| # The models the two tables know, longest first so ``MH200N`` is not read as ``MH200``. | ||
| _ZONE_REQUEST_MODELS = sorted(ACTUATOR_REQUEST_REFUSED | set(ALL_ZONE_REQUESTS), key=len, reverse=True) | ||
| # Gateways whose emulated firmware did not forward the zone humidity request (``*#4*Z*60##``: | ||
| # nack and nothing on the bus) in the own-firmware-oracle thermo-zone-requests suite | ||
| # (findings/thermo-zone-requests.md on main, ``firmware_observed``, emulated firmware); F459, | ||
| # F460, F461 and MyHomeServer1 forwarded it, and the live F461 / MyHomeServer1 answer it. | ||
| # Read from the OWNd profile first (``supports_heating_humidity_request``: ``True`` / ``False`` | ||
| # wins, ``None`` = unverified); this table is the fallback until the flag exists there. | ||
| HUMIDITY_REQUEST_REFUSED = frozenset({"F450", "F453", "F453AV", "F454", "H4684", "MH200", "MH200N", "MH202"}) | ||
| # Every gateway model the request tables can be keyed on: the twelve images the oracle emulates | ||
| # (results/<P>/<V>, refusing and forwarding alike) and the other models OWNd profiles, so a | ||
| # forwarding model named with a brand prefix ("BTicino F461") is keyed as itself (``F461``) | ||
| # and not left as the whole string. The same key on every table of #792 / #793 / #794. | ||
| KNOWN_GATEWAY_MODELS = frozenset( | ||
| { | ||
| "F450", "F452", "F452V", "F453", "F453AV", "F454", "F455", "F459", "F460", "F461", | ||
| "H4684", "H4890", "MH200", "MH200N", "MH201", "MH202", "MYHOMESERVER1", | ||
| } | ||
| ) | ||
| # The models the tables know, longest first so ``MH200N`` is not read as ``MH200``. | ||
| _ZONE_REQUEST_MODELS = sorted( | ||
| KNOWN_GATEWAY_MODELS | ACTUATOR_REQUEST_REFUSED | HUMIDITY_REQUEST_REFUSED | set(ALL_ZONE_REQUESTS), | ||
| key=len, | ||
| reverse=True, | ||
| ) | ||
| # SSDP announces the H4684 as "HL4684" (manifest.json), which OWNd does not alias to its H4684 | ||
| # profile yet (it knows h4684, l4684, n4684, nt4684, lgrh4684; #794 maps the same spelling), and | ||
| # "HC4684" is the other prefixed spelling of the same product: keyed on the oracle's product name. | ||
| _MODEL_SPELLINGS = {"HL4684": "H4684", "HC4684": "H4684"} | ||
|
|
||
|
|
||
| def _alnum(name: object) -> str: | ||
| """A model string reduced to its upper-case letters and digits (``"BTicino F461"`` -> ``BTICINOF461``).""" | ||
| return "".join(character for character in str(name).upper() if character.isalnum()) | ||
|
|
||
|
|
||
| def _resolved_model(word: str) -> str | None: | ||
| """The model OWNd resolves one word of a model string to, or ``None`` for a word it does not know. | ||
|
|
||
| ``L4684`` -> ``H4684``, ``003598`` -> ``F454``, ``03565`` -> ``MH200N`` (OWNd's aliases and | ||
| catalogue item numbers); the spellings OWNd has no alias for are mapped first. | ||
| """ | ||
| key = _MODEL_SPELLINGS.get(_alnum(word), word) | ||
| profile = get_gateway_profile(key) | ||
| if isinstance(profile, GenericGatewayProfile): | ||
| return None | ||
| return _alnum(profile.model_name) | ||
|
|
||
|
|
||
| # A WHO 4 zone address: ``Z``, ``#Z``, ``Z#N`` (actuator), each optionally behind an | ||
|
|
@@ -405,15 +448,24 @@ def _model_key(self) -> str: | |
| """The gateway model the request tables are keyed on (``F461``, ``MYHOMESERVER1``). | ||
|
|
||
| A known OWNd profile names the model; a ``GenericGatewayProfile`` (also what | ||
| ``get_gateway_profile("BTicino F461")`` returns) falls back to the configured name, | ||
| in which a known model is looked up as a substring (``supports_zone_status`` style). | ||
| ``get_gateway_profile("BTicino F461")`` returns) falls back to the configured name. | ||
| There a brand-prefixed alias or catalogue number is resolved word by word through | ||
| OWNd (``"BTicino L4684"`` -> ``H4684``, ``"Legrand 003598"`` -> ``F454``), the SSDP | ||
| spelling ``HL4684`` is read as ``H4684``, and otherwise a known model is looked up as | ||
| a substring, longest first (``supports_zone_status`` style), so ``MH200N`` stays | ||
| distinct from ``MH200``. The words are the alphanumeric runs of the string, so | ||
| ``"BTicino-L4684"`` and ``"F454 (003598)"`` resolve too. | ||
| """ | ||
| profile = getattr(self.gateway, "profile", None) | ||
| if profile is not None and not isinstance(profile, GenericGatewayProfile): | ||
| name = getattr(profile, "model_name", None) or self.model | ||
| else: | ||
| name = self.model | ||
| normalized = "".join(character for character in str(name).upper() if character.isalnum()) | ||
| for word in re.findall(r"[A-Za-z0-9]+", str(name)): | ||
| resolved = _resolved_model(word) | ||
| if resolved is not None: | ||
| return resolved | ||
| normalized = _MODEL_SPELLINGS.get(_alnum(name), _alnum(name)) | ||
| return next((model for model in _ZONE_REQUEST_MODELS if model in normalized), normalized) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Please normalize |
||
|
|
||
| def _who4_target(self) -> MyHOMEGatewayHandler: | ||
|
|
@@ -517,6 +569,23 @@ async def _send_zone_requests(self) -> None: | |
| except Exception: # a transport error must not end the task silently (#593) | ||
| LOGGER.exception("%s Zone requests %s could not be sent", self.log_id, frames) | ||
|
|
||
| @property | ||
| def humidity_request_refused(self) -> bool: | ||
| """Whether this gateway drops humidity requests (``*#4*Z*60##``) instead of forwarding them. | ||
|
|
||
| The request goes where ``send_status_request`` sends it: a secondary gateway WHO 4 is | ||
| delegated to, or the warm standby while this primary is down, decides with its own | ||
| profile and table. The OWNd profile flag wins when it exists; otherwise the | ||
| oracle-derived table. | ||
| """ | ||
| target = self._who4_target() | ||
| if target is not self: | ||
| return target.humidity_request_refused | ||
| supported = getattr(getattr(self.gateway, "profile", None), "supports_heating_humidity_request", None) | ||
| if isinstance(supported, bool): | ||
| return not supported | ||
| return self._model_key in HUMIDITY_REQUEST_REFUSED | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If a primary Please route this through the same target = self._who4_target()
if target is not self:
return target.humidity_request_refusedand add test coverage for both delegation and warm-standby failover directions. |
||
|
|
||
| @property | ||
| def profile(self) -> Any: | ||
| """Return gateway profile.""" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#792(2ece4744): Rebase ontov2-phase1-architectureand union_ZONE_REQUEST_MODELSacrossKNOWN_GATEWAY_MODELS | ACTUATOR_REQUEST_REFUSED | HUMIDITY_REQUEST_REFUSED | set(ALL_ZONE_REQUESTS)while keeping one shared_model_keyand_who4_target().thermo-zone-requests.tsvrows +results/mcp_index.json(OpenWebNet-HA/own-firmware-oracle@d3767f49) are still onoracle/update-suitesrather thanmain. Please get the suites update merged toown-firmware-oraclemainsoHUMIDITY_REQUEST_REFUSEDis backed by published oracle TSVs rather than prose-only findings.