ramses-rf 0.51.4__py3-none-any.whl → 0.51.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ramses_rf/const.py CHANGED
@@ -61,6 +61,8 @@ from ramses_tx.const import ( # noqa: F401
61
61
  SZ_REMAINING_DAYS as SZ_REMAINING_DAYS,
62
62
  SZ_REMAINING_MINS as SZ_REMAINING_MINS,
63
63
  SZ_REMAINING_PERCENT as SZ_REMAINING_PERCENT,
64
+ SZ_REQ_REASON as SZ_REQ_REASON,
65
+ SZ_REQ_SPEED as SZ_REQ_SPEED,
64
66
  SZ_SCHEDULE as SZ_SCHEDULE,
65
67
  SZ_SENSOR as SZ_SENSOR,
66
68
  SZ_SETPOINT as SZ_SETPOINT,
ramses_rf/device/hvac.py CHANGED
@@ -31,6 +31,8 @@ from ramses_rf.const import (
31
31
  SZ_REMAINING_DAYS,
32
32
  SZ_REMAINING_MINS,
33
33
  SZ_REMAINING_PERCENT,
34
+ SZ_REQ_REASON,
35
+ SZ_REQ_SPEED,
34
36
  SZ_SPEED_CAPABILITIES,
35
37
  SZ_SUPPLY_FAN_SPEED,
36
38
  SZ_SUPPLY_FLOW,
@@ -506,6 +508,18 @@ class HvacVentilator(FilterChange): # FAN: RP/31DA, I/31D[9A]
506
508
  def remaining_mins(self) -> int | None:
507
509
  return self._msg_value(Code._31DA, key=SZ_REMAINING_MINS)
508
510
 
511
+ @property
512
+ def request_fan_speed(self) -> float | None:
513
+ return self._msg_value(Code._2210, key=SZ_REQ_SPEED)
514
+
515
+ @property
516
+ def request_src(self) -> str | None:
517
+ """
518
+ Orcon, others?
519
+ :return: source sensor of auto speed request: IDL, CO2 or HUM
520
+ """
521
+ return self._msg_value(Code._2210, key=SZ_REQ_REASON)
522
+
509
523
  @property
510
524
  def speed_cap(self) -> int | None:
511
525
  return self._msg_value(Code._31DA, key=SZ_SPEED_CAPABILITIES)
ramses_rf/dispatcher.py CHANGED
@@ -91,7 +91,7 @@ def _create_devices_from_addrs(gwy: Gateway, this: Message) -> None:
91
91
  if not gwy.config.enable_eavesdrop:
92
92
  return
93
93
 
94
- if not isinstance(this.dst, Device) and this.src is not gwy.hgi: # type: ignore[unreachable]
94
+ if not isinstance(this.dst, Device) and this.src != gwy.hgi: # type: ignore[unreachable]
95
95
  with contextlib.suppress(LookupError):
96
96
  this.dst = gwy.get_device(this.dst.id) # type: ignore[assignment]
97
97
 
@@ -188,9 +188,9 @@ def process_msg(gwy: Gateway, msg: Message) -> None:
188
188
  def logger_xxxx(msg: Message) -> None:
189
189
  if _DBG_FORCE_LOG_MESSAGES:
190
190
  _LOGGER.warning(msg)
191
- elif msg.src is not gwy.hgi or (msg.code != Code._PUZZ and msg.verb != RQ):
191
+ elif msg.src != gwy.hgi or (msg.code != Code._PUZZ and msg.verb != RQ):
192
192
  _LOGGER.info(msg)
193
- elif msg.src is not gwy.hgi or msg.verb != RQ:
193
+ elif msg.src != gwy.hgi or msg.verb != RQ:
194
194
  _LOGGER.info(msg)
195
195
  elif _LOGGER.getEffectiveLevel() == logging.DEBUG:
196
196
  _LOGGER.info(msg)
@@ -215,7 +215,7 @@ def process_msg(gwy: Gateway, msg: Message) -> None:
215
215
  if (
216
216
  msg.src._SLUG != DevType.HGI # avoid: msg.src.id != gwy.hgi.id
217
217
  and msg.verb != I_
218
- and msg.dst is not msg.src
218
+ and msg.dst != msg.src
219
219
  ):
220
220
  # HGI80 can do what it likes
221
221
  # receiving an I isn't currently in the schema & so can't yet be tested
@@ -234,10 +234,10 @@ def process_msg(gwy: Gateway, msg: Message) -> None:
234
234
  # TODO: only be for fully-faked (not Fakable) dst (it picks up via RF if not)
235
235
 
236
236
  if msg.code == Code._1FC9 and msg.payload[SZ_PHASE] == SZ_OFFER:
237
- devices = [d for d in gwy.devices if d is not msg.src and d._is_binding]
237
+ devices = [d for d in gwy.devices if d != msg.src and d._is_binding]
238
238
 
239
239
  elif msg.dst == ALL_DEV_ADDR: # some offers use dst=63:, so after 1FC9 offer
240
- devices = [d for d in gwy.devices if d is not msg.src and d.is_faked]
240
+ devices = [d for d in gwy.devices if d != msg.src and d.is_faked]
241
241
 
242
242
  elif msg.dst is not msg.src and isinstance(msg.dst, Fakeable): # type: ignore[unreachable]
243
243
  # to eavesdrop pkts from other devices, but relevant to this device
ramses_rf/entity_base.py CHANGED
@@ -387,7 +387,7 @@ class _MessageDB(_Entity):
387
387
  codes = {
388
388
  k: (CODES_SCHEMA[k][SZ_NAME] if k in CODES_SCHEMA else None)
389
389
  for k in sorted(self._msgs)
390
- if self._msgs[k].src is (self if hasattr(self, "addr") else self.ctl)
390
+ if self._msgs[k].src == (self if hasattr(self, "addr") else self.ctl)
391
391
  }
392
392
 
393
393
  return {"_sent": list(codes.keys())}
ramses_rf/system/heat.py CHANGED
@@ -193,19 +193,19 @@ class SystemBase(Parent, Entity): # 3B00 (multi-relay)
193
193
  this.code in (Code._22D9, Code._3220) and this.verb == RQ
194
194
  ): # TODO: RPs too?
195
195
  # dst could be an Address...
196
- if this.src is self.ctl and isinstance(this.dst, OtbGateway): # type: ignore[unreachable]
196
+ if this.src == self.ctl and isinstance(this.dst, OtbGateway): # type: ignore[unreachable]
197
197
  app_cntrl = this.dst # type: ignore[unreachable]
198
198
 
199
199
  elif this.code == Code._3EF0 and this.verb == RQ:
200
200
  # dst could be an Address...
201
- if this.src is self.ctl and isinstance(
201
+ if this.src == self.ctl and isinstance(
202
202
  this.dst, # type: ignore[unreachable]
203
203
  BdrSwitch | OtbGateway,
204
204
  ):
205
205
  app_cntrl = this.dst # type: ignore[unreachable]
206
206
 
207
207
  elif this.code == Code._3B00 and this.verb == I_ and prev is not None:
208
- if this.src is self.ctl and isinstance(prev.src, BdrSwitch): # type: ignore[unreachable]
208
+ if this.src == self.ctl and isinstance(prev.src, BdrSwitch): # type: ignore[unreachable]
209
209
  if prev.code == this.code and prev.verb == this.verb: # type: ignore[unreachable]
210
210
  app_cntrl = prev.src
211
211
 
ramses_rf/system/zones.py CHANGED
@@ -266,7 +266,7 @@ class DhwZone(ZoneSchedule): # CS92A
266
266
  # self._get_dhw(sensor=this.dst)
267
267
 
268
268
  assert (
269
- msg.src is self.ctl
269
+ msg.src == self.ctl
270
270
  and msg.code in (Code._0005, Code._000C, Code._10A0, Code._1260, Code._1F41)
271
271
  or msg.payload.get(SZ_DOMAIN_ID) in (F9, FA)
272
272
  or msg.payload.get(SZ_ZONE_IDX) == "HW"
@@ -633,13 +633,13 @@ class Zone(ZoneSchedule):
633
633
  self._update_schema(**{SZ_CLASS: ZON_ROLE_MAP[ZoneRole.UFH]})
634
634
 
635
635
  assert (
636
- msg.src is self.ctl or msg.src.type == DEV_TYPE_MAP.UFC
636
+ msg.src == self.ctl or msg.src.type == DEV_TYPE_MAP.UFC
637
637
  ) and ( # DEX
638
638
  isinstance(msg.payload, dict)
639
639
  or [d for d in msg.payload if d.get(SZ_ZONE_IDX) == self.idx]
640
640
  ), f"msg inappropriately routed to {self}"
641
641
 
642
- assert (msg.src is self.ctl or msg.src.type == DEV_TYPE_MAP.UFC) and ( # DEX
642
+ assert (msg.src == self.ctl or msg.src.type == DEV_TYPE_MAP.UFC) and ( # DEX
643
643
  isinstance(msg.payload, list)
644
644
  or msg.code == Code._0005
645
645
  or msg.payload.get(SZ_ZONE_IDX) == self.idx
ramses_rf/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """RAMSES RF - a RAMSES-II protocol decoder & analyser (application layer)."""
2
2
 
3
- __version__ = "0.51.4"
3
+ __version__ = "0.51.5"
4
4
  VERSION = __version__
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ramses_rf
3
- Version: 0.51.4
3
+ Version: 0.51.5
4
4
  Summary: A stateful RAMSES-II protocol decoder & analyser.
5
5
  Project-URL: Homepage, https://github.com/ramses-rf/ramses_rf
6
6
  Project-URL: Bug Tracker, https://github.com/ramses-rf/ramses_rf/issues
@@ -6,50 +6,50 @@ ramses_cli/utils/cat_slow.py,sha256=AhUpM5gnegCitNKU-JGHn-DrRzSi-49ZR1Qw6lxe_t8,
6
6
  ramses_cli/utils/convert.py,sha256=D_YiCyX5na9pgC-_NhBlW9N1dgRKUK-uLtLBfofjzZM,1804
7
7
  ramses_rf/__init__.py,sha256=zONFBiRdf07cPTSxzr2V3t-b3CGokZjT9SGit4JUKRA,1055
8
8
  ramses_rf/binding_fsm.py,sha256=uZAOl3i19KCXqqlaLJWkEqMMP7NJBhVPW3xTikQD1fY,25996
9
- ramses_rf/const.py,sha256=DSo4ROWDlOlcdXQdrpAF17vOsTLgmf2u0UppjYa5qJI,5390
9
+ ramses_rf/const.py,sha256=L3z31CZ-xqno6oZp_h-67CB_5tDDqTwSWXsqRtsjMcs,5460
10
10
  ramses_rf/database.py,sha256=6k5MLtK5Lplz8THfluQoQU-eniUkqSwEUMvVW7VyGhI,9880
11
- ramses_rf/dispatcher.py,sha256=b7Cg1vAP6FECC6GeZsJ0BZVqy-ZjJTXhZquzcwE87WI,11221
12
- ramses_rf/entity_base.py,sha256=vH4lmwXnylSM-1MWmat0_QRSNVCRi3iVhcqj9O41Pms,39602
11
+ ramses_rf/dispatcher.py,sha256=L-XQ-mbE3HyyxExkhe5kfD4elKlGZnV0kHT4OHTWzE8,11197
12
+ ramses_rf/entity_base.py,sha256=EaIyGIu3fU1Ks30jdqS69nYmcWRKXaWx81oBGDiaXGw,39602
13
13
  ramses_rf/exceptions.py,sha256=rzVZDcYxFH7BjUAQ3U1fHWtgBpwk3BgjX1TO1L1iM8c,2538
14
14
  ramses_rf/gateway.py,sha256=vqoTEb6QXnwaIMa66oed_3LEVvlyQ3flsAAMliEEvVA,20921
15
15
  ramses_rf/helpers.py,sha256=LcrVLqnF2qJWqXrC7UXKOQE8khCT3OhoTpZ_ZVBjw3A,4249
16
16
  ramses_rf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  ramses_rf/schemas.py,sha256=mYOUZOH5OIDNBxRM2vd8POzDWEEmLhxh5UtqjTpFNek,13287
18
- ramses_rf/version.py,sha256=zEZhrTC1gMQzcGZ3ZSkZrhF_OBj8Dnlv5VhehgnT3_s,125
18
+ ramses_rf/version.py,sha256=2CNEExhYqbwGxmDWyifsA3RubG0bCgN6a1NUTKnIfBM,125
19
19
  ramses_rf/device/__init__.py,sha256=sUbH5dhbYFXSoM_TPFRutpRutBRpup7_cQ9smPtDTy8,4858
20
20
  ramses_rf/device/base.py,sha256=V2YzRhdxrTqfHYrCBq6pJsYdTgAx8gGzfdo8pkbeEo8,17450
21
21
  ramses_rf/device/heat.py,sha256=2sCsggySVcuTzyXDmgWy76QbhlU5MQWSejy3zgI5BDE,54242
22
- ramses_rf/device/hvac.py,sha256=QAlWx6jBArbHzZD5Mm1wZPmIbrQrw4ljbTDV0P4IH3I,23438
22
+ ramses_rf/device/hvac.py,sha256=fRzFGQD6zrkii0Ns9EV1uqh8MTXHn2CO3LZ1WOXLjhs,23835
23
23
  ramses_rf/system/__init__.py,sha256=uZLKio3gLlBzePa2aDQ1nxkcp1YXOGrn6iHTG8LiNIw,711
24
24
  ramses_rf/system/faultlog.py,sha256=GdGmVGT3137KsTlV_nhccgIFEmYu6DFsLTn4S-8JSok,12799
25
- ramses_rf/system/heat.py,sha256=dARzcwL39JGwOBJkKJBi0_i7rr8IvY-qaNmWmgJLpdo,39223
25
+ ramses_rf/system/heat.py,sha256=3jaFEChU-HlWCRMY1y7u09s7AH4hT0pC63hnqwdmZOc,39223
26
26
  ramses_rf/system/schedule.py,sha256=Ts6tdZPTQLV5NkgwA73tPa5QUsnZNIIuYoKC-8VsXDk,18808
27
- ramses_rf/system/zones.py,sha256=QwRtSHY5c-Amcs6JD16uQcimOsEQTZcMm1dW-pqEFqM,36041
27
+ ramses_rf/system/zones.py,sha256=2_c1YHrGbObUeEqjcqJDO08Fo2Mr1aYn4VorNmfFaKk,36041
28
28
  ramses_tx/__init__.py,sha256=wJ7Ntx-0AyJwYwSG8OrFMpxDLXs6GbECbCcYhq98mSA,3162
29
29
  ramses_tx/address.py,sha256=2640K3sXzogZtd4-tSxwVjYEEXcFE1DgmtvZlTMM5mE,8444
30
30
  ramses_tx/command.py,sha256=g5PBf9JnuygveyaYrqIuV8wIn7grm0evuqKy9Cp1oaA,53844
31
- ramses_tx/const.py,sha256=B2db8Yxks-lMNsQAK1DoPkF1gvwNIacLmKwXuApUyLk,30221
31
+ ramses_tx/const.py,sha256=ILZvbIp9qI2ZTmGDDJ0YFoxOH3GtQ7g9MT3vAUtHWAE,30291
32
32
  ramses_tx/exceptions.py,sha256=FJSU9YkvpKjs3yeTqUJX1o3TPFSe_B01gRGIh9b3PNc,2632
33
33
  ramses_tx/fingerprints.py,sha256=nfftA1E62HQnb-eLt2EqjEi_la0DAoT0wt-PtTMie0s,11974
34
34
  ramses_tx/frame.py,sha256=9lUVh8gAMXNRAolfFw2WuWANjn24AWkmscuM9Tm5imE,22036
35
35
  ramses_tx/gateway.py,sha256=FE5MWA1eIE9JATA2vRoBSQ8fAzqp7TqAm3Ds3k1KnKE,11267
36
- ramses_tx/helpers.py,sha256=WJ5JtAT9iyhkcW53AIPNPuvGEUWFwLumZc-mCG2kIOc,32236
36
+ ramses_tx/helpers.py,sha256=qDJTsTU2tfSZrfJuFi1q29efkkHzqRtg85M6ItQH6qA,32247
37
37
  ramses_tx/logger.py,sha256=7vUpcfOFMW95juMWDx5dhUOqV8DTsindZ-Qz2aCmEoA,11073
38
38
  ramses_tx/message.py,sha256=J1wvVkLPJQr2ffKCUQYSWwLPzRTZBC0zUU5W9DkN3hU,13190
39
39
  ramses_tx/opentherm.py,sha256=58PXz9l5x8Ou6Fm3y-R_UnGHCYahoi2RKIDdYStUMzk,42378
40
40
  ramses_tx/packet.py,sha256=NGunaGCkEjhTp9t4mARK5e7kbqT-Z_JKCH7ibMYMJXU,7357
41
- ramses_tx/parsers.py,sha256=eU5dqbbw1vzWDFxDhyNPy2j6t_LQN56mRJa0A-PeKiE,109411
41
+ ramses_tx/parsers.py,sha256=uwu_HsMjpsSyMYzoBpVnAxu2pqBJiQDL5ls8_B60V7c,109708
42
42
  ramses_tx/protocol.py,sha256=ifj3qwcQivjQDaQUwM94xp-U8Pmef6zwSH7mav8DLWA,28867
43
43
  ramses_tx/protocol_fsm.py,sha256=YhHkTqbl8w-myimsOjV50uIFgg9HiApwPU7xA_jg5nU,26827
44
44
  ramses_tx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
45
  ramses_tx/ramses.py,sha256=GnZwvx-HSVFdjXfUen6aWClDtrAmYaKwbrWl-LsyKO4,52045
46
46
  ramses_tx/schemas.py,sha256=h2AcArVROy1_C4n6F0Crj4e-2BxXxH74xogFlc6nKHI,12769
47
- ramses_tx/transport.py,sha256=-IO8UY85OOytciX3h7tFN58BBDtI3TEoOgmUmv-LiNc,56288
47
+ ramses_tx/transport.py,sha256=aLpULRSivoJqzH8GDPRDcbehETOhFflEqmHbaniGLvg,56210
48
48
  ramses_tx/typed_dicts.py,sha256=4ZT50M-Cuwy2ljAIorwoxEJ9c737xUHrUxX9wTh79xE,10834
49
49
  ramses_tx/typing.py,sha256=eF2SlPWhNhEFQj6WX2AhTXiyRQVXYnFutiepllYl2rI,5042
50
- ramses_tx/version.py,sha256=QqJQBBdWjuoIvxrhzIH4ysjOp5bzB0KI1TdFhr9c6og,123
51
- ramses_rf-0.51.4.dist-info/METADATA,sha256=odaoeguG-xZAlWq8nYF4PHVK3SV5mzJRi1QdD4IBKcE,3906
52
- ramses_rf-0.51.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
- ramses_rf-0.51.4.dist-info/entry_points.txt,sha256=NnyK29baOCNg8DinPYiZ368h7MTH7bgTW26z2A1NeIE,50
54
- ramses_rf-0.51.4.dist-info/licenses/LICENSE,sha256=-Kc35W7l1UkdiQ4314_yVWv7vDDrg7IrJfMLUiq6Nfs,1074
55
- ramses_rf-0.51.4.dist-info/RECORD,,
50
+ ramses_tx/version.py,sha256=uQWGeAYKPd3vO8uf3sjQlNE_kdbcsQl2FfC3y2J7NJg,123
51
+ ramses_rf-0.51.5.dist-info/METADATA,sha256=5qyM_GCuZGD5rzMMGOjQUfNR4NJAd5TOQwIrBnoSEmY,3906
52
+ ramses_rf-0.51.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
+ ramses_rf-0.51.5.dist-info/entry_points.txt,sha256=NnyK29baOCNg8DinPYiZ368h7MTH7bgTW26z2A1NeIE,50
54
+ ramses_rf-0.51.5.dist-info/licenses/LICENSE,sha256=-Kc35W7l1UkdiQ4314_yVWv7vDDrg7IrJfMLUiq6Nfs,1074
55
+ ramses_rf-0.51.5.dist-info/RECORD,,
ramses_tx/const.py CHANGED
@@ -88,6 +88,8 @@ SZ_REL_HUMIDITY: Final = "rel_humidity"
88
88
  SZ_REMAINING_DAYS: Final = "days_remaining"
89
89
  SZ_REMAINING_MINS: Final = "remaining_mins"
90
90
  SZ_REMAINING_PERCENT: Final = "percent_remaining"
91
+ SZ_REQ_REASON: Final = "req_reason"
92
+ SZ_REQ_SPEED: Final = "req_speed"
91
93
  SZ_SUPPLY_FAN_SPEED: Final = "supply_fan_speed"
92
94
  SZ_SUPPLY_FLOW: Final = "supply_flow"
93
95
  SZ_SUPPLY_TEMP: Final = "supply_temp"
ramses_tx/helpers.py CHANGED
@@ -757,7 +757,7 @@ def parse_fan_info(value: HexStr2) -> PayDictT.FAN_INFO:
757
757
  }
758
758
 
759
759
 
760
- # 31DA[38:40]
760
+ # 31DA[38:40], also 2210
761
761
  def parse_exhaust_fan_speed(value: HexStr2) -> PayDictT.EXHAUST_FAN_SPEED:
762
762
  """Return the exhaust fan speed (% of max speed)."""
763
763
  return _parse_fan_speed(SZ_EXHAUST_FAN_SPEED, value) # type: ignore[return-value]
ramses_tx/parsers.py CHANGED
@@ -72,6 +72,7 @@ from .const import (
72
72
  SZ_RELAY_DEMAND,
73
73
  SZ_REMAINING_DAYS,
74
74
  SZ_REMAINING_PERCENT,
75
+ SZ_REQ_REASON,
75
76
  SZ_SETPOINT,
76
77
  SZ_SETPOINT_BOUNDS,
77
78
  SZ_SYSTEM_MODE,
@@ -1378,22 +1379,18 @@ def parser_1fd4(payload: str, msg: Message) -> PayDictT._1FD4:
1378
1379
  return {"ticker": int(payload[2:], 16)}
1379
1380
 
1380
1381
 
1381
- # WIP: unknown, HVAC
1382
+ # WIP: HVAC auto requests (confirmed for Orcon, others?)
1382
1383
  def parser_2210(payload: str, msg: Message) -> dict[str, Any]:
1383
1384
  try:
1384
1385
  assert msg.verb in (RP, I_) or payload == "00"
1385
- assert payload[10:12] == payload[38:40] and payload[
1386
- 10:12
1387
- ] in ( # auto requested fan speed step?
1388
- "58",
1389
- "64",
1390
- "96",
1391
- "FF",
1392
- ), f"expected req.speed? (58|64|96|FF), not {payload[10:12]}"
1386
+ assert payload[10:12] == payload[38:40], (
1387
+ f"expected byte 19 {payload[10:12]}, not {payload[38:40]}"
1388
+ ) # auto requested fan speed %. Identical [38:40] is for supply?
1393
1389
  assert payload[20:22] == payload[48:50] and payload[20:22] in (
1394
- "00",
1395
- "03",
1396
- ), f"expected byte 10 (00|03), not {payload[20:22]}"
1390
+ "00", # idle
1391
+ "02", # requested by CO2 level/sensor
1392
+ "03", # requested by humidity level/sensor
1393
+ ), f"expected req_reason (00|02|03), not {payload[20:22]}"
1397
1394
  assert payload[78:80] in ("00", "02"), (
1398
1395
  f"expected byte 39 (00|02), not {payload[78:80]}"
1399
1396
  )
@@ -1407,9 +1404,17 @@ def parser_2210(payload: str, msg: Message) -> dict[str, Any]:
1407
1404
  except AssertionError as err:
1408
1405
  _LOGGER.warning(f"{msg!r} < {_INFORM_DEV_MSG} ({err})")
1409
1406
 
1407
+ _req = "IDL"
1408
+ if payload[20:22] == "02":
1409
+ _req = "CO2"
1410
+ elif payload[20:22] == "03":
1411
+ _req = "HUM"
1412
+
1410
1413
  return {
1411
- "unknown_10": payload[10:12],
1412
- "unknown_20": payload[20:22],
1414
+ **parse_exhaust_fan_speed(
1415
+ payload[10:12]
1416
+ ), # for Orcon: 29 hex == 41 decimal divided by 2 gives 20.5 (%)
1417
+ SZ_REQ_REASON: _req,
1413
1418
  "unknown_78": payload[78:80],
1414
1419
  "unknown_80": payload[80:82],
1415
1420
  "unknown_82": payload[82:],
ramses_tx/transport.py CHANGED
@@ -1073,7 +1073,7 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
1073
1073
  self._connecting = True
1074
1074
  try:
1075
1075
  self.client.connect_async(
1076
- self._broker_url.hostname, # type: ignore[arg-type]
1076
+ str(self._broker_url.hostname or "localhost"),
1077
1077
  self._broker_url.port or 1883,
1078
1078
  60,
1079
1079
  )
@@ -1172,10 +1172,18 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
1172
1172
  self,
1173
1173
  client: mqtt.Client,
1174
1174
  userdata: Any,
1175
- reason_code: Any,
1176
- properties: Any | None,
1175
+ *args: Any,
1176
+ **kwargs: Any,
1177
1177
  ) -> None:
1178
- _LOGGER.warning(f"MQTT disconnected: {reason_code.getName()}")
1178
+ # Handle different paho-mqtt callback signatures
1179
+ reason_code = args[0] if len(args) >= 1 else None
1180
+
1181
+ reason_name = (
1182
+ reason_code.getName()
1183
+ if reason_code is not None and hasattr(reason_code, "getName")
1184
+ else str(reason_code)
1185
+ )
1186
+ _LOGGER.warning(f"MQTT disconnected: {reason_name}")
1179
1187
 
1180
1188
  was_connected = self._connected
1181
1189
  self._connected = False
@@ -1191,13 +1199,9 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
1191
1199
  self._protocol.pause_writing()
1192
1200
 
1193
1201
  # Only attempt reconnection if we didn't deliberately disconnect
1194
- if not self._closing and not reason_code.is_failure:
1195
- # This was an unexpected disconnect, schedule reconnection
1196
- _LOGGER.debug("MQTT unexpected disconnect - scheduling reconnection")
1197
- self._schedule_reconnect()
1198
- elif reason_code.is_failure and not self._closing:
1199
- # Connection failed, also schedule reconnection
1200
- _LOGGER.debug("MQTT connection failed - scheduling reconnection")
1202
+
1203
+ if not self._closing:
1204
+ # Schedule reconnection for any disconnect (unexpected or failure)
1201
1205
  self._schedule_reconnect()
1202
1206
 
1203
1207
  def _create_connection(self, msg: mqtt.MQTTMessage) -> None:
ramses_tx/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """RAMSES RF - a RAMSES-II protocol decoder & analyser (transport layer)."""
2
2
 
3
- __version__ = "0.51.4"
3
+ __version__ = "0.51.5"
4
4
  VERSION = __version__