diff --git a/README.md b/README.md index 1f63cc6e1..996df89f4 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ We now maintain a comprehensive, community-curated **[GitHub Wiki](https://githu | **MH200** *(Legacy)* | OpenWebNet | 1 worker | 150 ms | ✅ SSDP | Strict single-session pacing; watchdog hardened | | **H4890 / AM4890** | OpenWebNet | 1 worker | 50 ms | ✅ SSDP | 3.5" Touch screen display IP gateway (Axolute / Livinglight) | | **F452 / F453AV** | OpenWebNet | 1 worker | 150 ms | ✅ Port 49153 | Audio/video & web server gateway | -| **HL4684** | OpenWebNet | 1 worker | 50 ms | ✅ SSDP | 10" Touch screen display IP gateway | +| **H4684 / H/L4684** | OpenWebNet | 1 worker | 50 ms | ✅ SSDP | Colour touch screen IP gateway (Axolute / LivingLight) | | **Legrand 3578** | OpenWebNet (Serial) | 1 worker | 50 ms | ❌ Manual (Serial) | USB / Serial gateway & OpenZigBee interface | @@ -98,19 +98,19 @@ We now maintain a comprehensive, community-curated **[GitHub Wiki](https://githu ### 📊 Hardware Trace Availability Matrix -| Gateway Model | WHO 0
Scenario | WHO 1
Lights | WHO 2
Autom. | WHO 4
Climate | WHO 5
Alarm | WHO 9
Power | WHO 13
Gateway | WHO 14
Lock | WHO 15
CEN | WHO 16
Audio | WHO 17
Scenario | WHO 18
Energy | WHO 22
Audio Diff. | WHO 25
Diag | WHO 1001
Diag | WHO 1013
Diag | WHO 1022
Diag | -| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| **F453AV** | | ✅ | ✅ | ✅ | ✅ | | ✅ | | | ✅ | | ✅ | | | ✅ | | | -| **F454** | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | | ✅ | | ✅ | | -| **F455** | | ✅ | ✅ | ✅ | ✅ | | ✅ | | | ✅ | | ✅ | | | | ✅ | | -| **F461** | | ✅ | ✅ | ✅ | ✅ | | ✅ | ✅ | | ✅ | | ✅ | | | | ✅ | | -| **H4684** | | | | | | | ✅ | | | | | | | | | ✅ | | -| **H4890 / AM4890** | | ✅ | ✅ | ✅ | ✅ | ✅ | | | | ✅ | | ✅ | ✅ | ✅ | | | | -| **MH200** | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | ✅ | ✅ | | | | ✅ | ✅ | | -| **MH200N** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| **MH201** | ✅ | ✅ | ✅ | ✅ | | | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | | | | -| **MH202** | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | ✅ | ✅ | | ✅ | | -| **MyHomeServer1** | | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | +| Gateway Model | WHO 0
Scenario | WHO 1
Lights | WHO 2
Autom. | WHO 3
Load | WHO 4
Climate | WHO 5
Alarm | WHO 9
Power | WHO 13
Gateway | WHO 14
Lock | WHO 15
CEN | WHO 16
Audio | WHO 17
Scenario | WHO 18
Energy | WHO 22
Audio Diff. | WHO 25
Diag | WHO 1001
Diag | WHO 1013
Diag | WHO 1022
Diag | +| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| **F453AV** | | ✅ | ✅ | | ✅ | ✅ | | ✅ | | | ✅ | | ✅ | | | ✅ | | | +| **F454** | | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | | ✅ | | ✅ | | +| **F455** | | ✅ | ✅ | | ✅ | ✅ | | ✅ | | | ✅ | | ✅ | | | | ✅ | | +| **F461** | | ✅ | ✅ | | ✅ | ✅ | | ✅ | ✅ | | ✅ | | ✅ | | | | ✅ | | +| **H4684 / H/L4684** | | ✅ | ✅ | ✅ | ✅ | | | ✅ | | | ✅ | | ✅ | ✅ | | | ✅ | | +| **H4890 / AM4890** | | ✅ | ✅ | | ✅ | ✅ | ✅ | | | | ✅ | | ✅ | ✅ | ✅ | | | | +| **MH200** | | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | | | ✅ | ✅ | | | | ✅ | ✅ | | +| **MH200N** | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **MH201** | ✅ | ✅ | ✅ | | ✅ | | | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | | | | +| **MH202** | | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | ✅ | ✅ | | ✅ | | +| **MyHomeServer1** | | ✅ | ✅ | | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | *Checkmarks (✅) indicate that at least one `diagnostic_summary.json` or `.txt` bus capture in our test corpus contains frames for that subsystem from the specified gateway model. This matrix is automatically updated from the fixtures repository.* diff --git a/custom_components/myhome/climate.py b/custom_components/myhome/climate.py index a7e8700ec..35b037a4d 100644 --- a/custom_components/myhome/climate.py +++ b/custom_components/myhome/climate.py @@ -368,6 +368,7 @@ def __init__( # Fan mode support (fancoil 3-speed + auto) self._fan: bool = False + self._fan_configured: bool = bool(fan and not self._central) self._attr_fan_mode: str | None = None self._attr_fan_modes: list[str] | None = None self._running_fan_speed: str | None = None @@ -473,6 +474,18 @@ def _enable_fan_mode(self) -> None: if self._attr_fan_mode is None: self._attr_fan_mode = "auto" + def _has_no_fan_coil_sides(self) -> bool: + """Whether dimension 19 reported that neither side of this zone is a fan coil.""" + return bool(self._fan_coil_sides) and not any(self._fan_coil_sides.values()) + + def _disable_fan_mode(self) -> None: + """Retract dynamically enabled fan mode once dimension 19 proves a pure-valve zone.""" + self._fan = False + self._attr_supported_features &= ~ClimateEntityFeature.FAN_MODE + self._attr_fan_modes = None + self._attr_fan_mode = None + self._running_fan_speed = None + def _enable_humidity(self) -> None: """Dynamically enable humidity support if not already enabled.""" if self._central: @@ -573,9 +586,10 @@ async def async_restore_last_state(self, last_state: State | None) -> None: "heating": bool(last_state.attributes.get("fan_coil_heating", False)), "cooling": bool(last_state.attributes.get("fan_coil_cooling", False)), } - if "fan_mode" in last_state.attributes or bool( - last_state.attributes.get("supported_features", 0) & ClimateEntityFeature.FAN_MODE - ): + if ( + "fan_mode" in last_state.attributes + or bool(last_state.attributes.get("supported_features", 0) & ClimateEntityFeature.FAN_MODE) + ) and not (self._has_no_fan_coil_sides() and not self._fan_configured): self._enable_fan_mode() restored_fan_mode = last_state.attributes.get("fan_mode") if restored_fan_mode in ("auto", "low", "medium", "high"): @@ -1379,10 +1393,18 @@ def handle_event(self, message: OWNHeatingEvent) -> None: } self._fan_coil_sides_asked_at = None self._fan_coil_sides_held = False - # Actuator status (Dimension 20) with values >= 5 reports fancoil fan status in OWNd. - is_fan = isinstance(getattr(message, "fan_speed", None), int) or isinstance( - getattr(message, "fan_on", None), bool - ) + if self._has_no_fan_coil_sides() and not self._fan_configured: + # Pure-valve actuator channels can report value 5 on dimension 20 (issue #466 H/L4684 + # zones 11..88) while dimension 19 carries 0..4 on both sides once _ask_fan_coil_sides() + # (*#4*Z*19##) is answered (myhomeserver1_live_2026-10-25 zones 35..70, issue_674 F455): + # retract the tentative fan_mode once dimension 19 proves neither side is a fan coil. + self._disable_fan_mode() + # Actuator status (Dimension 20) with values >= 5 reports fancoil fan status in OWNd, + # unless dimension 19 already proved neither side of an unconfigured zone is a fan coil. + is_fan = ( + isinstance(getattr(message, "fan_speed", None), int) + or isinstance(getattr(message, "fan_on", None), bool) + ) and not (self._has_no_fan_coil_sides() and not self._fan_configured) fan_enabled_now = is_fan and not self._fan if is_fan: self._enable_fan_mode() diff --git a/custom_components/myhome/const.py b/custom_components/myhome/const.py index dae81a40a..73d208d09 100644 --- a/custom_components/myhome/const.py +++ b/custom_components/myhome/const.py @@ -322,6 +322,7 @@ def percent_to_eight_bits(value: int) -> int: "AM4890", "H4890", "LN4890", + "H4684", "Generic", ] @@ -353,7 +354,11 @@ def percent_to_eight_bits(value: int) -> int: # Shared across modern gateway families (not all Linux-based, e.g. MH201), # so it identifies none of them (see WHO13_SHARED_DEVICE_TYPES). # It contradicts legacy gateways (e.g. MH200/F452), but only as field evidence. +# 23: Confirmed on physical BTicino H/L4684 touchscreen gateway (issue #466 +# comment 6398442412, firmware 6.0.11, kernel 2.6.2, distro 6.0.0; UPnP +# modelName `H/L4684`, distinct from 2006 spec code `13` on FW 2.0.54). WHO13_OBSERVED_DEVICE_TYPES: dict[str, tuple[str, ...]] = { + "23": ("H4684", "H/L4684", "L4684"), "200": ("F454", "MyHomeServer1", "MH202", "F461", "H4890", "MH201"), } # Codes from an independent implementation: the `device` table of Nmap's @@ -380,7 +385,6 @@ def percent_to_eight_bits(value: int) -> int: "12": ("F453AV",), "15": ("F427",), # Nmap: "F427 (Gateway Open-KNX)" "16": ("F453",), - "23": ("H4684",), # a second code for the model the 2006 table gives as 13 "27": ("L4686SDK",), "44": ("MH200N",), "51": ("F454",), @@ -487,7 +491,7 @@ def gateway_model_family(model: str | None) -> str: """ if not model: return "" - name = str(model).strip().upper().replace(" ", "").replace("-", "").replace("_", "") + name = str(model).strip().upper().replace(" ", "").replace("-", "").replace("_", "").replace("/", "") m = re.match(r"^([A-Z]+\d+)[A-Z]*$", name) return m.group(1) if m else name diff --git a/custom_components/myhome/diagnostics.py b/custom_components/myhome/diagnostics.py index 39f7154ae..212697fd8 100644 --- a/custom_components/myhome/diagnostics.py +++ b/custom_components/myhome/diagnostics.py @@ -12,6 +12,7 @@ CONF_DECODER_COMPANION, CONF_DECODER_ENTITY, CONF_DECODER_SLOTS, + CONF_FILE_PATH, CONF_PRIMARY_GATEWAY, DOMAIN, INTEGRATION_VERSION, @@ -53,6 +54,7 @@ "ssdp_location", "friendly_name", "file_path", + CONF_FILE_PATH, CONF_PRIMARY_GATEWAY, "primary_mac", "secondary_mac", diff --git a/custom_components/myhome/gateway.py b/custom_components/myhome/gateway.py index 01ddf252a..d04e0b31c 100644 --- a/custom_components/myhome/gateway.py +++ b/custom_components/myhome/gateway.py @@ -124,17 +124,55 @@ ] +def _call_get_gateway_profile( + model: str | None, firmware: str | None = None, *, lookup: Any = None +) -> Any: + """Call ``get_gateway_profile`` with optional firmware, tolerating 1-arg mocks.""" + fn = lookup if lookup is not None else get_gateway_profile + if firmware: + try: + return fn(model, firmware) + except TypeError: # pragma: no cover - fallback for 1-arg mocks or older OWNd + return fn(model) + return fn(model) + + +def _profile_for_model( + model: str | None, firmware: str | None = None, *, lookup: Any = None +) -> Any: + """Resolve ``model`` to an OWNd profile, mapping SSDP spellings like ``H/L4684``.""" + profile = _call_get_gateway_profile(model, firmware, lookup=lookup) + if isinstance(profile, GenericGatewayProfile) and model: + mapped = _MODEL_SPELLINGS.get(_alnum(model)) + if mapped is not None: + return _call_get_gateway_profile(mapped, firmware, lookup=lookup) + return profile + + +def _firmware_at_least(firmware: str | None, minimum: tuple[int, int, int]) -> bool: + """Return whether ``firmware`` parses to at least ``minimum`` (V, R, B).""" + if not firmware: + return False + parts = [int(m) for m in re.findall(r"\d+", str(firmware))] + if not parts: + return False + while len(parts) < 3: + parts.append(0) + return tuple(parts[:3]) >= minimum + + def command_session_limit(model: str | None) -> int | None: """Return how many command sessions a known gateway model accepts at once.""" - profile = get_gateway_profile(model) - if isinstance(profile, GenericGatewayProfile): + profile = _profile_for_model(model) + max_sessions = getattr(profile, "max_command_sessions", None) + if isinstance(profile, GenericGatewayProfile) or max_sessions is None: return None - return int(profile.max_command_sessions) + return int(max_sessions) def command_session_default(model: str | None) -> int: """Return how many command sessions a new entry should start with.""" - profile = get_gateway_profile(model) + profile = _profile_for_model(model) default = int(getattr(profile, "default_command_sessions", 1)) limit = command_session_limit(model) return max(1, min(default, limit) if limit is not None else 1) @@ -229,9 +267,9 @@ def command_session_default(model: str | None) -> int: 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. +# SSDP announces the H4684 as "H/L4684" (issue #466) or "HL4684" (manifest.json), which OWNd<=2.0.0b11 +# does not alias to its H4684 profile yet, and "HC4684" is the other prefixed spelling of the same product. +# TODO(OWNd>2.0.0b11): remove shim once manifest.json pins an OWNd release containing OWNd#147. _MODEL_SPELLINGS = {"HL4684": "H4684", "HC4684": "H4684"} @@ -313,6 +351,8 @@ def __init__( self.config_entry = config_entry self.generate_events = generate_events self.gateway = OWNGateway(build_info) + if isinstance(self.gateway.profile, GenericGatewayProfile): + self.gateway.profile = _profile_for_model(self.gateway.model_name, self.gateway.firmware) self.is_connected = False self._available = False self._failover_active = False @@ -460,17 +500,21 @@ def firmware(self) -> str | None: @property def supports_zone_status(self) -> bool: """Whether the gateway supports point-to-point zone status queries (*#4*Z##).""" - # The configured model string and the model OWNd resolves it to, so the spellings OWNd - # aliases (L4684, N4684, NT4684, LGR4684 and the MyHOME_Suite item numbers such as 003598 - # for the F454) are judged like the model they stand for. - names = (self.model.upper(), str(get_gateway_profile(self.model).model_name).upper()) + # Check the configured model string, its alphanumeric form (so "BTicino H/L4684" matches + # "HL4684"), and the profile model _profile_for_model resolves it to (L4684, NT4684, + # H/L4684, HL4684, HC4684, and MyHOME_Suite catalogue codes such as 003598). + names = ( + self.model.upper(), + _alnum(self.model), + str(_profile_for_model(self.model).model_name).upper(), + ) return not any( k in norm for norm in names for k in ( "F450", "F455", - "HL4684", # the SSDP spelling of the H4684 (manifest.json); not an OWNd alias yet + "HL4684", # TODO(OWNd>2.0.0b11): remove once manifest.json pins OWNd containing OWNd#147 "F454", "MH200", "MH201", @@ -766,6 +810,9 @@ def profile_supports_who(self, who: int) -> bool: def _profile_supports_who(self, who: int) -> bool: """Return whether the gateway profile advertises a WHO subsystem (True when unknown).""" + # TODO(OWNd>2.0.0b11): remove shim once manifest.json pins an OWNd release containing OWNd#147. + if who == 22 and self._model_key == "H4684" and _firmware_at_least(self.firmware, (6, 0, 0)): + return True profile = getattr(self.gateway, "profile", None) supports = getattr(profile, "supports_who", None) if not callable(supports): diff --git a/custom_components/myhome/identity.py b/custom_components/myhome/identity.py index eb5cc9576..e2bcd9b05 100644 --- a/custom_components/myhome/identity.py +++ b/custom_components/myhome/identity.py @@ -59,8 +59,8 @@ def _normalized(model: str | None) -> str: - """Compare model names without case, spaces, dashes or underscores.""" - return str(model or "").strip().upper().replace(" ", "").replace("-", "").replace("_", "") + """Compare model names without case, spaces, dashes, slashes or underscores.""" + return str(model or "").strip().upper().replace(" ", "").replace("-", "").replace("_", "").replace("/", "") def _same_product_names() -> dict[str, frozenset[str]]: @@ -403,6 +403,7 @@ def _handle_gateway_diagnostics(self, message: Any) -> None: fw, ) self.gateway.firmware = fw + self.gateway.profile = _gw._profile_for_model(self.gateway.model_name, fw) if self.config_entry is not None: new_data = dict(self.config_entry.data) if new_data.get(CONF_FIRMWARE) != fw: @@ -534,10 +535,7 @@ def _apply_model(self, model: str) -> None: from .topology import entry_firmware fw = entry_firmware(self.config_entry) - try: - self.gateway.profile = _gw.get_gateway_profile(model, fw) - except TypeError: # pragma: no cover - fallback for OWNd without firmware argument - self.gateway.profile = _gw.get_gateway_profile(model) + self.gateway.profile = _gw._profile_for_model(model, fw) self.gateway._log_id = f"[{model} gateway - {self.gateway.host}]" self._trim_sending_workers(model) new_data = dict(self.config_entry.data) diff --git a/custom_components/myhome/manifest.json b/custom_components/myhome/manifest.json index b1e39fee7..f41aeb422 100644 --- a/custom_components/myhome/manifest.json +++ b/custom_components/myhome/manifest.json @@ -19,6 +19,11 @@ "OWNd==2.0.0b11" ], "ssdp": [ + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "H/L4684" + }, { "st": "upnp:rootdevice", "manufacturer": "BTicino S.p.A.", diff --git a/custom_components/myhome/services.py b/custom_components/myhome/services.py index 3b45808c5..356adf217 100644 --- a/custom_components/myhome/services.py +++ b/custom_components/myhome/services.py @@ -246,11 +246,12 @@ async def handle_sweep_bus(call: ServiceCall) -> None: for gw_mac, handler in target_gateways.items(): _LOGGER.info("Executing diagnostic bus sweep on gateway %s (force=%s)", gw_mac, force) gateway_queries = [ - "*#13**0##", # Gateway real-time clock - "*#13**15##", # Gateway device model - "*#13**16##", # Gateway firmware version - "*#13**23##", # Gateway kernel version (modern Linux gateways; NACK ignored on older) - "*#13**24##", # Gateway distribution version (modern Linux gateways; NACK ignored on older) + "*#13**0##", # Gateway real-time clock + "*#13**15##", # Gateway device model + "*#13**16##", # Gateway firmware version + "*#13**23##", # Gateway kernel version (modern Linux gateways; NACK ignored on older) + "*#13**24##", # Gateway distribution version (modern Linux gateways; NACK ignored on older) + "*#1013*0*1##", # Gateway diagnostic OBJECT_MODEL ] general_queries = ((1, "*#1*0##"), (2, "*#2*0##"), (4, "*#4*0##"), (5, "*#5*0##"), (16, "*#16*0*5##")) diff --git a/custom_components/myhome/topology.py b/custom_components/myhome/topology.py index a2c24bf54..2c2c9d32e 100644 --- a/custom_components/myhome/topology.py +++ b/custom_components/myhome/topology.py @@ -87,16 +87,20 @@ def gateway_supported_whos(model: str | None, firmware: str | None = None) -> se whos: set[int] = set() norm = (model or "").strip().upper() try: - from OWNd.profiles import get_gateway_profile + import OWNd.profiles as _ownd_profiles - try: - profile = get_gateway_profile(model or "", firmware) - except TypeError: # pragma: no cover - fallback for OWNd without firmware argument - profile = get_gateway_profile(model or "") + from .gateway import _firmware_at_least, _profile_for_model + + profile = _profile_for_model( + model or "", firmware, lookup=_ownd_profiles.get_gateway_profile + ) supports = getattr(profile, "supports_who", None) for w in getattr(profile, "supported_who", None) or (): if not callable(supports) or supports(int(w)): whos.add(int(w)) + # TODO(OWNd>2.0.0b11): remove shim once manifest.json pins an OWNd release containing OWNd#147. + if getattr(profile, "model_name", None) == "H4684" and _firmware_at_least(firmware, (6, 0, 0)): + whos.add(22) except Exception: pass # Model-specific hardware capability constraints: diff --git a/docs/configuration/gateways.md b/docs/configuration/gateways.md index 80cac114c..6e2b966f5 100644 --- a/docs/configuration/gateways.md +++ b/docs/configuration/gateways.md @@ -21,7 +21,7 @@ The MyHOME integration communicates with SCS bus gateways over TCP/IP or RS232/U | **MH200** *(Legacy)* | OpenWebNet | 1 worker | 150 ms | ✅ SSDP | Strict single-session pacing; watchdog hardened | | **H4890 / AM4890** | OpenWebNet | 1 worker | 50 ms | ✅ SSDP | 3.5" Touch screen display IP gateway (Axolute / Livinglight) | | **F452 / F453AV** | OpenWebNet | 1 worker | 150 ms | ✅ Port 49153 | Audio/video & web server gateway | -| **HL4684** | OpenWebNet | 1 worker | 50 ms | ✅ SSDP | 10" Touch screen display IP gateway | +| **H4684 / H/L4684** | OpenWebNet | 1 worker | 50 ms | ✅ SSDP | Colour touch screen IP gateway (Axolute / LivingLight) | | **Legrand 3578** | OpenWebNet (Serial) | 1 worker | 50 ms | ❌ Manual (Serial) | USB / Serial gateway & OpenZigBee interface | diff --git a/docs/getting-started/hardware-compatibility.md b/docs/getting-started/hardware-compatibility.md index 2bde9b5d4..d80121e00 100644 --- a/docs/getting-started/hardware-compatibility.md +++ b/docs/getting-started/hardware-compatibility.md @@ -19,7 +19,7 @@ The MyHOME integration supports all official BTicino and Legrand OpenWebNet gate | **MH200** *(Legacy)* | OpenWebNet | 1 worker | 150 ms | ✅ SSDP | Strict single-session pacing; watchdog hardened | | **H4890 / AM4890** | OpenWebNet | 1 worker | 50 ms | ✅ SSDP | 3.5" Touch screen display IP gateway (Axolute / Livinglight) | | **F452 / F453AV** | OpenWebNet | 1 worker | 150 ms | ✅ Port 49153 | Audio/video & web server gateway | -| **HL4684** | OpenWebNet | 1 worker | 50 ms | ✅ SSDP | 10" Touch screen display IP gateway | +| **H4684 / H/L4684** | OpenWebNet | 1 worker | 50 ms | ✅ SSDP | Colour touch screen IP gateway (Axolute / LivingLight) | | **Legrand 3578** | OpenWebNet (Serial) | 1 worker | 50 ms | ❌ Manual (Serial) | USB / Serial gateway & OpenZigBee interface | @@ -64,6 +64,6 @@ During startup initial discovery, status queries are gated by the gateway profil | **MH201** | WHO 1, 2, 4, 16, 18 | WHO 1, 2, 16, 18 (firmware drops `*#4*0##`) | Lighting, cover, climate, sound (unmeasured class default), and energy meter 51 queries. Not in own-firmware-oracle; a physical MH201 3.3.6 returned no zone frame to `*#4*0##` although its zones report (`tests/fixtures/traces/issue_466`), so the sweep leaves it out and each climate zone asks `*#4**0##` itself. Alarm query omitted. | | **MH200** *(Legacy)* | WHO 1, 2, 4, 16 | WHO 1, 2, 16 (firmware drops `*#4*0##`) | Lighting, cover, climate, and sound queries (`bt_difson` hardware and firmware-verified). The `020102` firmware drops `*#4*0##` (own-firmware-oracle), so its climate zones ask `*#4**0##` themselves. Alarm and energy queries omitted. | | **MH200N** | WHO 1, 2, 4, 16 | WHO 1, 2, 4, 16 | Lighting, cover, climate, and sound queries (`bt_difson` hardware and firmware-verified). The `010108` firmware forwards `*#4*0##` (own-firmware-oracle), unlike the MH200. Alarm and energy queries omitted. | -| **H4684 / HL4684** | WHO 1, 2, 4, 16, 18 | WHO 1, 2, 16, 18 (firmware drops `*#4*0##`) | Lighting, cover, climate, sound, and energy meter 51 queries. The `020054` firmware drops `*#4*0##` (own-firmware-oracle), so each climate zone asks `*#4**0##` itself; the SSDP spelling `HL4684` is keyed on the same tables. Alarm query omitted. | +| **H4684 / H/L4684** | WHO 1, 2, 4, 16, 18 | WHO 1, 2, 16, 18 (firmware drops `*#4*0##`) | Lighting, cover, climate, sound, and energy meter 51 queries (`bt_multi` WHO 22 also enabled on firmware >= `6.0.0`). Both `020054` (own-firmware-oracle) and live `6.0.11` (`tests/fixtures/traces/issue_466`) drop `*#4*0##`, so each climate zone asks `*#4**0##` itself; the SSDP spellings `H/L4684` and `HL4684` resolve to `H4684Profile`. Alarm query omitted. | diff --git a/docs/trace-availability.md b/docs/trace-availability.md index d893de923..9a9dac628 100644 --- a/docs/trace-availability.md +++ b/docs/trace-availability.md @@ -3,19 +3,19 @@ This matrix documents the real-world physical gateways for which we have captured bus traces and diagnostic bundles in our `tests/fixtures` corpus. It is an honest representation of the hardware configurations that we actively test against to validate our protocol implementation. -| Gateway Model | WHO 0
Scenario | WHO 1
Lights | WHO 2
Autom. | WHO 4
Climate | WHO 5
Alarm | WHO 9
Power | WHO 13
Gateway | WHO 14
Lock | WHO 15
CEN | WHO 16
Audio | WHO 17
Scenario | WHO 18
Energy | WHO 22
Audio Diff. | WHO 25
Diag | WHO 1001
Diag | WHO 1013
Diag | WHO 1022
Diag | -| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| **F453AV** | | ✅ | ✅ | ✅ | ✅ | | ✅ | | | ✅ | | ✅ | | | ✅ | | | -| **F454** | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | | ✅ | | ✅ | | -| **F455** | | ✅ | ✅ | ✅ | ✅ | | ✅ | | | ✅ | | ✅ | | | | ✅ | | -| **F461** | | ✅ | ✅ | ✅ | ✅ | | ✅ | ✅ | | ✅ | | ✅ | | | | ✅ | | -| **H4684** | | | | | | | ✅ | | | | | | | | | ✅ | | -| **H4890 / AM4890** | | ✅ | ✅ | ✅ | ✅ | ✅ | | | | ✅ | | ✅ | ✅ | ✅ | | | | -| **MH200** | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | ✅ | ✅ | | | | ✅ | ✅ | | -| **MH200N** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| **MH201** | ✅ | ✅ | ✅ | ✅ | | | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | | | | -| **MH202** | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | ✅ | ✅ | | ✅ | | -| **MyHomeServer1** | | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | +| Gateway Model | WHO 0
Scenario | WHO 1
Lights | WHO 2
Autom. | WHO 3
Load | WHO 4
Climate | WHO 5
Alarm | WHO 9
Power | WHO 13
Gateway | WHO 14
Lock | WHO 15
CEN | WHO 16
Audio | WHO 17
Scenario | WHO 18
Energy | WHO 22
Audio Diff. | WHO 25
Diag | WHO 1001
Diag | WHO 1013
Diag | WHO 1022
Diag | +| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| **F453AV** | | ✅ | ✅ | | ✅ | ✅ | | ✅ | | | ✅ | | ✅ | | | ✅ | | | +| **F454** | | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | | ✅ | | ✅ | | +| **F455** | | ✅ | ✅ | | ✅ | ✅ | | ✅ | | | ✅ | | ✅ | | | | ✅ | | +| **F461** | | ✅ | ✅ | | ✅ | ✅ | | ✅ | ✅ | | ✅ | | ✅ | | | | ✅ | | +| **H4684 / H/L4684** | | ✅ | ✅ | ✅ | ✅ | | | ✅ | | | ✅ | | ✅ | ✅ | | | ✅ | | +| **H4890 / AM4890** | | ✅ | ✅ | | ✅ | ✅ | ✅ | | | | ✅ | | ✅ | ✅ | ✅ | | | | +| **MH200** | | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | | | ✅ | ✅ | | | | ✅ | ✅ | | +| **MH200N** | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **MH201** | ✅ | ✅ | ✅ | | ✅ | | | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | | | | +| **MH202** | | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | ✅ | ✅ | | ✅ | | +| **MyHomeServer1** | | ✅ | ✅ | | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | | *Checkmarks (✅) indicate that at least one `diagnostic_summary.json` or `.txt` bus capture in our test corpus contains frames for that subsystem from the specified gateway model.* diff --git a/scripts/update_gateway_profiles.py b/scripts/update_gateway_profiles.py index d855cdd3c..b21334fee 100644 --- a/scripts/update_gateway_profiles.py +++ b/scripts/update_gateway_profiles.py @@ -154,15 +154,15 @@ "notes": "Audio/video & web server gateway", }, { - "model": "**HL4684**", - "const_models": [], - "ssdp_models": ["HL4684"], - "profile_name": "Generic", + "model": "**H4684 / H/L4684**", + "const_models": ["H4684"], + "ssdp_models": ["H/L4684", "HL4684"], + "profile_name": "H4684", "protocol": "OpenWebNet", "max_workers": "1 worker", "delay": "50 ms", "upnp": "✅ SSDP", - "notes": '10" Touch screen display IP gateway', + "notes": "Colour touch screen IP gateway (Axolute / LivingLight)", }, { "model": "**Legrand 3578**", @@ -257,11 +257,11 @@ "notes": "Lighting, cover, climate, and sound queries (`bt_difson` hardware and firmware-verified). The `010108` firmware forwards `*#4*0##` (own-firmware-oracle), unlike the MH200. Alarm and energy queries omitted.", }, { - "model": "**H4684 / HL4684**", + "model": "**H4684 / H/L4684**", "profile_name": "H4684", "queried_whos": [1, 2, 4, 16, 18], "sweep_omits": [GENERAL_ZONE_STATUS], - "notes": "Lighting, cover, climate, sound, and energy meter 51 queries. The `020054` firmware drops `*#4*0##` (own-firmware-oracle), so each climate zone asks `*#4**0##` itself; the SSDP spelling `HL4684` is keyed on the same tables. Alarm query omitted.", + "notes": "Lighting, cover, climate, sound, and energy meter 51 queries (`bt_multi` WHO 22 also enabled on firmware >= `6.0.0`). Both `020054` (own-firmware-oracle) and live `6.0.11` (`tests/fixtures/traces/issue_466`) drop `*#4*0##`, so each climate zone asks `*#4**0##` itself; the SSDP spellings `H/L4684` and `HL4684` resolve to `H4684Profile`. Alarm query omitted.", }, ] diff --git a/scripts/update_trace_matrix.py b/scripts/update_trace_matrix.py index c1d6da964..30689ce28 100644 --- a/scripts/update_trace_matrix.py +++ b/scripts/update_trace_matrix.py @@ -10,6 +10,16 @@ START_MARKER = '' END_MARKER = '' +GATEWAY_CANONICAL_KEYS = { + "H/L4684": "H4684", + "HL4684": "H4684", + "L4684": "H4684", + "AM4890": "H4890", + "LN4890": "H4890", + "LN4890A": "H4890", +} + + def get_who(frame): if not frame.startswith('*'): return None @@ -53,6 +63,7 @@ def build_matrix(): if m: gateway = m.group(1) + gateway = GATEWAY_CANONICAL_KEYS.get(gateway, gateway) if whos: if gateway not in gateways_data: gateways_data[gateway] = set() @@ -84,7 +95,7 @@ def build_matrix(): all_whos_found.update(whos) who_names = { - 0: 'Scenario', 1: 'Lights', 2: 'Autom.', 4: 'Climate', 5: 'Alarm', 9: 'Power', + 0: 'Scenario', 1: 'Lights', 2: 'Autom.', 3: 'Load', 4: 'Climate', 5: 'Alarm', 9: 'Power', 13: 'Gateway', 14: 'Lock', 15: 'CEN', 16: 'Audio', 17: 'Scenario', 18: 'Energy', 22: 'Audio Diff.', 25: 'Diag', 1001: 'Diag', 1013: 'Diag', 1022: 'Diag' } @@ -92,6 +103,7 @@ def build_matrix(): whos_list = sorted([w for w in all_whos_found if w in who_names]) GATEWAY_DISPLAY_NAMES = { + "H4684": "H4684 / H/L4684", "H4890": "H4890 / AM4890", } diff --git a/tests/fixtures/traces/issue_466/README.md b/tests/fixtures/traces/issue_466/README.md index 07af5bb45..d2149aea4 100644 --- a/tests/fixtures/traces/issue_466/README.md +++ b/tests/fixtures/traces/issue_466/README.md @@ -667,5 +667,46 @@ Verbatim bus trace contributed by **@TheDarkWizard** on [#466 (comment 608572493 4. **Lighting (WHO 1)**: - 9 frames reporting actuator lighting status (`*1*0*0014##`, `*1*0*0016##`, etc.). +--- + +# #466 BTicino H/L4684 Colour Touchscreen Gateway Trace (FW 6.0.11 — WHO 1, 2, 3, 4, 13, 16, 18, 22) + +Verbatim Home Assistant diagnostics download contributed by **@andrea-mag** on [#466 (comment 6398442412)](https://github.com/OpenWebNet-HA/MyHOME/issues/466#issuecomment-6398442412) (`HA 2026.10.2`, `MyHOME 2.0.0b16`, `OWNd 2.0.0b11`). + +## Hardware Profile + +- **Gateway Model**: BTicino H/L4684 (Colour Touch Screen IP Gateway, Axolute / LivingLight; UPnP `modelName`: `H/L4684`, `deviceType`: `urn:schemas-bticino-it:device:touchscreen:1`) +- **Firmware**: `6.0.11` (`*#13**16*6*0*11##`) +- **Kernel**: `2.6.2` (`*#13**23*2*6*2##`) +- **Distribution**: `6.0.0` (`*#13**24*6*0*0##`) +- **WHO 13 Device Type Code**: `23` (`*#13**15*23##`) +- **Connection**: TCP OpenWebNet (Port 20000) + +## Contributed Files + +| File | Type | Description | +|---|---|---| +| `config_entry-myhome_HL4684_fw6_0_11.json` | HA Diagnostic Download (257 frames: 212 rx / 45 tx) | First physical on-wire capture from a BTicino H/L4684 touchscreen gateway, spanning `myhome.sweep_bus` across lighting & covers (`WHO 1` / `WHO 2`), autonomous load-control priority broadcasts (`WHO 3`), 8-zone thermoregulation (`WHO 4`), gateway identification (`WHO 13`), dual-dialect `WHO 16` + `WHO 22` sound system inventory, and `WHO 18` energy sweep queries. | + +## Sequence of Actions Recorded & Subsystems Verified + +1. **Gateway Identity & Software Stack (WHO 13 — 5 tx / 5 rx)**: + - Clock `*#13**0##` -> `*#13**0*14*41*00*##`. + - Device type `*#13**15##` -> `*#13**15*23##` (empirically confirming Nmap's code `23` on physical `H/L4684` FW `6.0.11`, distinct from 2006 spec code `13` on FW `2.0.54`). + - Firmware `*#13**16##` -> `*#13**16*6*0*11##` (`6.0.11`), kernel `*#13**23##` -> `*#13**23*2*6*2##` (`2.6.2`), distribution `*#13**24##` -> `*#13**24*6*0*0##` (`6.0.0`). + +2. **Load Control Priority Cycle (WHO 3 — 29 rx)**: + - Autonomous periodic load-priority status broadcasts (`*3*1*#1##` through `*3*1*#8##`, *Load priority 1–8 enabled*), cycling one priority every `~25.8 s` (`~3 min 26 s` per full `#1 -> #8` round-robin loop). + +3. **Thermoregulation & Actuator Telemetry (WHO 4 — 1 tx / 64 rx)**: + - General status query `[tx] *#4*0##` returns `0` `WHO 4` frames on the event session before the next sweep query `1.33 s` later (`*#16*0*5##`), corroborating the `020054` firmware-oracle `silent` verdict on live `6.0.11` hardware. + - Autonomous bus traffic covers 8 zones (`11, 22, 33, 44, 55, 66, 77, 88`; `Zone 33` configured with `fan: true`, the other 7 with `fan: false`) plus central pump/boiler actuators `0#1`, `0#3`, `0#4` (`*#4*0#*20*0##`, `26` frames): + - Ambient temperatures (`*#4*Z*0*0TTT##`), generic effective setpoints (`*#4*Z*14*0TTT*3##`), and Zone `66` mode/setpoint frames (`*4*1*66##`, `*4*303*66##`, `*#4*66*12*0170*3##`). + - Zone actuator channels (`11#1`, `22#1`, `33#1`, `33#2`, `44#1`, `55#1`, `66#2`, `66#3`, `77#1`, `77#2`, `88#1`) emit `*#4*#*20*5##` (`18` frames). +4. **Dual-Dialect Sound System Inventory (WHO 16 & WHO 22 — 1 tx / 28 rx)**: + - Single `[tx] *#16*0*5##` triggers `14` paired `WHO 16` (`bt_difson`) + `WHO 22` (`bt_multi`) replies (`4–14 ms` apart) on `H/L4684` FW `6.0.11`, covering `2` active stereo sources (`*16*3*101##` / `*#22*2#1*12*1*4##`, `*16*3*102##` / `*#22*2#2*12*1*4##`) and `12` amplifier zones (`11, 12, 13, 21, 22, 31, 32, 33, 41, 42, 51, 61` via `*16*13*##` / `*#22*3##*12*0*10##`; on zone `61`, `*#22*3#6#1*12*0*10##` arrives `13.6 ms` before `*16*13*61##`). +5. **Lighting, Automation & Energy Sweep Queries (WHO 1, WHO 2 & WHO 18 — 38 tx / 86 rx)**: + - `[tx] *#1*0##` and `[tx] *#2*0##` each trigger the same `43`-frame A/PL status burst (`42` `WHO 1` light/relay status frames + `1` `WHO 2` shutter status frame `*2*0*99##`, `86` `rx` frames total). + - `36` `WHO 18` `tx` queries (`*#18*51..59*51##` / `*1200##` and `*#18*71#0..79#0*51##` / `*1200##`): Dimension `51` queries wait `~943 ms` (SCS bus timeout with no meter answering), whereas unsupported Dimension `1200` queries return immediately (`~57 ms` including `50 ms` queue pacing). diff --git a/tests/fixtures/traces/issue_466/config_entry-myhome_HL4684_fw6_0_11.json b/tests/fixtures/traces/issue_466/config_entry-myhome_HL4684_fw6_0_11.json new file mode 100644 index 000000000..520e0b5b2 --- /dev/null +++ b/tests/fixtures/traces/issue_466/config_entry-myhome_HL4684_fw6_0_11.json @@ -0,0 +1,5470 @@ +{ + "home_assistant": { + "arch": "aarch64", + "dev": false, + "docker": true, + "hassio": true, + "installation_type": "Home Assistant OS", + "os_name": "Linux", + "os_version": "6.18.39-haos-raspi", + "python_version": "3.14.6", + "timezone": "Europe/Rome", + "version": "2026.10.2", + "virtualenv": false, + "container_arch": "aarch64", + "supervisor": "2026.10.3", + "host_os": "Home Assistant OS 18.3", + "docker_version": "29.7.2", + "chassis": "embedded", + "run_as_root": true + }, + "custom_components": { + "myhome": { + "documentation": "https://openwebnet-ha.github.io/MyHOME/beta/", + "version": "2.0.0b16", + "requirements": [ + "OWNd==2.0.0b11" + ] + }, + "hon": { + "documentation": "https://github.com/gvigroux/hon", + "version": "0.8.4", + "requirements": [] + }, + "hacs": { + "documentation": "https://hacs.xyz/docs/use/", + "version": "2.0.5", + "requirements": [ + "aiogithubapi>=22.10.1" + ] + }, + "tigo": { + "documentation": "https://github.com/Bobsilvio/tigosolar-local/", + "version": "4.0.1", + "requirements": [ + "websocket-client>=1.6.0" + ] + }, + "sonoff": { + "documentation": "https://github.com/AlexxIT/SonoffLAN", + "version": "3.13.2", + "requirements": [] + } + }, + "integration_manifest": { + "domain": "myhome", + "name": "MyHOME", + "after_dependencies": [ + "frontend", + "http", + "lovelace" + ], + "codeowners": [ + "anotherjulien", + "GreenGrassBlueOcean" + ], + "config_flow": true, + "documentation": "https://openwebnet-ha.github.io/MyHOME/beta/", + "integration_type": "hub", + "iot_class": "local_push", + "issue_tracker": "https://github.com/OpenWebNet-HA/MyHOME/issues", + "requirements": [ + "OWNd==2.0.0b11" + ], + "ssdp": [ + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "HL4684" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "AM4890" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "H4890" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "LN4890" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "LN4890A" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "MyHomeServer1" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "F455" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "F454" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "F453AV" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "F452" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "MH200N" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "MH200" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "MH202" + }, + { + "st": "upnp:rootdevice", + "manufacturer": "BTicino S.p.A.", + "modelName": "MH201" + } + ], + "version": "2.0.0b16", + "is_built_in": false, + "overwrites_built_in": false + }, + "setup_times": {}, + "data": { + "integration_version": "2.0.0b16", + "ownd_version": "2.0.0b11", + "config_entry": { + "entry_id": "**REDACTED**", + "version": 1, + "domain": "myhome", + "title": "H/L4684 Gateway", + "data": { + "UDN": "**REDACTED**", + "deviceType": "urn:schemas-bticino-it:device:touchscreen:1", + "firmware": "6.0.11", + "friendly_name": "**REDACTED**", + "host": "**REDACTED**", + "id": "**REDACTED**", + "mac": "**REDACTED**", + "manufacturer": "BTicino S.p.A.", + "manufacturerURL": "http://www.bticino.it", + "name": "H/L4684", + "password": "**REDACTED**", + "port": 20000, + "ssdp_location": "**REDACTED**", + "ssdp_st": "upnp:rootdevice" + }, + "options": { + "command_worker_count": 1, + "config_file_path": "**REDACTED**", + "devices": { + "media_player": { + "16-101": { + "name": "Sound Zone 101", + "where": "101", + "who": "16" + }, + "16-102": { + "name": "Sound Zone 102", + "where": "102", + "who": "16" + }, + "16-103": { + "name": "Sound Zone 103", + "where": "103", + "who": "16" + }, + "16-121": { + "name": "Sound Zone 121", + "where": "121", + "who": "16" + }, + "16-21": { + "name": "Sound Zone 21", + "where": "21", + "who": "16" + }, + "22-3#2#1": { + "name": "Sound Zone 3#2#1", + "where": "3#2#1", + "who": "22" + } + } + }, + "enable_auto_learning": false, + "generate_events": false, + "migrated_yaml_platforms": { + "light": { + "1-27": { + "where": "27", + "name": "Scale PS1-PTE", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-28": { + "where": "28", + "name": "Scale PTE-P1", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-29": { + "where": "29", + "name": "EXT - Matrimoniale P1", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-31": { + "where": "31", + "name": "EXT - Cucina Muro", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-32": { + "where": "32", + "name": "EXT - Patio Ingresso", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-33": { + "where": "33", + "name": "EXT - Balcone Ingresso", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-34": { + "where": "34", + "name": "PT - Bagno", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-35": { + "where": "35", + "name": "PT - Bagno Specchio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-36": { + "where": "36", + "name": "PT - Sala", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-38": { + "where": "38", + "name": "P1 - Corridoio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-39": { + "where": "39", + "name": "P1 - Matrimoniale", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-42": { + "where": "42", + "name": "P1 - Bagno Specchio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-43": { + "where": "43", + "name": "P1 - Bagno", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-44": { + "where": "44", + "name": "P1 - Cameretta", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-45": { + "where": "45", + "name": "P1 - Matrimoniale Armadio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-78": { + "where": "78", + "name": "PT - Cucina", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-79": { + "where": "79", + "name": "PT - Cucina Faretti", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-91": { + "where": "91", + "name": "EXT - Cucina Muretto", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-92": { + "where": "92", + "name": "EXT - Giardino Piscina", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-93": { + "where": "93", + "name": "EXT - Giardino Ingresso", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-94": { + "where": "94", + "name": "EXT - Giardino Ingresso", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-98": { + "where": "98", + "name": "EXT - Circolatore ACS", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-11": { + "where": "11", + "name": "PS1 - Dimmer Biliardo", + "dimmable": true, + "manufacturer": "BTicino", + "model": "F412", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-14": { + "where": "14", + "name": "PS1 - Cucina", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-15": { + "where": "15", + "name": "PS1 - Ripostiglio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-16": { + "where": "16", + "name": "PS1 - Biliardo", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-17": { + "where": "17", + "name": "PS1 - Camino", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-18": { + "where": "18", + "name": "PS1 - Libreria", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-81": { + "where": "81", + "name": "PS1 - Disimpegno", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-21": { + "where": "21", + "name": "PS1 - Cameretta", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-22": { + "where": "22", + "name": "PS1 - Bagno Specchio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-23": { + "where": "23", + "name": "PS1 - Bagno", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-24": { + "where": "24", + "name": "PS1 - Matrimoniale Applique", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-26": { + "where": "26", + "name": "PS1 - Matrimoniale Faretti", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + } + }, + "switch": { + "1-37": { + "where": "37", + "name": "PTE - Presa Comandata", + "class": "outlet", + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null + }, + "1-19": { + "where": "19", + "name": "PS1 - Presa Camino", + "class": "outlet", + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null + }, + "1-13": { + "where": "13", + "name": "PS1 - Presa Biliardo", + "class": "outlet", + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null + }, + "1-12": { + "where": "12", + "name": "PS1 - Presa Finestra Biliardo", + "class": "outlet", + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null + } + }, + "climate": { + "4-#0": { + "zone": "#0", + "name": "Central unit", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "3550", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "central_unit": { + "zone": "#0", + "name": "Central unit", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "3550", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "0": { + "zone": "#0", + "name": "Central unit", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "3550", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "#0": { + "zone": "#0", + "name": "Central unit", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "3550", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_0": { + "zone": "#0", + "name": "Central unit", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "3550", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "4-11": { + "zone": "11", + "name": "P1 - Matrimoniale", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_11_matrimoniale": { + "zone": "11", + "name": "P1 - Matrimoniale", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "11": { + "zone": "11", + "name": "P1 - Matrimoniale", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_11": { + "zone": "11", + "name": "P1 - Matrimoniale", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "4-22": { + "zone": "22", + "name": "P1 - Cameretta", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_22_cameretta": { + "zone": "22", + "name": "P1 - Cameretta", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "22": { + "zone": "22", + "name": "P1 - Cameretta", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_22": { + "zone": "22", + "name": "P1 - Cameretta", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "4-33": { + "zone": "33", + "name": "PTE - Soggiorno", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "fan": true, + "who": "4", + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_33_soggiorno": { + "zone": "33", + "name": "PTE - Soggiorno", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "fan": true, + "who": "4", + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "33": { + "zone": "33", + "name": "PTE - Soggiorno", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "fan": true, + "who": "4", + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_33": { + "zone": "33", + "name": "PTE - Soggiorno", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "fan": true, + "who": "4", + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "4-44": { + "zone": "44", + "name": "PS1 - Matrimoniale", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_44_ps1_matrimoniale": { + "zone": "44", + "name": "PS1 - Matrimoniale", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "44": { + "zone": "44", + "name": "PS1 - Matrimoniale", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_44": { + "zone": "44", + "name": "PS1 - Matrimoniale", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "4-55": { + "zone": "55", + "name": "PS1 - Cameretta", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_55_ps1_cameretta": { + "zone": "55", + "name": "PS1 - Cameretta", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "55": { + "zone": "55", + "name": "PS1 - Cameretta", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_55": { + "zone": "55", + "name": "PS1 - Cameretta", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "4-66": { + "zone": "66", + "name": "PS1 - Camino", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_66_ps1_camino": { + "zone": "66", + "name": "PS1 - Camino", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "66": { + "zone": "66", + "name": "PS1 - Camino", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_66": { + "zone": "66", + "name": "PS1 - Camino", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "4-77": { + "zone": "77", + "name": "PS1 - Biliardo", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_77_ps1_biliardo": { + "zone": "77", + "name": "PS1 - Biliardo", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "77": { + "zone": "77", + "name": "PS1 - Biliardo", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_77": { + "zone": "77", + "name": "PS1 - Biliardo", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "4-88": { + "zone": "88", + "name": "PS1 - Cucina", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_88_ps1_cucina": { + "zone": "88", + "name": "PS1 - Cucina", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "88": { + "zone": "88", + "name": "PS1 - Cucina", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "zone_88": { + "zone": "88", + "name": "PS1 - Cucina", + "heat": true, + "cool": false, + "standalone": false, + "manufacturer": "BTicino", + "model": "F430/4", + "who": "4", + "fan": false, + "humidity": false, + "central": false, + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + } + }, + "button": { + "1-27": { + "where": "27", + "name": "Scale PS1-PTE", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-28": { + "where": "28", + "name": "Scale PTE-P1", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-29": { + "where": "29", + "name": "EXT - Matrimoniale P1", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-31": { + "where": "31", + "name": "EXT - Cucina Muro", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-32": { + "where": "32", + "name": "EXT - Patio Ingresso", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-33": { + "where": "33", + "name": "EXT - Balcone Ingresso", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-34": { + "where": "34", + "name": "PT - Bagno", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-35": { + "where": "35", + "name": "PT - Bagno Specchio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-36": { + "where": "36", + "name": "PT - Sala", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-38": { + "where": "38", + "name": "P1 - Corridoio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-39": { + "where": "39", + "name": "P1 - Matrimoniale", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-42": { + "where": "42", + "name": "P1 - Bagno Specchio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-43": { + "where": "43", + "name": "P1 - Bagno", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-44": { + "where": "44", + "name": "P1 - Cameretta", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-45": { + "where": "45", + "name": "P1 - Matrimoniale Armadio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-78": { + "where": "78", + "name": "PT - Cucina", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-79": { + "where": "79", + "name": "PT - Cucina Faretti", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-91": { + "where": "91", + "name": "EXT - Cucina Muretto", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-92": { + "where": "92", + "name": "EXT - Giardino Piscina", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-93": { + "where": "93", + "name": "EXT - Giardino Ingresso", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-94": { + "where": "94", + "name": "EXT - Giardino Ingresso", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-98": { + "where": "98", + "name": "EXT - Circolatore ACS", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-11": { + "where": "11", + "name": "PS1 - Dimmer Biliardo", + "dimmable": true, + "manufacturer": "BTicino", + "model": "F412", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-14": { + "where": "14", + "name": "PS1 - Cucina", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-15": { + "where": "15", + "name": "PS1 - Ripostiglio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-16": { + "where": "16", + "name": "PS1 - Biliardo", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-17": { + "where": "17", + "name": "PS1 - Camino", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-18": { + "where": "18", + "name": "PS1 - Libreria", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-81": { + "where": "81", + "name": "PS1 - Disimpegno", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-21": { + "where": "21", + "name": "PS1 - Cameretta", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-22": { + "where": "22", + "name": "PS1 - Bagno Specchio", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-23": { + "where": "23", + "name": "PS1 - Bagno", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-24": { + "where": "24", + "name": "PS1 - Matrimoniale Applique", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-26": { + "where": "26", + "name": "PS1 - Matrimoniale Faretti", + "dimmable": false, + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null, + "class": "switch" + }, + "1-37": { + "where": "37", + "name": "PTE - Presa Comandata", + "class": "outlet", + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null + }, + "1-19": { + "where": "19", + "name": "PS1 - Presa Camino", + "class": "outlet", + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null + }, + "1-13": { + "where": "13", + "name": "PS1 - Presa Biliardo", + "class": "outlet", + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null + }, + "1-12": { + "where": "12", + "name": "PS1 - Presa Finestra Biliardo", + "class": "outlet", + "manufacturer": "BTicino", + "model": "F411U2", + "who": "1", + "entities": {}, + "icon": null, + "icon_on": null, + "entity_name": null + } + } + }, + "broadcast_resync": true, + "transition_mode": "software_stepped", + "auto_join_streaming": true, + "ignored_addresses": [], + "source_defaults": {}, + "source_1_name": "Radio", + "source_1_tuner": true, + "source_2_name": "AUX", + "source_2_tuner": false, + "source_3_name": "TouchScreen", + "source_3_tuner": false, + "source_4_name": "", + "source_4_tuner": false, + "decoder_1_entity": "", + "decoder_1_source": 1, + "decoder_1_pre_gain": 0, + "decoder_2_entity": "", + "decoder_2_source": 2, + "decoder_2_pre_gain": 0, + "decoder_3_entity": "", + "decoder_3_source": 3, + "decoder_3_pre_gain": 0, + "decoder_4_entity": "", + "decoder_4_source": 4, + "decoder_4_pre_gain": 0, + "bus_topology": "standalone", + "gateway_role": "primary" + } + }, + "gateway": { + "model_name": "H/L4684", + "manufacturer": "BTicino S.p.A.", + "firmware": "6.0.11", + "is_connected": true, + "send_workers": 1, + "bus_topology": "standalone", + "gateway_role": "primary", + "is_follower": false, + "is_standby": false, + "failover_active": false, + "primary_gateway": null, + "delegated_whos": [], + "identification": { + "model": "H/L4684", + "source": "ssdp", + "configured_model": "H/L4684", + "ssdp_model": "H/L4684", + "ssdp_location": "**REDACTED**", + "who13_code": "23", + "who13_model": "H4684", + "who13_model_official": null, + "who13_model_observed": "H4684", + "who13_firmware": "6.0.11", + "who13_kernel": "2.6.2", + "who13_distribution": "6.0.0", + "who1013_code": null, + "who1013_model": null, + "who1013_other_names": [], + "who1013_n_conf": null, + "who1013_brand": null, + "who1013_line": null, + "profile": "GenericGatewayProfile", + "conflict": null + }, + "device_faults": [] + }, + "profile": { + "name": "Generic", + "command_queue_delay": 0.05, + "max_queue_size": 200, + "keepalive_interval": 90.0 + }, + "queue": { + "queue_depth": 0, + "max_size": 200 + }, + "platforms": { + "light": 68, + "switch": 8, + "cover": 0, + "climate": 37, + "binary_sensor": 0, + "sensor": 0, + "media_player": 0, + "button": 76, + "alarm_control_panel": 0, + "lock": 0 + }, + "expose": [], + "audio": { + "zones": { + "zone_11": { + "state": "off", + "source": 1, + "volume_level": 0.03225806451612903, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_12": { + "state": "off", + "source": 1, + "volume_level": 0.03225806451612903, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_13": { + "state": "off", + "source": 1, + "volume_level": 0.6774193548387096, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_21": { + "state": "off", + "source": 2, + "volume_level": 0.3225806451612903, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_22": { + "state": "off", + "source": 2, + "volume_level": 0.03225806451612903, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_31": { + "state": "off", + "source": null, + "volume_level": 0.03225806451612903, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_32": { + "state": "off", + "source": null, + "volume_level": 0.03225806451612903, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_33": { + "state": "off", + "source": null, + "volume_level": 0.03225806451612903, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_41": { + "state": "off", + "source": null, + "volume_level": 0.03225806451612903, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_42": { + "state": "off", + "source": null, + "volume_level": 0.03225806451612903, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_51": { + "state": "off", + "source": null, + "volume_level": null, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + }, + "zone_61": { + "state": "off", + "source": null, + "volume_level": 0.03225806451612903, + "is_volume_muted": false, + "has_decoder": false, + "parked": false, + "wake_pending": false, + "status_seen": true + } + }, + "decoder_pool": null + }, + "bus_monitor": { + "stats": { + "capacity": 500, + "captured": 257, + "total_rx": 212, + "total_tx": 45, + "subscribers": 0 + }, + "recent_frames": [ + { + "timestamp": 1792934989.003769, + "iso_time": "2026-10-25T13:29:49.003769+00:00", + "direction": "rx", + "raw": "*3*1*#7##", + "who": "3", + "where": "#7", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935002.074709, + "iso_time": "2026-10-25T13:30:02.074709+00:00", + "direction": "rx", + "raw": "*#4*11*0*0209##", + "who": "4", + "where": "11", + "what": null, + "dimension": "0", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935002.294802, + "iso_time": "2026-10-25T13:30:02.294802+00:00", + "direction": "rx", + "raw": "*#4*11*14*0195*3##", + "who": "4", + "where": "11", + "what": null, + "dimension": "14", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935002.584182, + "iso_time": "2026-10-25T13:30:02.584182+00:00", + "direction": "rx", + "raw": "*#4*22*0*0224##", + "who": "4", + "where": "22", + "what": null, + "dimension": "0", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935002.786117, + "iso_time": "2026-10-25T13:30:02.786117+00:00", + "direction": "rx", + "raw": "*#4*22*14*0170*3##", + "who": "4", + "where": "22", + "what": null, + "dimension": "14", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935003.103726, + "iso_time": "2026-10-25T13:30:03.103726+00:00", + "direction": "rx", + "raw": "*#4*33*0*0214##", + "who": "4", + "where": "33", + "what": null, + "dimension": "0", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935003.274266, + "iso_time": "2026-10-25T13:30:03.274266+00:00", + "direction": "rx", + "raw": "*#4*33*14*0160*3##", + "who": "4", + "where": "33", + "what": null, + "dimension": "14", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935003.584058, + "iso_time": "2026-10-25T13:30:03.584058+00:00", + "direction": "rx", + "raw": "*#4*0#3*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935003.782921, + "iso_time": "2026-10-25T13:30:03.782921+00:00", + "direction": "rx", + "raw": "*#4*0#4*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935003.854139, + "iso_time": "2026-10-25T13:30:03.854139+00:00", + "direction": "rx", + "raw": "*#4*11#1*20*5##", + "who": "4", + "where": "11", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935003.914058, + "iso_time": "2026-10-25T13:30:03.914058+00:00", + "direction": "rx", + "raw": "*#4*44*0*0233##", + "who": "4", + "where": "44", + "what": null, + "dimension": "0", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935004.145349, + "iso_time": "2026-10-25T13:30:04.145349+00:00", + "direction": "rx", + "raw": "*#4*0#1*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935004.30411, + "iso_time": "2026-10-25T13:30:04.304110+00:00", + "direction": "rx", + "raw": "*#4*0#3*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935004.444204, + "iso_time": "2026-10-25T13:30:04.444204+00:00", + "direction": "rx", + "raw": "*#4*0#4*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935004.494073, + "iso_time": "2026-10-25T13:30:04.494073+00:00", + "direction": "rx", + "raw": "*#4*33#2*20*5##", + "who": "4", + "where": "33", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935004.533891, + "iso_time": "2026-10-25T13:30:04.533891+00:00", + "direction": "rx", + "raw": "*#4*33#1*20*5##", + "who": "4", + "where": "33", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935004.624258, + "iso_time": "2026-10-25T13:30:04.624258+00:00", + "direction": "rx", + "raw": "*#4*55*0*0226##", + "who": "4", + "where": "55", + "what": null, + "dimension": "0", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935004.704132, + "iso_time": "2026-10-25T13:30:04.704132+00:00", + "direction": "rx", + "raw": "*#4*66*0*0228##", + "who": "4", + "where": "66", + "what": null, + "dimension": "0", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935004.743794, + "iso_time": "2026-10-25T13:30:04.743794+00:00", + "direction": "rx", + "raw": "*4*1*66##", + "who": "4", + "where": "66", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935004.78469, + "iso_time": "2026-10-25T13:30:04.784690+00:00", + "direction": "rx", + "raw": "*#4*66*12*0170*3##", + "who": "4", + "where": "66", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935004.854856, + "iso_time": "2026-10-25T13:30:04.854856+00:00", + "direction": "rx", + "raw": "*#4*44*14*0180*3##", + "who": "4", + "where": "44", + "what": null, + "dimension": "14", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935005.224287, + "iso_time": "2026-10-25T13:30:05.224287+00:00", + "direction": "rx", + "raw": "*#4*77*0*0224##", + "who": "4", + "where": "77", + "what": null, + "dimension": "0", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935005.354506, + "iso_time": "2026-10-25T13:30:05.354506+00:00", + "direction": "rx", + "raw": "*#4*55*14*0180*3##", + "who": "4", + "where": "55", + "what": null, + "dimension": "14", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935005.484679, + "iso_time": "2026-10-25T13:30:05.484679+00:00", + "direction": "rx", + "raw": "*#4*66*14*0200*3##", + "who": "4", + "where": "66", + "what": null, + "dimension": "14", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935005.584652, + "iso_time": "2026-10-25T13:30:05.584652+00:00", + "direction": "rx", + "raw": "*#4*55*14*0180*3##", + "who": "4", + "where": "55", + "what": null, + "dimension": "14", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935005.814161, + "iso_time": "2026-10-25T13:30:05.814161+00:00", + "direction": "rx", + "raw": "*#4*88*0*0221##", + "who": "4", + "where": "88", + "what": null, + "dimension": "0", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935005.884495, + "iso_time": "2026-10-25T13:30:05.884495+00:00", + "direction": "rx", + "raw": "*#4*77*14*0180*3##", + "who": "4", + "where": "77", + "what": null, + "dimension": "14", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935006.195248, + "iso_time": "2026-10-25T13:30:06.195248+00:00", + "direction": "rx", + "raw": "*#4*88*14*0180*3##", + "who": "4", + "where": "88", + "what": null, + "dimension": "14", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935007.135683, + "iso_time": "2026-10-25T13:30:07.135683+00:00", + "direction": "rx", + "raw": "*#4*0#1*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935007.23423, + "iso_time": "2026-10-25T13:30:07.234230+00:00", + "direction": "rx", + "raw": "*#4*0#3*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935007.354015, + "iso_time": "2026-10-25T13:30:07.354015+00:00", + "direction": "rx", + "raw": "*#4*0#4*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935007.444058, + "iso_time": "2026-10-25T13:30:07.444058+00:00", + "direction": "rx", + "raw": "*#4*55#1*20*5##", + "who": "4", + "where": "55", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935007.894923, + "iso_time": "2026-10-25T13:30:07.894923+00:00", + "direction": "rx", + "raw": "*#4*0#1*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935008.01387, + "iso_time": "2026-10-25T13:30:08.013870+00:00", + "direction": "rx", + "raw": "*#4*0#3*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935008.144136, + "iso_time": "2026-10-25T13:30:08.144136+00:00", + "direction": "rx", + "raw": "*#4*0#4*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935008.244248, + "iso_time": "2026-10-25T13:30:08.244248+00:00", + "direction": "rx", + "raw": "*#4*44#1*20*5##", + "who": "4", + "where": "44", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935009.484523, + "iso_time": "2026-10-25T13:30:09.484523+00:00", + "direction": "rx", + "raw": "*#4*0#1*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935009.614385, + "iso_time": "2026-10-25T13:30:09.614385+00:00", + "direction": "rx", + "raw": "*#4*0#3*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935009.734336, + "iso_time": "2026-10-25T13:30:09.734336+00:00", + "direction": "rx", + "raw": "*#4*0#4*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935009.824162, + "iso_time": "2026-10-25T13:30:09.824162+00:00", + "direction": "rx", + "raw": "*#4*88#1*20*5##", + "who": "4", + "where": "88", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935009.924394, + "iso_time": "2026-10-25T13:30:09.924394+00:00", + "direction": "rx", + "raw": "*#4*0#1*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935010.067771, + "iso_time": "2026-10-25T13:30:10.067771+00:00", + "direction": "rx", + "raw": "*#4*0#3*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935010.144441, + "iso_time": "2026-10-25T13:30:10.144441+00:00", + "direction": "rx", + "raw": "*#4*0#4*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935010.304411, + "iso_time": "2026-10-25T13:30:10.304411+00:00", + "direction": "rx", + "raw": "*#4*66#3*20*5##", + "who": "4", + "where": "66", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935010.334557, + "iso_time": "2026-10-25T13:30:10.334557+00:00", + "direction": "rx", + "raw": "*#4*66#2*20*5##", + "who": "4", + "where": "66", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935010.444315, + "iso_time": "2026-10-25T13:30:10.444315+00:00", + "direction": "rx", + "raw": "*#4*0#1*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935010.564479, + "iso_time": "2026-10-25T13:30:10.564479+00:00", + "direction": "rx", + "raw": "*#4*0#3*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935010.684512, + "iso_time": "2026-10-25T13:30:10.684512+00:00", + "direction": "rx", + "raw": "*#4*0#4*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935010.774631, + "iso_time": "2026-10-25T13:30:10.774631+00:00", + "direction": "rx", + "raw": "*#4*22#1*20*5##", + "who": "4", + "where": "22", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935013.58489, + "iso_time": "2026-10-25T13:30:13.584890+00:00", + "direction": "rx", + "raw": "*3*1*#8##", + "who": "3", + "where": "#8", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935014.555524, + "iso_time": "2026-10-25T13:30:14.555524+00:00", + "direction": "rx", + "raw": "*#4*0#1*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935014.726034, + "iso_time": "2026-10-25T13:30:14.726034+00:00", + "direction": "rx", + "raw": "*#4*0#3*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935014.834526, + "iso_time": "2026-10-25T13:30:14.834526+00:00", + "direction": "rx", + "raw": "*#4*0#4*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935015.054556, + "iso_time": "2026-10-25T13:30:15.054556+00:00", + "direction": "rx", + "raw": "*#4*77#1*20*5##", + "who": "4", + "where": "77", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935015.075108, + "iso_time": "2026-10-25T13:30:15.075108+00:00", + "direction": "rx", + "raw": "*#4*77#2*20*5##", + "who": "4", + "where": "77", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935020.00496, + "iso_time": "2026-10-25T13:30:20.004960+00:00", + "direction": "rx", + "raw": "*#4*66#3*20*5##", + "who": "4", + "where": "66", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935020.048923, + "iso_time": "2026-10-25T13:30:20.048923+00:00", + "direction": "rx", + "raw": "*#4*66#2*20*5##", + "who": "4", + "where": "66", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935029.932124, + "iso_time": "2026-10-25T13:30:29.932124+00:00", + "direction": "rx", + "raw": "*4*303*66##", + "who": "4", + "where": "66", + "what": "303", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935030.043664, + "iso_time": "2026-10-25T13:30:30.043664+00:00", + "direction": "rx", + "raw": "*#4*0#1*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935030.145423, + "iso_time": "2026-10-25T13:30:30.145423+00:00", + "direction": "rx", + "raw": "*#4*0#3*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935030.275479, + "iso_time": "2026-10-25T13:30:30.275479+00:00", + "direction": "rx", + "raw": "*#4*0#4*20*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935030.405247, + "iso_time": "2026-10-25T13:30:30.405247+00:00", + "direction": "rx", + "raw": "*#4*66#3*20*5##", + "who": "4", + "where": "66", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935030.435639, + "iso_time": "2026-10-25T13:30:30.435639+00:00", + "direction": "rx", + "raw": "*#4*66#2*20*5##", + "who": "4", + "where": "66", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935039.096078, + "iso_time": "2026-10-25T13:30:39.096078+00:00", + "direction": "rx", + "raw": "*3*1*#1##", + "who": "3", + "where": "#1", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935040.086685, + "iso_time": "2026-10-25T13:30:40.086685+00:00", + "direction": "rx", + "raw": "*#4*66#3*20*5##", + "who": "4", + "where": "66", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935040.135733, + "iso_time": "2026-10-25T13:30:40.135733+00:00", + "direction": "rx", + "raw": "*#4*66#2*20*5##", + "who": "4", + "where": "66", + "what": null, + "dimension": "20", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935050.022258, + "iso_time": "2026-10-25T13:30:50.022258+00:00", + "direction": "rx", + "raw": "*4*303*66##", + "who": "4", + "where": "66", + "what": "303", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935064.807417, + "iso_time": "2026-10-25T13:31:04.807417+00:00", + "direction": "rx", + "raw": "*3*1*#2##", + "who": "3", + "where": "#2", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935090.628003, + "iso_time": "2026-10-25T13:31:30.628003+00:00", + "direction": "rx", + "raw": "*3*1*#3##", + "who": "3", + "where": "#3", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935116.389482, + "iso_time": "2026-10-25T13:31:56.389482+00:00", + "direction": "rx", + "raw": "*3*1*#4##", + "who": "3", + "where": "#4", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935142.210597, + "iso_time": "2026-10-25T13:32:22.210597+00:00", + "direction": "rx", + "raw": "*3*1*#5##", + "who": "3", + "where": "#5", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935167.962611, + "iso_time": "2026-10-25T13:32:47.962611+00:00", + "direction": "rx", + "raw": "*3*1*#6##", + "who": "3", + "where": "#6", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935193.783431, + "iso_time": "2026-10-25T13:33:13.783431+00:00", + "direction": "rx", + "raw": "*3*1*#7##", + "who": "3", + "where": "#7", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935219.545181, + "iso_time": "2026-10-25T13:33:39.545181+00:00", + "direction": "rx", + "raw": "*3*1*#8##", + "who": "3", + "where": "#8", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935245.325858, + "iso_time": "2026-10-25T13:34:05.325858+00:00", + "direction": "rx", + "raw": "*3*1*#1##", + "who": "3", + "where": "#1", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935271.117695, + "iso_time": "2026-10-25T13:34:31.117695+00:00", + "direction": "rx", + "raw": "*3*1*#2##", + "who": "3", + "where": "#2", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935296.898458, + "iso_time": "2026-10-25T13:34:56.898458+00:00", + "direction": "rx", + "raw": "*3*1*#3##", + "who": "3", + "where": "#3", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935322.700195, + "iso_time": "2026-10-25T13:35:22.700195+00:00", + "direction": "rx", + "raw": "*3*1*#4##", + "who": "3", + "where": "#4", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935348.480913, + "iso_time": "2026-10-25T13:35:48.480913+00:00", + "direction": "rx", + "raw": "*3*1*#5##", + "who": "3", + "where": "#5", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935374.192885, + "iso_time": "2026-10-25T13:36:14.192885+00:00", + "direction": "rx", + "raw": "*3*1*#6##", + "who": "3", + "where": "#6", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935399.973797, + "iso_time": "2026-10-25T13:36:39.973797+00:00", + "direction": "rx", + "raw": "*3*1*#7##", + "who": "3", + "where": "#7", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935425.734274, + "iso_time": "2026-10-25T13:37:05.734274+00:00", + "direction": "rx", + "raw": "*3*1*#8##", + "who": "3", + "where": "#8", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935451.555935, + "iso_time": "2026-10-25T13:37:31.555935+00:00", + "direction": "rx", + "raw": "*3*1*#1##", + "who": "3", + "where": "#1", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935477.317713, + "iso_time": "2026-10-25T13:37:57.317713+00:00", + "direction": "rx", + "raw": "*3*1*#2##", + "who": "3", + "where": "#2", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935503.138728, + "iso_time": "2026-10-25T13:38:23.138728+00:00", + "direction": "rx", + "raw": "*3*1*#3##", + "who": "3", + "where": "#3", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935528.891552, + "iso_time": "2026-10-25T13:38:48.891552+00:00", + "direction": "rx", + "raw": "*3*1*#4##", + "who": "3", + "where": "#4", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935554.691384, + "iso_time": "2026-10-25T13:39:14.691384+00:00", + "direction": "rx", + "raw": "*3*1*#5##", + "who": "3", + "where": "#5", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935580.472874, + "iso_time": "2026-10-25T13:39:40.472874+00:00", + "direction": "rx", + "raw": "*3*1*#6##", + "who": "3", + "where": "#6", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935606.253657, + "iso_time": "2026-10-25T13:40:06.253657+00:00", + "direction": "rx", + "raw": "*3*1*#7##", + "who": "3", + "where": "#7", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935632.061033, + "iso_time": "2026-10-25T13:40:32.061033+00:00", + "direction": "rx", + "raw": "*3*1*#8##", + "who": "3", + "where": "#8", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935657.836375, + "iso_time": "2026-10-25T13:40:57.836375+00:00", + "direction": "rx", + "raw": "*3*1*#1##", + "who": "3", + "where": "#1", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.260423, + "iso_time": "2026-10-25T13:41:02.260423+00:00", + "direction": "tx", + "raw": "*#13**0##", + "who": "13", + "where": "", + "what": null, + "dimension": "0", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.275146, + "iso_time": "2026-10-25T13:41:02.275146+00:00", + "direction": "rx", + "raw": "*#13**0*14*41*00*##", + "who": "13", + "where": "", + "what": null, + "dimension": "0", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.326815, + "iso_time": "2026-10-25T13:41:02.326815+00:00", + "direction": "tx", + "raw": "*#13**15##", + "who": "13", + "where": "", + "what": null, + "dimension": "15", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.334342, + "iso_time": "2026-10-25T13:41:02.334342+00:00", + "direction": "rx", + "raw": "*#13**15*23##", + "who": "13", + "where": "", + "what": null, + "dimension": "15", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.385798, + "iso_time": "2026-10-25T13:41:02.385798+00:00", + "direction": "tx", + "raw": "*#13**16##", + "who": "13", + "where": "", + "what": null, + "dimension": "16", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.39168, + "iso_time": "2026-10-25T13:41:02.391680+00:00", + "direction": "rx", + "raw": "*#13**16*6*0*11##", + "who": "13", + "where": "", + "what": null, + "dimension": "16", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.443527, + "iso_time": "2026-10-25T13:41:02.443527+00:00", + "direction": "tx", + "raw": "*#13**23##", + "who": "13", + "where": "", + "what": null, + "dimension": "23", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.449567, + "iso_time": "2026-10-25T13:41:02.449567+00:00", + "direction": "rx", + "raw": "*#13**23*2*6*2##", + "who": "13", + "where": "", + "what": null, + "dimension": "23", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.501909, + "iso_time": "2026-10-25T13:41:02.501909+00:00", + "direction": "tx", + "raw": "*#13**24##", + "who": "13", + "where": "", + "what": null, + "dimension": "24", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.507936, + "iso_time": "2026-10-25T13:41:02.507936+00:00", + "direction": "rx", + "raw": "*#13**24*6*0*0##", + "who": "13", + "where": "", + "what": null, + "dimension": "24", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.559214, + "iso_time": "2026-10-25T13:41:02.559214+00:00", + "direction": "tx", + "raw": "*#1*0##", + "who": "1", + "where": "0", + "what": null, + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.630505, + "iso_time": "2026-10-25T13:41:02.630505+00:00", + "direction": "rx", + "raw": "*1*0*11##", + "who": "1", + "where": "11", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.670423, + "iso_time": "2026-10-25T13:41:02.670423+00:00", + "direction": "rx", + "raw": "*1*0*21##", + "who": "1", + "where": "21", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.709112, + "iso_time": "2026-10-25T13:41:02.709112+00:00", + "direction": "rx", + "raw": "*1*0*81##", + "who": "1", + "where": "81", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.739436, + "iso_time": "2026-10-25T13:41:02.739436+00:00", + "direction": "rx", + "raw": "*1*0*22##", + "who": "1", + "where": "22", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.783754, + "iso_time": "2026-10-25T13:41:02.783754+00:00", + "direction": "rx", + "raw": "*1*0*82##", + "who": "1", + "where": "82", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.821192, + "iso_time": "2026-10-25T13:41:02.821192+00:00", + "direction": "rx", + "raw": "*1*0*91##", + "who": "1", + "where": "91", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.886909, + "iso_time": "2026-10-25T13:41:02.886909+00:00", + "direction": "rx", + "raw": "*1*0*92##", + "who": "1", + "where": "92", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.915703, + "iso_time": "2026-10-25T13:41:02.915703+00:00", + "direction": "rx", + "raw": "*1*0*12##", + "who": "1", + "where": "12", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.949535, + "iso_time": "2026-10-25T13:41:02.949535+00:00", + "direction": "rx", + "raw": "*1*0*42##", + "who": "1", + "where": "42", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935662.996809, + "iso_time": "2026-10-25T13:41:02.996809+00:00", + "direction": "rx", + "raw": "*1*0*13##", + "who": "1", + "where": "13", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.045868, + "iso_time": "2026-10-25T13:41:03.045868+00:00", + "direction": "rx", + "raw": "*1*0*43##", + "who": "1", + "where": "43", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.095511, + "iso_time": "2026-10-25T13:41:03.095511+00:00", + "direction": "rx", + "raw": "*1*0*23##", + "who": "1", + "where": "23", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.158503, + "iso_time": "2026-10-25T13:41:03.158503+00:00", + "direction": "rx", + "raw": "*1*0*33##", + "who": "1", + "where": "33", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.189571, + "iso_time": "2026-10-25T13:41:03.189571+00:00", + "direction": "rx", + "raw": "*1*0*24##", + "who": "1", + "where": "24", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.24044, + "iso_time": "2026-10-25T13:41:03.240440+00:00", + "direction": "rx", + "raw": "*1*0*31##", + "who": "1", + "where": "31", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.270421, + "iso_time": "2026-10-25T13:41:03.270421+00:00", + "direction": "rx", + "raw": "*1*0*93##", + "who": "1", + "where": "93", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.302097, + "iso_time": "2026-10-25T13:41:03.302097+00:00", + "direction": "rx", + "raw": "*1*0*94##", + "who": "1", + "where": "94", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.350825, + "iso_time": "2026-10-25T13:41:03.350825+00:00", + "direction": "rx", + "raw": "*1*0*14##", + "who": "1", + "where": "14", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.409955, + "iso_time": "2026-10-25T13:41:03.409955+00:00", + "direction": "rx", + "raw": "*1*0*34##", + "who": "1", + "where": "34", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.441718, + "iso_time": "2026-10-25T13:41:03.441718+00:00", + "direction": "rx", + "raw": "*1*0*15##", + "who": "1", + "where": "15", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.487728, + "iso_time": "2026-10-25T13:41:03.487728+00:00", + "direction": "rx", + "raw": "*1*0*37##", + "who": "1", + "where": "37", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.545248, + "iso_time": "2026-10-25T13:41:03.545248+00:00", + "direction": "rx", + "raw": "*1*0*44##", + "who": "1", + "where": "44", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.575163, + "iso_time": "2026-10-25T13:41:03.575163+00:00", + "direction": "rx", + "raw": "*1*0*45##", + "who": "1", + "where": "45", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.62509, + "iso_time": "2026-10-25T13:41:03.625090+00:00", + "direction": "rx", + "raw": "*1*0*25##", + "who": "1", + "where": "25", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.676028, + "iso_time": "2026-10-25T13:41:03.676028+00:00", + "direction": "rx", + "raw": "*1*0*35##", + "who": "1", + "where": "35", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.706269, + "iso_time": "2026-10-25T13:41:03.706269+00:00", + "direction": "rx", + "raw": "*1*0*26##", + "who": "1", + "where": "26", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.753354, + "iso_time": "2026-10-25T13:41:03.753354+00:00", + "direction": "rx", + "raw": "*1*0*32##", + "who": "1", + "where": "32", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.80128, + "iso_time": "2026-10-25T13:41:03.801280+00:00", + "direction": "rx", + "raw": "*1*0*95##", + "who": "1", + "where": "95", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.840133, + "iso_time": "2026-10-25T13:41:03.840133+00:00", + "direction": "rx", + "raw": "*1*0*16##", + "who": "1", + "where": "16", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.887842, + "iso_time": "2026-10-25T13:41:03.887842+00:00", + "direction": "rx", + "raw": "*1*0*36##", + "who": "1", + "where": "36", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.932435, + "iso_time": "2026-10-25T13:41:03.932435+00:00", + "direction": "rx", + "raw": "*1*0*17##", + "who": "1", + "where": "17", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935663.985839, + "iso_time": "2026-10-25T13:41:03.985839+00:00", + "direction": "rx", + "raw": "*1*0*27##", + "who": "1", + "where": "27", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.032359, + "iso_time": "2026-10-25T13:41:04.032359+00:00", + "direction": "rx", + "raw": "*1*0*29##", + "who": "1", + "where": "29", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.07796, + "iso_time": "2026-10-25T13:41:04.077960+00:00", + "direction": "rx", + "raw": "*1*0*18##", + "who": "1", + "where": "18", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.127114, + "iso_time": "2026-10-25T13:41:04.127114+00:00", + "direction": "rx", + "raw": "*1*0*38##", + "who": "1", + "where": "38", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.169958, + "iso_time": "2026-10-25T13:41:04.169958+00:00", + "direction": "rx", + "raw": "*1*0*19##", + "who": "1", + "where": "19", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.219877, + "iso_time": "2026-10-25T13:41:04.219877+00:00", + "direction": "rx", + "raw": "*1*0*39##", + "who": "1", + "where": "39", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.268708, + "iso_time": "2026-10-25T13:41:04.268708+00:00", + "direction": "rx", + "raw": "*1*0*98##", + "who": "1", + "where": "98", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.282087, + "iso_time": "2026-10-25T13:41:04.282087+00:00", + "direction": "rx", + "raw": "*1*0*29##", + "who": "1", + "where": "29", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.32795, + "iso_time": "2026-10-25T13:41:04.327950+00:00", + "direction": "rx", + "raw": "*1*0*28##", + "who": "1", + "where": "28", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.378287, + "iso_time": "2026-10-25T13:41:04.378287+00:00", + "direction": "rx", + "raw": "*1*1*79##", + "who": "1", + "where": "79", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.408652, + "iso_time": "2026-10-25T13:41:04.408652+00:00", + "direction": "rx", + "raw": "*2*0*99##", + "who": "2", + "where": "99", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935664.45302, + "iso_time": "2026-10-25T13:41:04.453020+00:00", + "direction": "rx", + "raw": "*1*0*78##", + "who": "1", + "where": "78", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.218415, + "iso_time": "2026-10-25T13:41:06.218415+00:00", + "direction": "tx", + "raw": "*#2*0##", + "who": "2", + "where": "0", + "what": null, + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.289241, + "iso_time": "2026-10-25T13:41:06.289241+00:00", + "direction": "rx", + "raw": "*1*0*11##", + "who": "1", + "where": "11", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.330646, + "iso_time": "2026-10-25T13:41:06.330646+00:00", + "direction": "rx", + "raw": "*1*0*21##", + "who": "1", + "where": "21", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.368601, + "iso_time": "2026-10-25T13:41:06.368601+00:00", + "direction": "rx", + "raw": "*1*0*81##", + "who": "1", + "where": "81", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.398907, + "iso_time": "2026-10-25T13:41:06.398907+00:00", + "direction": "rx", + "raw": "*1*0*91##", + "who": "1", + "where": "91", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.437151, + "iso_time": "2026-10-25T13:41:06.437151+00:00", + "direction": "rx", + "raw": "*1*0*22##", + "who": "1", + "where": "22", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.48026, + "iso_time": "2026-10-25T13:41:06.480260+00:00", + "direction": "rx", + "raw": "*1*0*82##", + "who": "1", + "where": "82", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.530057, + "iso_time": "2026-10-25T13:41:06.530057+00:00", + "direction": "rx", + "raw": "*1*0*92##", + "who": "1", + "where": "92", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.578987, + "iso_time": "2026-10-25T13:41:06.578987+00:00", + "direction": "rx", + "raw": "*1*0*12##", + "who": "1", + "where": "12", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.620453, + "iso_time": "2026-10-25T13:41:06.620453+00:00", + "direction": "rx", + "raw": "*1*0*42##", + "who": "1", + "where": "42", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.671232, + "iso_time": "2026-10-25T13:41:06.671232+00:00", + "direction": "rx", + "raw": "*1*0*13##", + "who": "1", + "where": "13", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.713004, + "iso_time": "2026-10-25T13:41:06.713004+00:00", + "direction": "rx", + "raw": "*1*0*43##", + "who": "1", + "where": "43", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.772232, + "iso_time": "2026-10-25T13:41:06.772232+00:00", + "direction": "rx", + "raw": "*1*0*23##", + "who": "1", + "where": "23", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.80886, + "iso_time": "2026-10-25T13:41:06.808860+00:00", + "direction": "rx", + "raw": "*1*0*33##", + "who": "1", + "where": "33", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.859708, + "iso_time": "2026-10-25T13:41:06.859708+00:00", + "direction": "rx", + "raw": "*1*0*24##", + "who": "1", + "where": "24", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.887348, + "iso_time": "2026-10-25T13:41:06.887348+00:00", + "direction": "rx", + "raw": "*1*0*31##", + "who": "1", + "where": "31", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.932355, + "iso_time": "2026-10-25T13:41:06.932355+00:00", + "direction": "rx", + "raw": "*1*0*93##", + "who": "1", + "where": "93", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935666.977557, + "iso_time": "2026-10-25T13:41:06.977557+00:00", + "direction": "rx", + "raw": "*1*0*94##", + "who": "1", + "where": "94", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.019223, + "iso_time": "2026-10-25T13:41:07.019223+00:00", + "direction": "rx", + "raw": "*1*0*14##", + "who": "1", + "where": "14", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.068717, + "iso_time": "2026-10-25T13:41:07.068717+00:00", + "direction": "rx", + "raw": "*1*0*34##", + "who": "1", + "where": "34", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.11237, + "iso_time": "2026-10-25T13:41:07.112370+00:00", + "direction": "rx", + "raw": "*1*0*15##", + "who": "1", + "where": "15", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.166378, + "iso_time": "2026-10-25T13:41:07.166378+00:00", + "direction": "rx", + "raw": "*1*0*37##", + "who": "1", + "where": "37", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.203771, + "iso_time": "2026-10-25T13:41:07.203771+00:00", + "direction": "rx", + "raw": "*1*0*44##", + "who": "1", + "where": "44", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.254917, + "iso_time": "2026-10-25T13:41:07.254917+00:00", + "direction": "rx", + "raw": "*1*0*45##", + "who": "1", + "where": "45", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.304127, + "iso_time": "2026-10-25T13:41:07.304127+00:00", + "direction": "rx", + "raw": "*1*0*25##", + "who": "1", + "where": "25", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.335952, + "iso_time": "2026-10-25T13:41:07.335952+00:00", + "direction": "rx", + "raw": "*1*0*35##", + "who": "1", + "where": "35", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.399777, + "iso_time": "2026-10-25T13:41:07.399777+00:00", + "direction": "rx", + "raw": "*1*0*32##", + "who": "1", + "where": "32", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.446143, + "iso_time": "2026-10-25T13:41:07.446143+00:00", + "direction": "rx", + "raw": "*1*0*26##", + "who": "1", + "where": "26", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.486414, + "iso_time": "2026-10-25T13:41:07.486414+00:00", + "direction": "rx", + "raw": "*1*0*95##", + "who": "1", + "where": "95", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.529877, + "iso_time": "2026-10-25T13:41:07.529877+00:00", + "direction": "rx", + "raw": "*1*0*16##", + "who": "1", + "where": "16", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.578937, + "iso_time": "2026-10-25T13:41:07.578937+00:00", + "direction": "rx", + "raw": "*1*0*36##", + "who": "1", + "where": "36", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.616813, + "iso_time": "2026-10-25T13:41:07.616813+00:00", + "direction": "rx", + "raw": "*1*0*17##", + "who": "1", + "where": "17", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.664294, + "iso_time": "2026-10-25T13:41:07.664294+00:00", + "direction": "rx", + "raw": "*1*0*27##", + "who": "1", + "where": "27", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.697884, + "iso_time": "2026-10-25T13:41:07.697884+00:00", + "direction": "rx", + "raw": "*1*0*29##", + "who": "1", + "where": "29", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.759927, + "iso_time": "2026-10-25T13:41:07.759927+00:00", + "direction": "rx", + "raw": "*1*0*18##", + "who": "1", + "where": "18", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.787864, + "iso_time": "2026-10-25T13:41:07.787864+00:00", + "direction": "rx", + "raw": "*1*0*38##", + "who": "1", + "where": "38", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.828866, + "iso_time": "2026-10-25T13:41:07.828866+00:00", + "direction": "rx", + "raw": "*1*0*19##", + "who": "1", + "where": "19", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.872136, + "iso_time": "2026-10-25T13:41:07.872136+00:00", + "direction": "rx", + "raw": "*1*0*39##", + "who": "1", + "where": "39", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.918247, + "iso_time": "2026-10-25T13:41:07.918247+00:00", + "direction": "rx", + "raw": "*1*0*98##", + "who": "1", + "where": "98", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.960239, + "iso_time": "2026-10-25T13:41:07.960239+00:00", + "direction": "rx", + "raw": "*1*0*29##", + "who": "1", + "where": "29", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935667.988179, + "iso_time": "2026-10-25T13:41:07.988179+00:00", + "direction": "rx", + "raw": "*1*1*79##", + "who": "1", + "where": "79", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935668.030402, + "iso_time": "2026-10-25T13:41:08.030402+00:00", + "direction": "rx", + "raw": "*1*0*28##", + "who": "1", + "where": "28", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935668.073979, + "iso_time": "2026-10-25T13:41:08.073979+00:00", + "direction": "rx", + "raw": "*1*0*78##", + "who": "1", + "where": "78", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935668.119749, + "iso_time": "2026-10-25T13:41:08.119749+00:00", + "direction": "rx", + "raw": "*2*0*99##", + "who": "2", + "where": "99", + "what": "0", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935668.871935, + "iso_time": "2026-10-25T13:41:08.871935+00:00", + "direction": "tx", + "raw": "*#4*0##", + "who": "4", + "where": "0", + "what": null, + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.197834, + "iso_time": "2026-10-25T13:41:10.197834+00:00", + "direction": "tx", + "raw": "*#16*0*5##", + "who": "16", + "where": "0", + "what": null, + "dimension": "5", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.330277, + "iso_time": "2026-10-25T13:41:10.330277+00:00", + "direction": "rx", + "raw": "*16*3*101##", + "who": "16", + "where": "101", + "what": "3", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.335537, + "iso_time": "2026-10-25T13:41:10.335537+00:00", + "direction": "rx", + "raw": "*#22*2#1*12*1*4##", + "who": "22", + "where": "2", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.379009, + "iso_time": "2026-10-25T13:41:10.379009+00:00", + "direction": "rx", + "raw": "*16*3*102##", + "who": "16", + "where": "102", + "what": "3", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.383856, + "iso_time": "2026-10-25T13:41:10.383856+00:00", + "direction": "rx", + "raw": "*#22*2#2*12*1*4##", + "who": "22", + "where": "2", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.848101, + "iso_time": "2026-10-25T13:41:10.848101+00:00", + "direction": "rx", + "raw": "*16*13*42##", + "who": "16", + "where": "42", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.853709, + "iso_time": "2026-10-25T13:41:10.853709+00:00", + "direction": "rx", + "raw": "*#22*3#4#2*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.889427, + "iso_time": "2026-10-25T13:41:10.889427+00:00", + "direction": "rx", + "raw": "*16*13*32##", + "who": "16", + "where": "32", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.896421, + "iso_time": "2026-10-25T13:41:10.896421+00:00", + "direction": "rx", + "raw": "*#22*3#3#2*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.918736, + "iso_time": "2026-10-25T13:41:10.918736+00:00", + "direction": "rx", + "raw": "*16*13*41##", + "who": "16", + "where": "41", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935670.9251, + "iso_time": "2026-10-25T13:41:10.925100+00:00", + "direction": "rx", + "raw": "*#22*3#4#1*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.068936, + "iso_time": "2026-10-25T13:41:11.068936+00:00", + "direction": "rx", + "raw": "*16*13*22##", + "who": "16", + "where": "22", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.073764, + "iso_time": "2026-10-25T13:41:11.073764+00:00", + "direction": "rx", + "raw": "*#22*3#2#2*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.099686, + "iso_time": "2026-10-25T13:41:11.099686+00:00", + "direction": "rx", + "raw": "*16*13*51##", + "who": "16", + "where": "51", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.1053, + "iso_time": "2026-10-25T13:41:11.105300+00:00", + "direction": "rx", + "raw": "*#22*3#5#1*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.19946, + "iso_time": "2026-10-25T13:41:11.199460+00:00", + "direction": "rx", + "raw": "*16*13*33##", + "who": "16", + "where": "33", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.204139, + "iso_time": "2026-10-25T13:41:11.204139+00:00", + "direction": "rx", + "raw": "*#22*3#3#3*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.229306, + "iso_time": "2026-10-25T13:41:11.229306+00:00", + "direction": "rx", + "raw": "*16*13*13##", + "who": "16", + "where": "13", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.236039, + "iso_time": "2026-10-25T13:41:11.236039+00:00", + "direction": "rx", + "raw": "*#22*3#1#3*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.599414, + "iso_time": "2026-10-25T13:41:11.599414+00:00", + "direction": "rx", + "raw": "*16*13*31##", + "who": "16", + "where": "31", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.604306, + "iso_time": "2026-10-25T13:41:11.604306+00:00", + "direction": "rx", + "raw": "*#22*3#3#1*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.829112, + "iso_time": "2026-10-25T13:41:11.829112+00:00", + "direction": "rx", + "raw": "*16*13*21##", + "who": "16", + "where": "21", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.833942, + "iso_time": "2026-10-25T13:41:11.833942+00:00", + "direction": "rx", + "raw": "*#22*3#2#1*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.900125, + "iso_time": "2026-10-25T13:41:11.900125+00:00", + "direction": "rx", + "raw": "*#22*3#6#1*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.913711, + "iso_time": "2026-10-25T13:41:11.913711+00:00", + "direction": "rx", + "raw": "*16*13*61##", + "who": "16", + "where": "61", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.97786, + "iso_time": "2026-10-25T13:41:11.977860+00:00", + "direction": "rx", + "raw": "*16*13*12##", + "who": "16", + "where": "12", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935671.979603, + "iso_time": "2026-10-25T13:41:11.979603+00:00", + "direction": "rx", + "raw": "*#22*3#1#2*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935672.058725, + "iso_time": "2026-10-25T13:41:12.058725+00:00", + "direction": "rx", + "raw": "*16*13*11##", + "who": "16", + "where": "11", + "what": "13", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935672.063847, + "iso_time": "2026-10-25T13:41:12.063847+00:00", + "direction": "rx", + "raw": "*#22*3#1#1*12*0*10##", + "who": "22", + "where": "3", + "what": null, + "dimension": "12", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935674.848816, + "iso_time": "2026-10-25T13:41:14.848816+00:00", + "direction": "tx", + "raw": "*#18*51*51##", + "who": "18", + "where": "51", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935674.925752, + "iso_time": "2026-10-25T13:41:14.925752+00:00", + "direction": "tx", + "raw": "*#18*51*1200##", + "who": "18", + "where": "51", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935674.982456, + "iso_time": "2026-10-25T13:41:14.982456+00:00", + "direction": "tx", + "raw": "*#18*52*51##", + "who": "18", + "where": "52", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935675.92585, + "iso_time": "2026-10-25T13:41:15.925850+00:00", + "direction": "tx", + "raw": "*#18*52*1200##", + "who": "18", + "where": "52", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935675.981999, + "iso_time": "2026-10-25T13:41:15.981999+00:00", + "direction": "tx", + "raw": "*#18*53*51##", + "who": "18", + "where": "53", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935676.925961, + "iso_time": "2026-10-25T13:41:16.925961+00:00", + "direction": "tx", + "raw": "*#18*53*1200##", + "who": "18", + "where": "53", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935676.982433, + "iso_time": "2026-10-25T13:41:16.982433+00:00", + "direction": "tx", + "raw": "*#18*54*51##", + "who": "18", + "where": "54", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935677.92598, + "iso_time": "2026-10-25T13:41:17.925980+00:00", + "direction": "tx", + "raw": "*#18*54*1200##", + "who": "18", + "where": "54", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935677.98265, + "iso_time": "2026-10-25T13:41:17.982650+00:00", + "direction": "tx", + "raw": "*#18*55*51##", + "who": "18", + "where": "55", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935678.926197, + "iso_time": "2026-10-25T13:41:18.926197+00:00", + "direction": "tx", + "raw": "*#18*55*1200##", + "who": "18", + "where": "55", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935678.983482, + "iso_time": "2026-10-25T13:41:18.983482+00:00", + "direction": "tx", + "raw": "*#18*56*51##", + "who": "18", + "where": "56", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935679.926535, + "iso_time": "2026-10-25T13:41:19.926535+00:00", + "direction": "tx", + "raw": "*#18*56*1200##", + "who": "18", + "where": "56", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935679.982793, + "iso_time": "2026-10-25T13:41:19.982793+00:00", + "direction": "tx", + "raw": "*#18*57*51##", + "who": "18", + "where": "57", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935680.925684, + "iso_time": "2026-10-25T13:41:20.925684+00:00", + "direction": "tx", + "raw": "*#18*57*1200##", + "who": "18", + "where": "57", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935680.981808, + "iso_time": "2026-10-25T13:41:20.981808+00:00", + "direction": "tx", + "raw": "*#18*58*51##", + "who": "18", + "where": "58", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935681.926342, + "iso_time": "2026-10-25T13:41:21.926342+00:00", + "direction": "tx", + "raw": "*#18*58*1200##", + "who": "18", + "where": "58", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935681.983168, + "iso_time": "2026-10-25T13:41:21.983168+00:00", + "direction": "tx", + "raw": "*#18*59*51##", + "who": "18", + "where": "59", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935682.548558, + "iso_time": "2026-10-25T13:41:22.548558+00:00", + "direction": "rx", + "raw": "*3*1*#2##", + "who": "3", + "where": "#2", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935682.926713, + "iso_time": "2026-10-25T13:41:22.926713+00:00", + "direction": "tx", + "raw": "*#18*59*1200##", + "who": "18", + "where": "59", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935682.982922, + "iso_time": "2026-10-25T13:41:22.982922+00:00", + "direction": "tx", + "raw": "*#18*71#0*51##", + "who": "18", + "where": "71", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935683.926587, + "iso_time": "2026-10-25T13:41:23.926587+00:00", + "direction": "tx", + "raw": "*#18*71#0*1200##", + "who": "18", + "where": "71", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935683.983229, + "iso_time": "2026-10-25T13:41:23.983229+00:00", + "direction": "tx", + "raw": "*#18*72#0*51##", + "who": "18", + "where": "72", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935684.926507, + "iso_time": "2026-10-25T13:41:24.926507+00:00", + "direction": "tx", + "raw": "*#18*72#0*1200##", + "who": "18", + "where": "72", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935684.982967, + "iso_time": "2026-10-25T13:41:24.982967+00:00", + "direction": "tx", + "raw": "*#18*73#0*51##", + "who": "18", + "where": "73", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935685.926137, + "iso_time": "2026-10-25T13:41:25.926137+00:00", + "direction": "tx", + "raw": "*#18*73#0*1200##", + "who": "18", + "where": "73", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935685.982745, + "iso_time": "2026-10-25T13:41:25.982745+00:00", + "direction": "tx", + "raw": "*#18*74#0*51##", + "who": "18", + "where": "74", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935686.926572, + "iso_time": "2026-10-25T13:41:26.926572+00:00", + "direction": "tx", + "raw": "*#18*74#0*1200##", + "who": "18", + "where": "74", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935686.983581, + "iso_time": "2026-10-25T13:41:26.983581+00:00", + "direction": "tx", + "raw": "*#18*75#0*51##", + "who": "18", + "where": "75", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935687.926448, + "iso_time": "2026-10-25T13:41:27.926448+00:00", + "direction": "tx", + "raw": "*#18*75#0*1200##", + "who": "18", + "where": "75", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935687.98318, + "iso_time": "2026-10-25T13:41:27.983180+00:00", + "direction": "tx", + "raw": "*#18*76#0*51##", + "who": "18", + "where": "76", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935688.926535, + "iso_time": "2026-10-25T13:41:28.926535+00:00", + "direction": "tx", + "raw": "*#18*76#0*1200##", + "who": "18", + "where": "76", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935688.983141, + "iso_time": "2026-10-25T13:41:28.983141+00:00", + "direction": "tx", + "raw": "*#18*77#0*51##", + "who": "18", + "where": "77", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935689.930527, + "iso_time": "2026-10-25T13:41:29.930527+00:00", + "direction": "tx", + "raw": "*#18*77#0*1200##", + "who": "18", + "where": "77", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935689.987287, + "iso_time": "2026-10-25T13:41:29.987287+00:00", + "direction": "tx", + "raw": "*#18*78#0*51##", + "who": "18", + "where": "78", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935690.926272, + "iso_time": "2026-10-25T13:41:30.926272+00:00", + "direction": "tx", + "raw": "*#18*78#0*1200##", + "who": "18", + "where": "78", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935690.982317, + "iso_time": "2026-10-25T13:41:30.982317+00:00", + "direction": "tx", + "raw": "*#18*79#0*51##", + "who": "18", + "where": "79", + "what": null, + "dimension": "51", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935691.926814, + "iso_time": "2026-10-25T13:41:31.926814+00:00", + "direction": "tx", + "raw": "*#18*79#0*1200##", + "who": "18", + "where": "79", + "what": null, + "dimension": "1200", + "is_ack": false, + "is_nack": false, + "is_duplicate": false + }, + { + "timestamp": 1792935708.189435, + "iso_time": "2026-10-25T13:41:48.189435+00:00", + "direction": "rx", + "raw": "*3*1*#3##", + "who": "3", + "where": "#3", + "what": "1", + "dimension": null, + "is_ack": false, + "is_nack": false, + "is_duplicate": false + } + ] + }, + "topology_inference": { + "target_gateway": { + "mac": "**REDACTED**", + "model": "H/L4684", + "hardware_tier": 3, + "command_queue_delay": 0.05, + "supported_whos": [ + 1, + 2, + 4, + 15, + 16, + 18, + 22, + 25 + ], + "configured_topology": "standalone", + "configured_role": "primary", + "configured_primary": null, + "configured_delegated_whos": [] + }, + "peer_count": 0, + "evaluations": [] + } + }, + "issues": [ + { + "created": "2026-10-24T17:34:56.287730+00:00", + "dismissed_version": "2026.10.2", + "domain": "myhome", + "is_persistent": false, + "issue_id": "legacy_yaml_imported_01M1VB4S1ZJM0GCE7T4D18PAGZ" + } + ] +} diff --git a/tests/test_climate_humidity_poll_gate.py b/tests/test_climate_humidity_poll_gate.py index 48eff51f1..981903a82 100644 --- a/tests/test_climate_humidity_poll_gate.py +++ b/tests/test_climate_humidity_poll_gate.py @@ -68,7 +68,6 @@ def _entry(model: str) -> MagicMock: def _handler(hass, model: str) -> MyHOMEGatewayHandler: handler = MyHOMEGatewayHandler(hass, _entry(model)) - handler.gateway.profile = get_gateway_profile(model) handler.send_status_request = AsyncMock() return handler @@ -202,20 +201,23 @@ def test_aliased_model_strings_follow_their_profile(hass, model, profile_model): assert handler._model_key == profile_model -def test_ssdp_model_string_hl4684_is_keyed_as_h4684(hass): - """SSDP announces the H4684 as ``HL4684`` (manifest.json); OWNd has no alias for it yet.""" - handler = _handler(hass, "HL4684") - assert handler.gateway.profile.model_name == "HL4684" # the Generic profile keeps the SSDP string +@pytest.mark.parametrize("ssdp_model", ["HL4684", "H/L4684", "HC4684"]) +def test_ssdp_model_string_hl4684_is_keyed_as_h4684(hass, ssdp_model): + """SSDP announces the H4684 as ``H/L4684`` or ``HL4684``; handler keeps the SSDP name and resolves H4684Profile.""" + handler = _handler(hass, ssdp_model) + assert handler.model == ssdp_model + assert handler.gateway.profile.model_name == "H4684" assert handler._model_key == "H4684" + assert handler.humidity_request_refused is True @pytest.mark.parametrize( ("model", "key", "refused"), [ - ("HL4684", "H4684", True), # the SSDP modelName, never overwritten by WHO 13 / 1013 - ("HC4684", "H4684", True), ("BTicino L4684", "H4684", True), # OWNd aliases the word, not the brand-prefixed string ("BTicino HL4684", "H4684", True), + ("BTicino H/L4684", "H4684", True), + ("BTicino HC4684", "H4684", True), ("Legrand 003598", "F454", True), # the MyHOME_Suite item number of the F454 ("Legrand 003565", "MH200N", True), # ... of the MH200N: not collapsed to MH200 ("Legrand 003535", "MH202", True), diff --git a/tests/test_diagnostics.py b/tests/test_diagnostics.py index 93d11307f..0bc20c463 100644 --- a/tests/test_diagnostics.py +++ b/tests/test_diagnostics.py @@ -195,7 +195,7 @@ async def test_diagnostics_carry_no_household_identity(hass: HomeAssistant): "name": "MyHomeServer1", } mock_entry.options = { - "file_path": "C:/Users/rossi/myhome.yaml", "command_worker_count": 1, + "file_path": "C:/Users/rossi/myhome.yaml", "config_file_path": "/config/myhome.yaml", "command_worker_count": 1, # decoder slots: the media_player is named after a room, its slot -> source mapping is diagnostics "decoder_1_entity": "media_player.rossi_living_room", "decoder_1_source": 2, "decoder_1_pre_gain": 10, "decoder_2_entity": "", "decoder_2_source": 2, "decoder_2_pre_gain": 0, @@ -208,6 +208,7 @@ async def test_diagnostics_carry_no_household_identity(hass: HomeAssistant): for key in ("host", CONF_MAC, "id", "UDN", "ssdp_location", "friendly_name", CONF_PASSWORD): assert data[key] == "**REDACTED**", key assert options["file_path"] == "**REDACTED**" + assert options["config_file_path"] == "**REDACTED**" assert data["port"] == 20000 and data["name"] == "MyHomeServer1" and options["command_worker_count"] == 1 assert options["decoder_1_entity"] == "media_player.decoder_1" # the slot, not the room assert options["decoder_1_source"] == 2 and options["decoder_1_pre_gain"] == 10 diff --git a/tests/test_gateway_identification.py b/tests/test_gateway_identification.py index ce883dd11..33368d56c 100644 --- a/tests/test_gateway_identification.py +++ b/tests/test_gateway_identification.py @@ -92,7 +92,10 @@ def test_official_table_is_the_2006_document_verbatim(): assert GATEWAY_DEVICE_TYPE_MAP["44"] == "MH200N" # code 200: observed on F454 (#370, #420), MyHOMEServer1 (#292 / #297, #420), MH202 (#420); # reported for F461 (#370), observed on H4890 (#466), observed on MH201 (OWNd#116) - assert WHO13_OBSERVED_DEVICE_TYPES == {"200": ("F454", "MyHomeServer1", "MH202", "F461", "H4890", "MH201")} + assert WHO13_OBSERVED_DEVICE_TYPES == { + "23": ("H4684", "H/L4684", "L4684"), + "200": ("F454", "MyHomeServer1", "MH202", "F461", "H4890", "MH201"), + } assert WHO13_SHARED_DEVICE_TYPES == {"200"} assert WHO13_SHARED_DEVICE_TYPES <= set(WHO13_OBSERVED_DEVICE_TYPES) # every model a shared WHO=13 code may stand for has a WHO=1013 code that settles it @@ -124,7 +127,6 @@ def test_third_party_table_is_the_nmap_device_table(): "12": ("F453AV",), "15": ("F427",), "16": ("F453",), - "23": ("H4684",), "27": ("L4686SDK",), "44": ("MH200N",), "51": ("F454",), @@ -154,7 +156,11 @@ def test_who13_and_who1013_are_separate_identifier_spaces(): 12, 44, 51 - which is exactly why this is pinned: agreeing on a few codes is not a reason to treat one table as the other. """ - who13_all = {**WHO13_OFFICIAL_DEVICE_TYPES, **{c: m[0] for c, m in WHO13_THIRD_PARTY_DEVICE_TYPES.items()}} + who13_all = { + **WHO13_OFFICIAL_DEVICE_TYPES, + **{c: m[0] for c, m in WHO13_OBSERVED_DEVICE_TYPES.items() if c not in WHO13_SHARED_DEVICE_TYPES}, + **{c: m[0] for c, m in WHO13_THIRD_PARTY_DEVICE_TYPES.items()}, + } by_model_who13 = {model: code for code, model in who13_all.items()} by_model_who1013 = {models[0]: code for code, models in WHO1013_OBJECT_MODELS.items()} @@ -219,6 +225,8 @@ def test_h4684_ground_truth_firmware_mapping(): assert r19.alternative_names == ("L4684",) assert r19.compatible_with("H4684") is True assert r19.compatible_with("L4684") is True + assert r19.compatible_with("H/L4684") is True + assert r19.compatible_with("HL4684") is True r26 = read_who1013("26") assert r26.canonical == "LGRH4684" diff --git a/tests/test_gateway_profiles.py b/tests/test_gateway_profiles.py index 17e69941f..de4f0d763 100644 --- a/tests/test_gateway_profiles.py +++ b/tests/test_gateway_profiles.py @@ -43,6 +43,7 @@ def test_extract_gateway_models_from_const(): assert "F452V" in models assert "F453" in models assert "F453AV" in models + assert "H4684" in models assert "Generic" in models @@ -53,6 +54,7 @@ def test_extract_ssdp_models_from_manifest(): assert "MyHomeServer1" in ssdp_models assert "AM4890" in ssdp_models assert "HL4684" in ssdp_models + assert "H/L4684" in ssdp_models def test_generate_gateway_profiles_table(): @@ -172,7 +174,7 @@ def test_generate_gateway_discovery_table_and_verify_oracle(): assert "| **MH200** *(Legacy)* | WHO 1, 2, 4, 16 | WHO 1, 2, 16 (firmware drops `*#4*0##`) |" in block assert "| **MH200N** | WHO 1, 2, 4, 16 | WHO 1, 2, 4, 16 |" in block assert "| **MH201** | WHO 1, 2, 4, 16, 18 | WHO 1, 2, 16, 18 (firmware drops `*#4*0##`) |" in block - assert "| **H4684 / HL4684** | WHO 1, 2, 4, 16, 18 | WHO 1, 2, 16, 18 (firmware drops `*#4*0##`) |" in block + assert "| **H4684 / H/L4684** | WHO 1, 2, 4, 16, 18 | WHO 1, 2, 16, 18 (firmware drops `*#4*0##`) |" in block assert ugp.verify_profiles_against_oracle() == [] diff --git a/tests/test_issue_466_hl4684_trace.py b/tests/test_issue_466_hl4684_trace.py new file mode 100644 index 000000000..d58426ba5 --- /dev/null +++ b/tests/test_issue_466_hl4684_trace.py @@ -0,0 +1,491 @@ +"""Replay and regression tests for physical BTicino H/L4684 trace (Issue #466). + +Contributed by @andrea-mag in https://github.com/OpenWebNet-HA/MyHOME/issues/466#issuecomment-6398442412 +(H/L4684 firmware 6.0.11, kernel 2.6.2, distro 6.0.0, WHO 13 device type 23). +""" +from __future__ import annotations + +import json +from pathlib import Path +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from homeassistant.components.climate import ClimateEntityFeature, HVACAction, HVACMode +from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PASSWORD, CONF_PORT +from homeassistant.core import HomeAssistant, State +from homeassistant.helpers import entity_registry as er +from OWNd.message import ( + OWNEvent, + OWNGatewayDiagnosticEvent, + OWNGatewayEvent, + OWNHeatingEvent, + OWNLoadEvent, + OWNMessage, +) +from OWNd.profiles import GenericGatewayProfile +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.myhome.climate import MyHOMEClimate, _zone_address +from custom_components.myhome.const import ( + CONF_DEVICE_TYPE, + CONF_ENTITY, + CONF_FILE_PATH, + CONF_FIRMWARE, + CONF_SSDP_LOCATION, + CONF_SSDP_ST, + DOMAIN, + IDENTIFICATION_SSDP, +) +from custom_components.myhome.diagnostics import TO_REDACT, async_get_config_entry_diagnostics +from custom_components.myhome.gateway import ( + MyHOMEGatewayHandler, + _firmware_at_least, + _profile_for_model, + command_session_default, + command_session_limit, +) +from custom_components.myhome.topology import gateway_supported_whos + +TRACE_PATH = ( + Path(__file__).resolve().parent + / "fixtures" + / "traces" + / "issue_466" + / "config_entry-myhome_HL4684_fw6_0_11.json" +) + + +def _load_fixture() -> dict: + return json.loads(TRACE_PATH.read_text(encoding="utf-8")) + + +def test_hl4684_trace_fixture_integrity_and_parser_coverage() -> None: + """Every frame in the H/L4684 diagnostics trace parses cleanly and redacts file paths.""" + doc = _load_fixture() + assert CONF_FILE_PATH in TO_REDACT + assert doc["data"]["config_entry"]["options"][CONF_FILE_PATH] == "**REDACTED**" + + frames = doc["data"]["bus_monitor"]["recent_frames"] + assert len(frames) == 257 + + rx_frames = [f["raw"] for f in frames if f["direction"] == "rx"] + tx_frames = [f["raw"] for f in frames if f["direction"] == "tx"] + assert len(rx_frames) == 212 + assert len(tx_frames) == 45 + + rx_whos: set[int] = set() + who3_events: list[OWNLoadEvent] = [] + for raw in rx_frames: + msg = OWNEvent.parse(raw) or OWNMessage.parse(raw) + assert msg is not None, f"Failed to parse rx frame: {raw}" + if getattr(msg, "who", None) is not None: + rx_whos.add(int(msg.who)) + if isinstance(msg, OWNLoadEvent): + who3_events.append(msg) + + assert rx_whos == {1, 2, 3, 4, 13, 16, 22} + assert len(who3_events) == 29 + assert {e.where for e in who3_events} == {f"#{i}" for i in range(1, 9)} + assert {str(e.what) for e in who3_events} == {"1"} + + +@pytest.mark.parametrize( + "model", + [ + "H/L4684", + "HL4684", + "HC4684", + "H4684", + "L4684", + "BTicino H/L4684", + "003598", # F454 + "003594", # F455 + "003565", # MH200N + "003535", # MH202 + "573992", # F453AV + ], +) +def test_scs_gateway_aliases_disable_point_to_point_zone_status( + hass: HomeAssistant, model: str +) -> None: + """All H/L4684 spellings and MyHOME_Suite catalogue codes disable *#4*Z## zone polling.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "192.0.2.10", + CONF_PORT: 20000, + CONF_PASSWORD: "12345", + CONF_MAC: "00:03:50:aa:bb:cc", + CONF_NAME: model, + }, + ) + handler = MyHOMEGatewayHandler(hass, entry) + assert handler.supports_zone_status is False + + +def test_hl4684_ssdp_profile_identity_and_fw6_who22(hass: HomeAssistant) -> None: + """SSDP H/L4684 binds H4684Profile, dynamically enables WHO 22 on FW >= 6.0.0, and corroborates code 23.""" + # Start without CONF_FIRMWARE so we also verify dynamic profile refresh on *#13**16*6*0*11##! + entry = MockConfigEntry( + domain=DOMAIN, + title="H/L4684 Gateway", + data={ + CONF_HOST: "192.0.2.10", + CONF_PORT: 20000, + CONF_PASSWORD: "12345", + CONF_MAC: "00:03:50:aa:bb:cc", + CONF_NAME: "H/L4684", + CONF_DEVICE_TYPE: "urn:schemas-bticino-it:device:touchscreen:1", + CONF_SSDP_LOCATION: "http://192.0.2.10:49152/description.xml", + CONF_SSDP_ST: "upnp:rootdevice", + }, + options={CONF_FILE_PATH: "/config/myhome.yaml"}, + ) + entry.add_to_hass(hass) + handler = MyHOMEGatewayHandler(hass, entry) + + assert handler.model == "H/L4684" + assert not isinstance(handler.gateway.profile, GenericGatewayProfile) + assert handler.gateway.profile.model_name == "H4684" + assert command_session_limit("H/L4684") == 1 + assert command_session_default("H/L4684") == 1 + + # Before firmware is known (or on 2.0.54), WHO 22 is False; WHO 16 is True; WHO 5/25 are False + assert handler.profile_supports_who(16) is True + assert handler.profile_supports_who(22) is False + assert handler.profile_supports_who(5) is False + assert handler.profile_supports_who(25) is False + assert gateway_supported_whos("H/L4684", "6.0.11") == {1, 2, 4, 15, 16, 18, 22} + assert gateway_supported_whos("H/L4684", "2.0.54") == {1, 2, 4, 15, 16, 18} + assert _firmware_at_least("unknown", (6, 0, 0)) is False + assert _firmware_at_least("6.0", (6, 0, 0)) is True + + # Simulate OWNd <= 2.0.0b11 where get_gateway_profile("H/L4684") returned GenericGatewayProfile + def _legacy_lookup(m: str | None, fw: str | None = None): + if m == "H4684": + return handler.gateway.profile + return GenericGatewayProfile(model_name=m or "Generic") + + assert _profile_for_model("H/L4684", "6.0.11", lookup=_legacy_lookup).model_name == "H4684" + + # Replay the real WHO 13 identification sequence from the H/L4684 trace + for raw in ( + "*#13**15*23##", + "*#13**16*6*0*11##", + "*#13**23*2*6*2##", + "*#13**24*6*0*0##", + ): + evt = OWNEvent.parse(raw) + assert isinstance(evt, OWNGatewayEvent) + handler._handle_gateway_diagnostics(evt) + + # After *#13**16*6*0*11##, firmware 6.0.11 upgrades the bound H4684Profile to support WHO 22! + assert handler.firmware == "6.0.11" + assert handler.profile_supports_who(22) is True + + ident = handler.identification() + assert ident["model"] == "H/L4684" + assert ident["source"] == IDENTIFICATION_SSDP + assert ident["who13_code"] == "23" + assert ident["who13_model"] == "H4684" + assert ident["who13_firmware"] == "6.0.11" + assert ident["who13_kernel"] == "2.6.2" + assert ident["who13_distribution"] == "6.0.0" + assert ident["conflict"] is None + + # Official 2006 code 13 and WHO 1013 OBJECT_MODEL 19 must also corroborate H/L4684 without conflict + evt_official = OWNEvent.parse("*#13**15*13##") + assert isinstance(evt_official, OWNGatewayEvent) + handler._handle_gateway_diagnostics(evt_official) + assert handler.identification()["conflict"] is None + + evt_1013 = OWNEvent.parse("*#1013**1*19*15*5*0##") + assert isinstance(evt_1013, OWNGatewayDiagnosticEvent) + handler._handle_gateway_identity_diagnostics(evt_1013) + assert handler.identification()["who1013_code"] == "19" + assert handler.identification()["conflict"] is None + + +async def test_hl4684_diagnostics_redacts_config_file_path(hass: HomeAssistant) -> None: + """Diagnostics redacts options['config_file_path'] alongside legacy 'file_path'.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "192.0.2.10", CONF_MAC: "00:03:50:aa:bb:cc", CONF_NAME: "H/L4684"}, + options={CONF_FILE_PATH: "/config/myhome.yaml"}, + ) + entry.add_to_hass(hass) + hass.data[DOMAIN] = {} + + diag = await async_get_config_entry_diagnostics(hass, entry) + assert diag["config_entry"]["options"][CONF_FILE_PATH] == "**REDACTED**" + + +async def test_hl4684_trace_f430_4_pure_valve_vs_fancoil_zones(hass: HomeAssistant) -> None: + """Replay WHO 4 frames from H/L4684 trace: F430/4 pure-valve zones retract tentative fan_mode.""" + doc = _load_fixture() + rx_who4 = [ + f["raw"] + for f in doc["data"]["bus_monitor"]["recent_frames"] + if f["direction"] == "rx" and f["who"] == "4" + ] + assert len(rx_who4) == 64 + + gateway = MagicMock() + gateway.mac = "00:03:50:aa:bb:cc" + gateway.log_id = "[H/L4684 gateway]" + gateway.send = AsyncMock() + gateway.send_status_request = AsyncMock() + gateway.request_zone_dimension = MagicMock() + gateway.has_central_unit = True + gateway.supports_zone_status = False + + # Match @andrea-mag's plant config: zones 11, 22, 44, 55, 66, 77, 88 have fan=False; + # zone 33 (PTE - Soggiorno) is explicitly configured with fan=True. + zone_addrs = ("11", "22", "33", "44", "55", "66", "77", "88") + zones: dict[str, MyHOMEClimate] = {} + for where in zone_addrs: + entity = MyHOMEClimate( + hass=hass, + name=f"Zone {where}", + device_id=f"4-{where}", + who="4", + where=where, + heating=True, + cooling=False, + fan=(where == "33"), + standalone=False, + central=False, + manufacturer="BTicino", + model="F430/4", + gateway=gateway, + ) + entity.hass = hass + entity.entity_id = f"climate.zone_{where}" + entity.async_write_ha_state = MagicMock() + zones[where] = entity + + central = MyHOMEClimate( + hass=hass, + name="Central Unit", + device_id="4-#0", + who="4", + where="#0", + heating=True, + cooling=False, + fan=False, + standalone=False, + central=True, + manufacturer="BTicino", + model="3550", + gateway=gateway, + ) + central.hass = hass + central.entity_id = "climate.central_unit" + central.async_write_ha_state = MagicMock() + + for raw in rx_who4: + evt = OWNEvent.parse(raw) + assert isinstance(evt, OWNHeatingEvent) + addr_obj = _zone_address(evt) + addr = addr_obj.where if addr_obj else None + if addr in ("#0", "0"): + central.handle_event(evt) + elif addr in zones: + zones[addr].handle_event(evt) + + expected_temps = { + "11": 20.9, + "22": 22.4, + "33": 21.4, + "44": 23.3, + "55": 22.6, + "66": 22.8, + "77": 22.4, + "88": 22.1, + } + expected_setpoints = { + "11": 19.5, + "22": 17.0, + "33": 16.0, + "44": 18.0, + "55": 18.0, + "66": 17.0, + "77": 18.0, + "88": 18.0, + } + expected_actuators = { + "11": {"1": "off"}, + "22": {"1": "off"}, + "33": {"1": "off", "2": "off"}, + "44": {"1": "off"}, + "55": {"1": "off"}, + "66": {"2": "off", "3": "off"}, + "77": {"1": "off", "2": "off"}, + "88": {"1": "off"}, + } + for where, entity in zones.items(): + assert entity.current_temperature == expected_temps[where] + assert entity.target_temperature == expected_setpoints[where] + assert entity.extra_state_attributes["actuators"] == expected_actuators[where] + + # Receiving 20*5 on each zone triggered _ask_fan_coil_sides() (*#4*ZZ*19##) via request_zone_dimension: + asked_dim19 = { + call.args[0] + for call in gateway.request_zone_dimension.call_args_list + if len(call.args) >= 2 and call.args[1] == 19 + } + assert asked_dim19 == set(zone_addrs) + + # Once Dimension 19 answers *#4*ZZ*19*0*0## (no fan-coil sides, as captured on MyHomeServer1 zones + # 35..70 and F455 issue #674), unconfigured pure-valve zones (11, 22, 44, 55, 66, 77, 88) retract + # tentative FAN_MODE, while explicitly configured Zone 33 (fan=True) keeps FAN_MODE: + for where, entity in zones.items(): + entity.handle_event(OWNHeatingEvent(f"*#4*{where}*19*0*0##")) + attrs = entity.extra_state_attributes + assert attrs["fan_coil_heating"] is False + assert attrs["fan_coil_cooling"] is False + if where == "33": + assert bool(entity.supported_features & ClimateEntityFeature.FAN_MODE) + assert entity.fan_modes == ["auto", "low", "medium", "high"] + assert "fan_mode" in attrs + else: + assert not bool(entity.supported_features & ClimateEntityFeature.FAN_MODE) + assert entity.fan_modes is None + assert entity.fan_mode is None + assert "fan_mode" not in attrs + + # A subsequent *#4*11#1*20*5## or active heating valve report on pure-valve Zone 11 + # must NOT re-enable FAN_MODE, while still tracking actuator state and hvac_action. + z11 = zones["11"] + z11.handle_event(OWNHeatingEvent("*#4*11#1*20*5##")) + assert not bool(z11.supported_features & ClimateEntityFeature.FAN_MODE) + + z11._attr_hvac_mode = HVACMode.HEAT + z11.handle_event(OWNHeatingEvent("*#4*11#1*20*1##")) + assert z11.extra_state_attributes["actuators"] == {"1": "on"} + assert z11.hvac_action == HVACAction.HEATING + assert not bool(z11.supported_features & ClimateEntityFeature.FAN_MODE) + + # Genuine auto-discovered fan-coil zone (e.g. F461 reporting 19*5*0 + 20*5 with fan=False) + # MUST keep FAN_MODE enabled. + fancoil_zone = MyHOMEClimate( + hass=hass, + name="Fan-Coil Zone 99", + device_id="4-99", + who="4", + where="99", + heating=True, + cooling=False, + fan=False, + standalone=False, + central=False, + manufacturer="BTicino", + model="F461", + gateway=gateway, + ) + fancoil_zone.hass = hass + fancoil_zone.entity_id = "climate.zone_99" + fancoil_zone.async_write_ha_state = MagicMock() + fancoil_zone.handle_event(OWNHeatingEvent("*#4*99*19*5*0##")) + fancoil_zone.handle_event(OWNHeatingEvent("*#4*99#1*20*5##")) + assert bool(fancoil_zone.supported_features & ClimateEntityFeature.FAN_MODE) + assert fancoil_zone.fan_mode == "auto" + + # Restoring stale fan_mode attributes alongside fan_coil_heating=False, fan_coil_cooling=False + # on an unconfigured zone must NOT re-enable FAN_MODE. + fresh_z11 = MyHOMEClimate( + hass=hass, + name="Zone 11", + device_id="4-11", + who="4", + where="11", + heating=True, + cooling=False, + fan=False, + standalone=False, + central=False, + manufacturer="BTicino", + model="F430/4", + gateway=gateway, + ) + await fresh_z11.async_restore_last_state( + State( + "climate.zone_11", + HVACMode.HEAT, + { + "temperature": 19.5, + "fan_coil_heating": False, + "fan_coil_cooling": False, + "fan_mode": "auto", + "supported_features": int( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.TURN_ON + | ClimateEntityFeature.TURN_OFF + | ClimateEntityFeature.FAN_MODE + ), + }, + ) + ) + assert not bool(fresh_z11.supported_features & ClimateEntityFeature.FAN_MODE) + assert fresh_z11.fan_mode is None + + +async def test_hl4684_trace_dual_dialect_sound_inventory_deduplication( + hass: HomeAssistant, +) -> None: + """Replay all 28 WHO 16 + WHO 22 frames from H/L4684 FW 6.0.11: 12 amplifier zones deduplicate.""" + doc = _load_fixture() + sound_rx = [ + f["raw"] + for f in doc["data"]["bus_monitor"]["recent_frames"] + if f["direction"] == "rx" and f["who"] in ("16", "22") + ] + assert len(sound_rx) == 28 + + mac = "00:03:50:aa:bb:cc" + entry = MockConfigEntry( + domain=DOMAIN, + title="H/L4684 Gateway", + data={ + CONF_HOST: "192.0.2.10", + CONF_PORT: 20000, + CONF_PASSWORD: "12345", + CONF_MAC: mac, + CONF_NAME: "H/L4684", + CONF_FIRMWARE: "6.0.11", + }, + options={"enable_auto_learning": True}, + unique_id=mac, + ) + entry.add_to_hass(hass) + + with ( + patch( + "custom_components.myhome.gateway.OWNSession.test_connection", + return_value={"Success": True}, + ), + patch("custom_components.myhome.gateway.MyHOMEGatewayHandler.listening_loop"), + patch("custom_components.myhome.gateway.MyHOMEGatewayHandler.sending_loop"), + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + handler: MyHOMEGatewayHandler = hass.data[DOMAIN][mac][CONF_ENTITY] + handler._on_event_connection_state_change(True) + await hass.async_block_till_done() + + for raw in sound_rx: + msg = OWNEvent.parse(raw) + assert msg is not None + await handler._process_message(msg) + await hass.async_block_till_done() + + ent_reg = er.async_get(hass) + mp_entries = [ + e + for e in er.async_entries_for_config_entry(ent_reg, entry.entry_id) + if e.domain == "media_player" + ] + # 24 zone frames + 4 stereo-source frames (101/2#1, 102/2#2 are source inputs, not entities) + # register 12 deduplicated amplifier-zone media_player entities (11..13, 21..22, 31..33, 41..42, 51, 61). + assert len(mp_entries) == 12 diff --git a/tests/test_multi_gateway.py b/tests/test_multi_gateway.py index e0e1ee3dc..c82489fc4 100644 --- a/tests/test_multi_gateway.py +++ b/tests/test_multi_gateway.py @@ -359,7 +359,7 @@ async def test_services_multi_gateway(hass: HomeAssistant, fast_bus_pacing) -> N # covers/climate/audio/energy are delegated away, so its only general request is Lighting (*#1*0##). All are status requests. gw_a.send.assert_not_called() assert [str(c.args[0]) for c in gw_a.send_status_request.call_args_list] == [ - "*#13**0##", "*#13**15##", "*#13**16##", "*#13**23##", "*#13**24##", "*#1*0##", + "*#13**0##", "*#13**15##", "*#13**16##", "*#13**23##", "*#13**24##", "*#1013*0*1##", "*#1*0##", ] gw_b.send.assert_not_called() gw_b.send_status_request.assert_not_called() @@ -380,10 +380,10 @@ async def test_services_multi_gateway(hass: HomeAssistant, fast_bus_pacing) -> N # them out like the startup sweep does), plus the 36 delegated WHO=18 energy queries = 43 gw_b.send.assert_not_called() calls_b = [str(c.args[0]) for c in gw_b.send_status_request.call_args_list] - assert len(calls_b) == 43 - assert calls_b[:5] == ["*#13**0##", "*#13**15##", "*#13**16##", "*#13**23##", "*#13**24##"] - assert calls_b[5:7] == ["*#2*0##", "*#5*0##"] - assert len(calls_b[7:]) == 36 + assert len(calls_b) == 44 + assert calls_b[:6] == ["*#13**0##", "*#13**15##", "*#13**16##", "*#13**23##", "*#13**24##", "*#1013*0*1##"] + assert calls_b[6:8] == ["*#2*0##", "*#5*0##"] + assert len(calls_b[8:]) == 36 # 4. _get_gateway_handler falls back to next(iter(gateways.values())) if no primary hass.config_entries.async_update_entry( diff --git a/tests/test_services.py b/tests/test_services.py index c19d449ca..e0f52982c 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -162,11 +162,13 @@ async def test_sweep_bus_queries_sent(hass: HomeAssistant, attach_gateway) -> No # ... after the gateway queries and before the energy queries names = [call[0] for call in order.mock_calls] paced_at = names.index("send_paced") - assert names[:paced_at] == ["send_status_request"] * 5 + assert names[:paced_at] == ["send_status_request"] * 6 assert names[paced_at + 1:] == ["send_status_request"] * 36 sent_raw = [str(call.args[0]) for call in mock_handler.send_status_request.await_args_list] - assert sent_raw[:5] == ["*#13**0##", "*#13**15##", "*#13**16##", "*#13**23##", "*#13**24##"] + assert sent_raw[:6] == [ + "*#13**0##", "*#13**15##", "*#13**16##", "*#13**23##", "*#13**24##", "*#1013*0*1##", + ] assert not any(raw.startswith(("*#1*", "*#2*", "*#4*", "*#5*", "*#16*")) for raw in sent_raw) mock_handler.send.assert_not_called() # Energy Management (WHO 18) F520 discovery queries @@ -248,7 +250,7 @@ async def test_sweep_bus_follower_delegated_who1(hass: HomeAssistant, attach_gat mock_handler.send_paced.assert_awaited_once_with(["*#1*0##"]) sent_raw = [str(call.args[0]) for call in mock_handler.send_status_request.await_args_list] - assert sent_raw == ["*#13**0##", "*#13**15##", "*#13**16##", "*#13**23##", "*#13**24##"] + assert sent_raw == ["*#13**0##", "*#13**15##", "*#13**16##", "*#13**23##", "*#13**24##", "*#1013*0*1##"] mock_handler.send.assert_not_called() diff --git a/tests/test_startup_requests_per_firmware.py b/tests/test_startup_requests_per_firmware.py index 70c99e0f4..a94599dd9 100644 --- a/tests/test_startup_requests_per_firmware.py +++ b/tests/test_startup_requests_per_firmware.py @@ -32,7 +32,6 @@ from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PASSWORD, CONF_PORT from homeassistant.core import State from OWNd.message import OWNHeatingEvent -from OWNd.profiles import get_gateway_profile from custom_components.myhome.climate import MyHOMEClimate from custom_components.myhome.const import CONF_FIRMWARE @@ -60,9 +59,7 @@ def _entry(model: str) -> MagicMock: def _handler(hass, model: str) -> MyHOMEGatewayHandler: - handler = MyHOMEGatewayHandler(hass, _entry(model)) - handler.gateway.profile = get_gateway_profile(model) - return handler + return MyHOMEGatewayHandler(hass, _entry(model)) def _zone(hass, handler, where="3", **kwargs) -> MyHOMEClimate: @@ -85,7 +82,7 @@ def _zone(hass, handler, where="3", **kwargs) -> MyHOMEClimate: ("F461", False, False), ("MyHomeServer1", False, False), ("MH202", False, False), ("F459", False, False), ("F460", False, False), ("F450", False, False), ("F454", False, True), ("MH200", False, True), ("MH201", False, True), - ("H4684", False, True), ("HL4684", False, True), + ("H4684", False, True), ("HL4684", False, True), ("H/L4684", False, True), ("F453", True, True), ("F453AV", True, True), ("MH200N", True, True), ("Generic", True, True), ], ) @@ -153,10 +150,12 @@ def test_aliased_model_strings_follow_their_profile(hass, model, profile_model): assert handler.zone_temperature_poll_needed is True -def test_ssdp_model_string_hl4684_is_keyed_as_h4684(hass): - """SSDP announces the H4684 as ``HL4684`` (manifest.json); OWNd has no alias for it yet.""" - handler = _handler(hass, "HL4684") - assert handler.gateway.profile.model_name == "HL4684" # the Generic profile keeps the SSDP string +@pytest.mark.parametrize("ssdp_model", ["H/L4684", "HL4684", "HC4684"]) +def test_ssdp_model_string_hl4684_is_keyed_as_h4684(hass, ssdp_model): + """SSDP announces the H4684 as ``H/L4684`` (or ``HL4684``); handler resolves ``H4684Profile``.""" + handler = _handler(hass, ssdp_model) + assert handler.model == ssdp_model + assert handler.gateway.profile.model_name == "H4684" assert handler._model_key == "H4684" assert handler.supports_zone_status is False assert handler.general_zone_request_refused is True @@ -190,7 +189,7 @@ def test_profile_without_who_16_skips_the_sound_request(hass): ("model", "expected"), [ ("MH201", True), ("MH202", True), ("F450", True), ("F454", True), ("MH200", True), ("H4684", True), - ("HL4684", True), ("F459", True), + ("HL4684", True), ("H/L4684", True), ("F459", True), ("F455", False), ("F453", False), ("MH200N", False), # unknown, or the sweep answers ("F460", False), ("F461", False), ("MyHomeServer1", False), ("Generic", False), # polled with *#4*Z## ],