ramses-rf 0.51.3__py3-none-any.whl → 0.51.4__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/version.py +1 -1
- {ramses_rf-0.51.3.dist-info → ramses_rf-0.51.4.dist-info}/METADATA +1 -1
- {ramses_rf-0.51.3.dist-info → ramses_rf-0.51.4.dist-info}/RECORD +10 -10
- ramses_tx/parsers.py +15 -6
- ramses_tx/ramses.py +1 -1
- ramses_tx/transport.py +74 -11
- ramses_tx/version.py +1 -1
- {ramses_rf-0.51.3.dist-info → ramses_rf-0.51.4.dist-info}/WHEEL +0 -0
- {ramses_rf-0.51.3.dist-info → ramses_rf-0.51.4.dist-info}/entry_points.txt +0 -0
- {ramses_rf-0.51.3.dist-info → ramses_rf-0.51.4.dist-info}/licenses/LICENSE +0 -0
ramses_rf/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ramses_rf
|
|
3
|
-
Version: 0.51.
|
|
3
|
+
Version: 0.51.4
|
|
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
|
|
@@ -15,7 +15,7 @@ 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=
|
|
18
|
+
ramses_rf/version.py,sha256=zEZhrTC1gMQzcGZ3ZSkZrhF_OBj8Dnlv5VhehgnT3_s,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
|
|
@@ -38,18 +38,18 @@ 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=
|
|
41
|
+
ramses_tx/parsers.py,sha256=eU5dqbbw1vzWDFxDhyNPy2j6t_LQN56mRJa0A-PeKiE,109411
|
|
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
|
-
ramses_tx/ramses.py,sha256=
|
|
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
|
|
47
|
+
ramses_tx/transport.py,sha256=-IO8UY85OOytciX3h7tFN58BBDtI3TEoOgmUmv-LiNc,56288
|
|
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=
|
|
51
|
-
ramses_rf-0.51.
|
|
52
|
-
ramses_rf-0.51.
|
|
53
|
-
ramses_rf-0.51.
|
|
54
|
-
ramses_rf-0.51.
|
|
55
|
-
ramses_rf-0.51.
|
|
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,,
|
ramses_tx/parsers.py
CHANGED
|
@@ -1382,18 +1382,27 @@ def parser_1fd4(payload: str, msg: Message) -> PayDictT._1FD4:
|
|
|
1382
1382
|
def parser_2210(payload: str, msg: Message) -> dict[str, Any]:
|
|
1383
1383
|
try:
|
|
1384
1384
|
assert msg.verb in (RP, I_) or payload == "00"
|
|
1385
|
-
assert payload[10:12] == payload[38:40] and payload[
|
|
1385
|
+
assert payload[10:12] == payload[38:40] and payload[
|
|
1386
|
+
10:12
|
|
1387
|
+
] in ( # auto requested fan speed step?
|
|
1386
1388
|
"58",
|
|
1389
|
+
"64",
|
|
1387
1390
|
"96",
|
|
1388
1391
|
"FF",
|
|
1389
|
-
), f"expected (58|96|FF), not {payload[10:12]}"
|
|
1392
|
+
), f"expected req.speed? (58|64|96|FF), not {payload[10:12]}"
|
|
1390
1393
|
assert payload[20:22] == payload[48:50] and payload[20:22] in (
|
|
1391
1394
|
"00",
|
|
1392
1395
|
"03",
|
|
1393
|
-
), f"expected (00|03), not {payload[
|
|
1394
|
-
assert payload[78:80] in ("00", "02"),
|
|
1395
|
-
|
|
1396
|
-
|
|
1396
|
+
), f"expected byte 10 (00|03), not {payload[20:22]}"
|
|
1397
|
+
assert payload[78:80] in ("00", "02"), (
|
|
1398
|
+
f"expected byte 39 (00|02), not {payload[78:80]}"
|
|
1399
|
+
)
|
|
1400
|
+
assert payload[80:82] in ("01", "08"), (
|
|
1401
|
+
f"expected byte 40 (01|08), not {payload[80:82]}"
|
|
1402
|
+
)
|
|
1403
|
+
assert payload[82:] in ("00", "40"), (
|
|
1404
|
+
f"expected byte 41- (00|40), not {payload[82:]}"
|
|
1405
|
+
)
|
|
1397
1406
|
|
|
1398
1407
|
except AssertionError as err:
|
|
1399
1408
|
_LOGGER.warning(f"{msg!r} < {_INFORM_DEV_MSG} ({err})")
|
ramses_tx/ramses.py
CHANGED
|
@@ -1059,7 +1059,7 @@ _DEV_KLASSES_HVAC: dict[str, dict[Code, dict[VerbT, Any]]] = {
|
|
|
1059
1059
|
Code._12C8: {I_: {}},
|
|
1060
1060
|
Code._1470: {RP: {}},
|
|
1061
1061
|
Code._1F09: {I_: {}, RP: {}},
|
|
1062
|
-
Code._1FC9: {W_: {}},
|
|
1062
|
+
Code._1FC9: {I_: {}, W_: {}},
|
|
1063
1063
|
Code._2210: {I_: {}, RP: {}},
|
|
1064
1064
|
Code._22E0: {RP: {}},
|
|
1065
1065
|
Code._22E5: {RP: {}},
|
ramses_tx/transport.py
CHANGED
|
@@ -778,7 +778,8 @@ class FileTransport(_ReadTransport, _FileTransportAbstractor):
|
|
|
778
778
|
while not self._reading:
|
|
779
779
|
await asyncio.sleep(0.001)
|
|
780
780
|
self._frame_read(dtm_str, pkt_line)
|
|
781
|
-
|
|
781
|
+
await asyncio.sleep(0)
|
|
782
|
+
# NOTE: instable without, big performance penalty if delay >0
|
|
782
783
|
|
|
783
784
|
elif isinstance(self._pkt_source, str): # file_name, used in client parse
|
|
784
785
|
# open file file_name before reading
|
|
@@ -794,7 +795,8 @@ class FileTransport(_ReadTransport, _FileTransportAbstractor):
|
|
|
794
795
|
] != "#":
|
|
795
796
|
self._frame_read(dtm_pkt_line[:26], dtm_pkt_line[27:])
|
|
796
797
|
# this is where the parsing magic happens!
|
|
797
|
-
|
|
798
|
+
await asyncio.sleep(0)
|
|
799
|
+
# NOTE: instable without, big performance penalty if delay >0
|
|
798
800
|
except FileNotFoundError as err:
|
|
799
801
|
_LOGGER.warning(f"Correct the packet file name; {err}")
|
|
800
802
|
elif isinstance(self._pkt_source, TextIOWrapper): # used by client monitor
|
|
@@ -804,7 +806,8 @@ class FileTransport(_ReadTransport, _FileTransportAbstractor):
|
|
|
804
806
|
# can be blank lines in annotated log files
|
|
805
807
|
if (dtm_pkt_line := dtm_pkt_line.strip()) and dtm_pkt_line[:1] != "#":
|
|
806
808
|
self._frame_read(dtm_pkt_line[:26], dtm_pkt_line[27:])
|
|
807
|
-
await asyncio.sleep(0)
|
|
809
|
+
await asyncio.sleep(0)
|
|
810
|
+
# NOTE: instable without, big performance penalty if delay >0
|
|
808
811
|
else:
|
|
809
812
|
raise exc.TransportSourceInvalid(
|
|
810
813
|
f"Packet source is not dict, TextIOWrapper or str: {self._pkt_source:!r}"
|
|
@@ -1035,6 +1038,7 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
|
|
|
1035
1038
|
|
|
1036
1039
|
self._connected = False
|
|
1037
1040
|
self._connecting = False
|
|
1041
|
+
self._connection_established = False # Track if initial connection was made
|
|
1038
1042
|
self._extra[SZ_IS_EVOFW3] = True
|
|
1039
1043
|
|
|
1040
1044
|
# Reconnection settings
|
|
@@ -1136,8 +1140,21 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
|
|
|
1136
1140
|
self._reconnect_task.cancel()
|
|
1137
1141
|
self._reconnect_task = None
|
|
1138
1142
|
|
|
1143
|
+
# Subscribe to base topic to see 'online' messages
|
|
1139
1144
|
self.client.subscribe(self._topic_base) # hope to see 'online' message
|
|
1140
1145
|
|
|
1146
|
+
# Also subscribe to data topics with wildcard for reliability after reconnect
|
|
1147
|
+
# This ensures we get data even if we miss the 'online' message
|
|
1148
|
+
if self._topic_base.endswith("/+"):
|
|
1149
|
+
data_wildcard = self._topic_base.replace("/+", "/+/rx")
|
|
1150
|
+
self.client.subscribe(data_wildcard, qos=self._mqtt_qos)
|
|
1151
|
+
_LOGGER.debug(f"Subscribed to data wildcard: {data_wildcard}")
|
|
1152
|
+
|
|
1153
|
+
# If we already have specific topics, re-subscribe to them
|
|
1154
|
+
if hasattr(self, "_topic_sub") and self._topic_sub:
|
|
1155
|
+
self.client.subscribe(self._topic_sub, qos=self._mqtt_qos)
|
|
1156
|
+
_LOGGER.debug(f"Re-subscribed to specific topic: {self._topic_sub}")
|
|
1157
|
+
|
|
1141
1158
|
def _on_connect_fail(
|
|
1142
1159
|
self,
|
|
1143
1160
|
client: mqtt.Client,
|
|
@@ -1160,14 +1177,27 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
|
|
|
1160
1177
|
) -> None:
|
|
1161
1178
|
_LOGGER.warning(f"MQTT disconnected: {reason_code.getName()}")
|
|
1162
1179
|
|
|
1180
|
+
was_connected = self._connected
|
|
1163
1181
|
self._connected = False
|
|
1164
1182
|
|
|
1183
|
+
# If we were previously connected and had established communication,
|
|
1184
|
+
# notify that the device is now offline
|
|
1185
|
+
if was_connected and hasattr(self, "_topic_sub") and self._topic_sub:
|
|
1186
|
+
device_topic = self._topic_sub[:-3] # Remove "/rx" suffix
|
|
1187
|
+
_LOGGER.warning(f"{self}: the MQTT device is offline: {device_topic}")
|
|
1188
|
+
|
|
1189
|
+
# Pause writing since device is offline
|
|
1190
|
+
if hasattr(self, "_protocol"):
|
|
1191
|
+
self._protocol.pause_writing()
|
|
1192
|
+
|
|
1165
1193
|
# Only attempt reconnection if we didn't deliberately disconnect
|
|
1166
1194
|
if not self._closing and not reason_code.is_failure:
|
|
1167
1195
|
# This was an unexpected disconnect, schedule reconnection
|
|
1196
|
+
_LOGGER.debug("MQTT unexpected disconnect - scheduling reconnection")
|
|
1168
1197
|
self._schedule_reconnect()
|
|
1169
1198
|
elif reason_code.is_failure and not self._closing:
|
|
1170
1199
|
# Connection failed, also schedule reconnection
|
|
1200
|
+
_LOGGER.debug("MQTT connection failed - scheduling reconnection")
|
|
1171
1201
|
self._schedule_reconnect()
|
|
1172
1202
|
|
|
1173
1203
|
def _create_connection(self, msg: mqtt.MQTTMessage) -> None:
|
|
@@ -1191,7 +1221,12 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
|
|
|
1191
1221
|
|
|
1192
1222
|
self.client.subscribe(self._topic_sub, qos=self._mqtt_qos)
|
|
1193
1223
|
|
|
1194
|
-
|
|
1224
|
+
# Only call connection_made on first connection, not reconnections
|
|
1225
|
+
if not self._connection_established:
|
|
1226
|
+
self._connection_established = True
|
|
1227
|
+
self._make_connection(gwy_id=msg.topic[-9:]) # type: ignore[arg-type]
|
|
1228
|
+
else:
|
|
1229
|
+
_LOGGER.info("MQTT reconnected - protocol connection already established")
|
|
1195
1230
|
|
|
1196
1231
|
# NOTE: self._frame_read() invoked from here
|
|
1197
1232
|
def _on_message(
|
|
@@ -1211,23 +1246,51 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
|
|
|
1211
1246
|
_LOGGER.info("Rx: %s", msg.payload)
|
|
1212
1247
|
|
|
1213
1248
|
if msg.topic[-3:] != "/rx": # then, e.g. 'RAMSES/GATEWAY/18:017804'
|
|
1214
|
-
if msg.payload == b"offline"
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1249
|
+
if msg.payload == b"offline":
|
|
1250
|
+
# Check if this offline message is for our current device
|
|
1251
|
+
if (
|
|
1252
|
+
hasattr(self, "_topic_sub")
|
|
1253
|
+
and self._topic_sub
|
|
1254
|
+
and msg.topic == self._topic_sub[:-3]
|
|
1255
|
+
) or not hasattr(self, "_topic_sub"):
|
|
1256
|
+
_LOGGER.warning(
|
|
1257
|
+
f"{self}: the ESP device is offline (via LWT): {msg.topic}"
|
|
1258
|
+
)
|
|
1259
|
+
# Don't set _connected = False here - that's for MQTT connection, not ESP device
|
|
1260
|
+
if hasattr(self, "_protocol"):
|
|
1261
|
+
self._protocol.pause_writing()
|
|
1220
1262
|
|
|
1221
1263
|
# BUG: using create task (self._loop.ct() & asyncio.ct()) causes the
|
|
1222
1264
|
# BUG: event look to close early
|
|
1223
1265
|
elif msg.payload == b"online":
|
|
1224
1266
|
_LOGGER.info(
|
|
1225
|
-
f"{self}: the
|
|
1267
|
+
f"{self}: the ESP device is online (via status): {msg.topic}"
|
|
1226
1268
|
)
|
|
1227
1269
|
self._create_connection(msg)
|
|
1228
1270
|
|
|
1229
1271
|
return
|
|
1230
1272
|
|
|
1273
|
+
# Handle data messages - if we don't have connection established yet but get data,
|
|
1274
|
+
# we can infer the gateway from the topic
|
|
1275
|
+
if not self._connection_established and msg.topic.endswith("/rx"):
|
|
1276
|
+
# Extract gateway ID from topic like "RAMSES/GATEWAY/18:123456/rx"
|
|
1277
|
+
topic_parts = msg.topic.split("/")
|
|
1278
|
+
if len(topic_parts) >= 3 and topic_parts[-2] not in ("+", "*"):
|
|
1279
|
+
gateway_id = topic_parts[-2] # Should be something like "18:123456"
|
|
1280
|
+
_LOGGER.info(
|
|
1281
|
+
f"Inferring gateway connection from data topic: {gateway_id}"
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
# Set up topics and connection
|
|
1285
|
+
self._topic_pub = f"{'/'.join(topic_parts[:-1])}/tx"
|
|
1286
|
+
self._topic_sub = msg.topic
|
|
1287
|
+
self._extra[SZ_ACTIVE_HGI] = gateway_id
|
|
1288
|
+
|
|
1289
|
+
# Mark as connected and establish protocol connection
|
|
1290
|
+
self._connected = True
|
|
1291
|
+
self._connection_established = True
|
|
1292
|
+
self._make_connection(gwy_id=gateway_id) # type: ignore[arg-type]
|
|
1293
|
+
|
|
1231
1294
|
try:
|
|
1232
1295
|
payload = json.loads(msg.payload)
|
|
1233
1296
|
except json.JSONDecodeError:
|
ramses_tx/version.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|