emerald-hws 0.0.15__py3-none-any.whl → 0.0.17__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.
- emerald_hws/emeraldhws.py +88 -16
- {emerald_hws-0.0.15.dist-info → emerald_hws-0.0.17.dist-info}/METADATA +1 -1
- emerald_hws-0.0.17.dist-info/RECORD +7 -0
- emerald_hws-0.0.15.dist-info/RECORD +0 -7
- {emerald_hws-0.0.15.dist-info → emerald_hws-0.0.17.dist-info}/WHEEL +0 -0
- {emerald_hws-0.0.15.dist-info → emerald_hws-0.0.17.dist-info}/top_level.txt +0 -0
emerald_hws/emeraldhws.py
CHANGED
@@ -40,13 +40,15 @@ class EmeraldHWS():
|
|
40
40
|
self.logger = logging.getLogger(__name__)
|
41
41
|
self.update_callback = update_callback
|
42
42
|
self._state_lock = threading.RLock() # Thread-safe lock for state operations
|
43
|
-
|
43
|
+
self._connection_event = threading.Event() # Event to signal when MQTT connection is established
|
44
|
+
self.mqttClient = None # Initialize to None
|
45
|
+
|
44
46
|
# Convert minutes to seconds for internal use
|
45
47
|
self.connection_timeout = connection_timeout_minutes * 60.0
|
46
48
|
self.health_check_interval = health_check_minutes * 60.0 if health_check_minutes > 0 else 0
|
47
49
|
self.last_message_time = None
|
48
50
|
self.health_check_timer = None
|
49
|
-
|
51
|
+
|
50
52
|
# Connection state tracking
|
51
53
|
self.connection_state = "initial" # possible states: initial, connected, failed
|
52
54
|
self.consecutive_failures = 0
|
@@ -118,7 +120,7 @@ class EmeraldHWS():
|
|
118
120
|
:param reason: Reason for reconnection (scheduled, health_check, etc.)
|
119
121
|
"""
|
120
122
|
self.logger.info(f"emeraldhws: awsiot: Reconnecting MQTT connection (reason: {reason})")
|
121
|
-
|
123
|
+
|
122
124
|
# Store current temperature values for comparison after reconnect
|
123
125
|
temp_values = {}
|
124
126
|
for properties in self.properties:
|
@@ -127,8 +129,11 @@ class EmeraldHWS():
|
|
127
129
|
hws_id = heat_pump['id']
|
128
130
|
if 'last_state' in heat_pump and 'temp_current' in heat_pump['last_state']:
|
129
131
|
temp_values[hws_id] = heat_pump['last_state']['temp_current']
|
130
|
-
|
131
|
-
self.mqttClient
|
132
|
+
|
133
|
+
if self.mqttClient is not None:
|
134
|
+
self.mqttClient.stop()
|
135
|
+
self.mqttClient = None # Clear the client so a new one can be created
|
136
|
+
|
132
137
|
self.connectMQTT()
|
133
138
|
self.subscribeAllHWS()
|
134
139
|
|
@@ -153,6 +158,14 @@ class EmeraldHWS():
|
|
153
158
|
""" Establishes a connection to Amazon IOT core's MQTT service
|
154
159
|
"""
|
155
160
|
|
161
|
+
# If already connected, skip
|
162
|
+
if self.mqttClient is not None:
|
163
|
+
self.logger.debug("emeraldhws: awsiot: MQTT client already exists, skipping connection")
|
164
|
+
return
|
165
|
+
|
166
|
+
# Clear the connection event before starting new connection
|
167
|
+
self._connection_event.clear()
|
168
|
+
|
156
169
|
# Certificate path is available but not currently used in the connection
|
157
170
|
# os.path.join(os.path.dirname(__file__), '__assets__', 'SFSRootCAG2.pem')
|
158
171
|
identityPoolID = self.COGNITO_IDENTITY_POOL_ID
|
@@ -185,11 +198,16 @@ class EmeraldHWS():
|
|
185
198
|
|
186
199
|
client.start()
|
187
200
|
self.mqttClient = client
|
188
|
-
|
201
|
+
|
202
|
+
# Block until connection is established or timeout (30 seconds)
|
203
|
+
if not self._connection_event.wait(timeout=30):
|
204
|
+
self.logger.warning("emeraldhws: awsiot: Connection establishment timed out after 30 seconds")
|
205
|
+
# Continue anyway - the connection may still succeed asynchronously
|
206
|
+
|
189
207
|
# Schedule periodic reconnection using configurable timeout
|
190
208
|
if self.connection_timeout > 0:
|
191
209
|
threading.Timer(self.connection_timeout, self.reconnectMQTT).start()
|
192
|
-
|
210
|
+
|
193
211
|
# Start health check timer if enabled
|
194
212
|
if self.health_check_interval > 0:
|
195
213
|
self.health_check_timer = threading.Timer(self.health_check_interval, self.check_connection_health)
|
@@ -238,6 +256,8 @@ class EmeraldHWS():
|
|
238
256
|
# Reset failure counter and update connection state
|
239
257
|
self.consecutive_failures = 0
|
240
258
|
self.connection_state = "connected"
|
259
|
+
# Signal that connection is established
|
260
|
+
self._connection_event.set()
|
241
261
|
return
|
242
262
|
|
243
263
|
def on_lifecycle_connection_failure(self, lifecycle_connection_failure: mqtt5.LifecycleConnectFailureData):
|
@@ -247,14 +267,18 @@ class EmeraldHWS():
|
|
247
267
|
error_code = getattr(error, 'code', 'unknown')
|
248
268
|
error_name = getattr(error, 'name', 'unknown')
|
249
269
|
error_message = str(error)
|
250
|
-
|
270
|
+
|
251
271
|
# Update connection state and increment failure counter
|
252
272
|
self.connection_state = "failed"
|
253
273
|
self.consecutive_failures += 1
|
254
|
-
|
274
|
+
|
255
275
|
# Log at INFO level since this is important for troubleshooting
|
256
276
|
self.logger.info(f"emeraldhws: awsiot: connection failed - Error: {error_name} (code: {error_code}), Message: {error_message}")
|
257
|
-
|
277
|
+
|
278
|
+
# Log additional error details if available
|
279
|
+
if hasattr(error, '__dict__'):
|
280
|
+
self.logger.debug(f"emeraldhws: awsiot: error details: {error.__dict__}")
|
281
|
+
|
258
282
|
# If there's a CONNACK packet available, log its details too
|
259
283
|
if hasattr(lifecycle_connection_failure, 'connack_packet') and lifecycle_connection_failure.connack_packet:
|
260
284
|
connack = lifecycle_connection_failure.connack_packet
|
@@ -264,6 +288,17 @@ class EmeraldHWS():
|
|
264
288
|
self.logger.info(f"emeraldhws: awsiot: MQTT CONNACK reason: {reason_code} - {reason_string}")
|
265
289
|
else:
|
266
290
|
self.logger.info(f"emeraldhws: awsiot: MQTT CONNACK reason code: {reason_code}")
|
291
|
+
|
292
|
+
# Log all CONNACK properties if available
|
293
|
+
if hasattr(connack, '__dict__'):
|
294
|
+
self.logger.debug(f"emeraldhws: awsiot: CONNACK details: {connack.__dict__}")
|
295
|
+
else:
|
296
|
+
self.logger.debug("emeraldhws: awsiot: no CONNACK packet available in failure data")
|
297
|
+
|
298
|
+
# Log the exception data structure itself for deeper debugging
|
299
|
+
if hasattr(lifecycle_connection_failure, '__dict__'):
|
300
|
+
self.logger.debug(f"emeraldhws: awsiot: failure data: {lifecycle_connection_failure.__dict__}")
|
301
|
+
|
267
302
|
return
|
268
303
|
|
269
304
|
def on_lifecycle_stopped(self, lifecycle_stopped_data: mqtt5.LifecycleStoppedData):
|
@@ -278,19 +313,26 @@ class EmeraldHWS():
|
|
278
313
|
# Extract disconnect reason if available
|
279
314
|
reason = "unknown reason"
|
280
315
|
if hasattr(lifecycle_disconnect_data, 'disconnect_packet') and lifecycle_disconnect_data.disconnect_packet:
|
281
|
-
|
282
|
-
|
316
|
+
disconnect_packet = lifecycle_disconnect_data.disconnect_packet
|
317
|
+
reason_code = getattr(disconnect_packet, 'reason_code', 'unknown')
|
318
|
+
reason_string = getattr(disconnect_packet, 'reason_string', '')
|
283
319
|
reason = f"reason code: {reason_code}" + (f" - {reason_string}" if reason_string else "")
|
284
|
-
|
320
|
+
|
321
|
+
# Log full disconnect packet details at debug level
|
322
|
+
if hasattr(disconnect_packet, '__dict__'):
|
323
|
+
self.logger.debug(f"emeraldhws: awsiot: disconnect packet details: {disconnect_packet.__dict__}")
|
324
|
+
else:
|
325
|
+
# Log the disconnect data structure if no packet available
|
326
|
+
if hasattr(lifecycle_disconnect_data, '__dict__'):
|
327
|
+
self.logger.debug(f"emeraldhws: awsiot: disconnect data: {lifecycle_disconnect_data.__dict__}")
|
328
|
+
|
285
329
|
self.logger.info(f"emeraldhws: awsiot: disconnected - {reason}")
|
286
330
|
return
|
287
331
|
|
288
332
|
def on_lifecycle_attempting_connect(self, lifecycle_attempting_connect_data: mqtt5.LifecycleAttemptingConnectData):
|
289
333
|
""" Log message when attempting connect
|
290
334
|
"""
|
291
|
-
|
292
|
-
endpoint = getattr(lifecycle_attempting_connect_data, 'endpoint', 'unknown')
|
293
|
-
self.logger.debug(f"emeraldhws: awsiot: attempting to connect to {endpoint}")
|
335
|
+
self.logger.debug("emeraldhws: awsiot: attempting to connect")
|
294
336
|
return
|
295
337
|
|
296
338
|
def check_connection_health(self):
|
@@ -459,10 +501,40 @@ class EmeraldHWS():
|
|
459
501
|
"""
|
460
502
|
full_status = self.getFullStatus(id)
|
461
503
|
if full_status:
|
504
|
+
# Try to get work_state from last_state (updated via MQTT)
|
505
|
+
if full_status.get("last_state") and "work_state" in full_status.get("last_state"):
|
506
|
+
work_state = full_status.get("last_state").get("work_state")
|
507
|
+
# work_state: 0=off/idle, 1=actively heating, 2=on but not heating
|
508
|
+
return (work_state == 1)
|
509
|
+
|
510
|
+
# Fallback to device_operation_status if work_state not available yet
|
511
|
+
# (e.g., before first MQTT update after initialization)
|
462
512
|
heating_status = full_status.get("device_operation_status")
|
463
513
|
return (heating_status == 1)
|
514
|
+
|
464
515
|
return False
|
465
516
|
|
517
|
+
def getHourlyEnergyUsage(self, id):
|
518
|
+
""" Returns energy usage as reported by heater for the previous hour in kWh and a string of format YYYY-MM-DD HH:00 dictating the starting hour for the energy reading
|
519
|
+
:param id: The UUID of the HWS to query
|
520
|
+
"""
|
521
|
+
full_status = self.getFullStatus(id)
|
522
|
+
if not full_status:
|
523
|
+
return None
|
524
|
+
|
525
|
+
consumption = full_status.get("consumption_data")
|
526
|
+
if consumption:
|
527
|
+
consumption = json.loads(consumption)
|
528
|
+
else:
|
529
|
+
return None
|
530
|
+
|
531
|
+
current_hour = consumption.get("current_hour")
|
532
|
+
last_data_at = consumption.get("last_data_at")
|
533
|
+
if current_hour is None or last_data_at is None:
|
534
|
+
return None
|
535
|
+
|
536
|
+
return current_hour, last_data_at
|
537
|
+
|
466
538
|
def currentMode(self, id):
|
467
539
|
""" Returns an integer specifying the current mode (0==boost, 1==normal, 2==quiet)
|
468
540
|
:param id: The UUID of the HWS to query
|
@@ -0,0 +1,7 @@
|
|
1
|
+
emerald_hws/__init__.py,sha256=uukjQ-kiPYKWvGT3jLL6kJA1DCNAxtw4HlLKqPSypXs,61
|
2
|
+
emerald_hws/emeraldhws.py,sha256=ixNkwFXVEHLXtQhMmGi_ncfKhNDZWJdmlyuAW92tyyo,26201
|
3
|
+
emerald_hws/__assets__/SFSRootCAG2.pem,sha256=hw9W0AnYrrlbcWsOewAgIl1ULEsoO57Ylu35dCjWcS4,1424
|
4
|
+
emerald_hws-0.0.17.dist-info/METADATA,sha256=ytcqaOt_6fMzL1gGvoSybD3cQHiqLzPr5KTrPvO3rCc,2534
|
5
|
+
emerald_hws-0.0.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
6
|
+
emerald_hws-0.0.17.dist-info/top_level.txt,sha256=ZCiUmnBkDr2n4QVkTet1s_AKiGJjuz3heuCR5w5ZqLY,12
|
7
|
+
emerald_hws-0.0.17.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
emerald_hws/__init__.py,sha256=uukjQ-kiPYKWvGT3jLL6kJA1DCNAxtw4HlLKqPSypXs,61
|
2
|
-
emerald_hws/emeraldhws.py,sha256=0yvazkMtwEfimqGsBe6Q3zGEfpQbCKgCiXDpRYwKFL4,22876
|
3
|
-
emerald_hws/__assets__/SFSRootCAG2.pem,sha256=hw9W0AnYrrlbcWsOewAgIl1ULEsoO57Ylu35dCjWcS4,1424
|
4
|
-
emerald_hws-0.0.15.dist-info/METADATA,sha256=6SQeBxgNLAu_nHDnT4VqJdFB_z7GtkKLixAlSbikCK0,2534
|
5
|
-
emerald_hws-0.0.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
6
|
-
emerald_hws-0.0.15.dist-info/top_level.txt,sha256=ZCiUmnBkDr2n4QVkTet1s_AKiGJjuz3heuCR5w5ZqLY,12
|
7
|
-
emerald_hws-0.0.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|