emerald-hws 0.0.9__py3-none-any.whl → 0.0.11__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 CHANGED
@@ -5,6 +5,7 @@ import logging
5
5
  import boto3
6
6
  import random
7
7
  import threading
8
+ import time
8
9
  from awsiot import mqtt5_client_builder, mqtt_connection_builder
9
10
  from awscrt import mqtt5, http, auth, io
10
11
 
@@ -22,11 +23,13 @@ class EmeraldHWS():
22
23
  MQTT_HOST = "a13v32g67itvz9-ats.iot.ap-southeast-2.amazonaws.com"
23
24
  COGNITO_IDENTITY_POOL_ID = "ap-southeast-2:f5bbb02c-c00e-4f10-acb3-e7d1b05268e8"
24
25
 
25
- def __init__(self, email, password, update_callback=None):
26
+ def __init__(self, email, password, update_callback=None, connection_timeout_minutes=720, health_check_minutes=60):
26
27
  """ Initialise the API client
27
28
  :param email: The email address for logging into the Emerald app
28
29
  :param password: The password for the supplied user account
29
30
  :param update_callback: Optional callback function to be called when an update is available
31
+ :param connection_timeout_minutes: Optional timeout in minutes before reconnecting MQTT (default: 720 minutes/12 hours)
32
+ :param health_check_minutes: Optional interval in minutes to check for message activity (default: 60 minutes/1 hour)
30
33
  """
31
34
 
32
35
  self.email = email
@@ -35,6 +38,22 @@ class EmeraldHWS():
35
38
  self.properties = {}
36
39
  self.logger = logging.getLogger()
37
40
  self.update_callback = update_callback
41
+
42
+ # Convert minutes to seconds for internal use
43
+ self.connection_timeout = connection_timeout_minutes * 60.0
44
+ self.health_check_interval = health_check_minutes * 60.0 if health_check_minutes > 0 else 0
45
+ self.last_message_time = None
46
+ self.health_check_timer = None
47
+
48
+ # Ensure reasonable minimum values (e.g., at least 5 minutes for connection timeout)
49
+ if connection_timeout_minutes < 5 and connection_timeout_minutes != 0:
50
+ self.logger.warning("emeraldhws: Connection timeout too short, setting to minimum of 5 minutes")
51
+ self.connection_timeout = 5 * 60.0
52
+
53
+ # Ensure reasonable minimum values for health check (e.g., at least 5 minutes)
54
+ if 0 < health_check_minutes < 5:
55
+ self.logger.warning("emeraldhws: Health check interval too short, setting to minimum of 5 minutes")
56
+ self.health_check_interval = 5 * 60.0
38
57
 
39
58
  def getLoginToken(self):
40
59
  """ Performs an API request to get a token from the API
@@ -87,14 +106,41 @@ class EmeraldHWS():
87
106
 
88
107
  self.update_callback = update_callback
89
108
 
90
- def reconnectMQTT(self):
109
+ def reconnectMQTT(self, reason="scheduled"):
91
110
  """ Stops an existing MQTT connection and creates a new one
111
+ :param reason: Reason for reconnection (scheduled, health_check, etc.)
92
112
  """
93
-
94
- self.logger.debug("emeraldhws: awsiot: Tearing down and reconnecting to prevent stale connection")
113
+ self.logger.info(f"emeraldhws: awsiot: Reconnecting MQTT connection (reason: {reason})")
114
+
115
+ # Store current temperature values for comparison after reconnect
116
+ temp_values = {}
117
+ for properties in self.properties:
118
+ heat_pumps = properties.get('heat_pump', [])
119
+ for heat_pump in heat_pumps:
120
+ hws_id = heat_pump['id']
121
+ if 'last_state' in heat_pump and 'temp_current' in heat_pump['last_state']:
122
+ temp_values[hws_id] = heat_pump['last_state']['temp_current']
123
+
95
124
  self.mqttClient.stop()
96
125
  self.connectMQTT()
97
126
  self.subscribeAllHWS()
127
+
128
+ # After reconnection, check if temperatures have changed
129
+ def check_temp_changes():
130
+ for properties in self.properties:
131
+ heat_pumps = properties.get('heat_pump', [])
132
+ for heat_pump in heat_pumps:
133
+ hws_id = heat_pump['id']
134
+ if (hws_id in temp_values and
135
+ 'last_state' in heat_pump and
136
+ 'temp_current' in heat_pump['last_state']):
137
+ old_temp = temp_values[hws_id]
138
+ new_temp = heat_pump['last_state']['temp_current']
139
+ if old_temp != new_temp:
140
+ self.logger.info(f"emeraldhws: Temperature changed after reconnect for {hws_id}: {old_temp} → {new_temp}")
141
+
142
+ # Check for temperature changes after a short delay to allow for updates
143
+ threading.Timer(10.0, check_temp_changes).start()
98
144
 
99
145
  def connectMQTT(self):
100
146
  """ Establishes a connection to Amazon IOT core's MQTT service
@@ -131,7 +177,16 @@ class EmeraldHWS():
131
177
 
132
178
  client.start()
133
179
  self.mqttClient = client
134
- threading.Timer(43200.0, self.reconnectMQTT).start() # 12 hours
180
+
181
+ # Schedule periodic reconnection using configurable timeout
182
+ if self.connection_timeout > 0:
183
+ threading.Timer(self.connection_timeout, self.reconnectMQTT).start()
184
+
185
+ # Start health check timer if enabled
186
+ if self.health_check_interval > 0:
187
+ self.health_check_timer = threading.Timer(self.health_check_interval, self.check_connection_health)
188
+ self.health_check_timer.daemon = True
189
+ self.health_check_timer.start()
135
190
 
136
191
  def mqttDecodeUpdate(self, topic, payload):
137
192
  """ Attempt to decode a received MQTT message and direct appropriately
@@ -153,6 +208,7 @@ class EmeraldHWS():
153
208
  publish_packet = publish_packet_data.publish_packet
154
209
  assert isinstance(publish_packet, mqtt5.PublishPacket)
155
210
  self.logger.debug("emeraldhws: awsiot: Received message from MQTT topic {}: {}".format(publish_packet.topic, publish_packet.payload))
211
+ self.last_message_time = time.time() # Update the last message time
156
212
  self.mqttDecodeUpdate(publish_packet.topic, publish_packet.payload)
157
213
 
158
214
  def on_connection_interrupted(self, connection, error, **kwargs):
@@ -194,6 +250,31 @@ class EmeraldHWS():
194
250
  """
195
251
  self.logger.debug("emeraldhws: awsiot: attempting to connect")
196
252
  return
253
+
254
+ def check_connection_health(self):
255
+ """ Check if we've received any messages recently, reconnect if not
256
+ """
257
+ if self.last_message_time is None:
258
+ # No messages received yet, don't reconnect
259
+ self.logger.debug("emeraldhws: awsiot: Health check - No messages received yet")
260
+ else:
261
+ current_time = time.time()
262
+ time_since_last_message = current_time - self.last_message_time
263
+ minutes_since_last = time_since_last_message / 60.0
264
+
265
+ if time_since_last_message > self.health_check_interval:
266
+ # This is an INFO level log because it's an important event
267
+ self.logger.info(f"emeraldhws: awsiot: No messages received for {minutes_since_last:.1f} minutes, reconnecting")
268
+ self.reconnectMQTT(reason="health_check")
269
+ else:
270
+ # This is a DEBUG level log to avoid cluttering logs
271
+ self.logger.debug(f"emeraldhws: awsiot: Health check - Last message received {minutes_since_last:.1f} minutes ago")
272
+
273
+ # Schedule next health check
274
+ if self.health_check_interval > 0:
275
+ self.health_check_timer = threading.Timer(self.health_check_interval, self.check_connection_health)
276
+ self.health_check_timer.daemon = True
277
+ self.health_check_timer.start()
197
278
 
198
279
  def updateHWSState(self, id, key, value):
199
280
  """ Updates the specified value for the supplied key in the HWS id specified
@@ -311,6 +392,13 @@ class EmeraldHWS():
311
392
  switch_status = self.getFullStatus(id).get("last_state").get("switch")
312
393
  return (switch_status == 1 or switch_status == "on")
313
394
 
395
+ def isHeating(self, id):
396
+ """ Returns true if the specified HWS is currently heating
397
+ :param id: The UUID of the HWS to query
398
+ """
399
+ heating_status = self.getFullStatus(id).get("device_operation_status")
400
+ return (heating_status == 1)
401
+
314
402
  def currentMode(self, id):
315
403
  """ Returns an integer specifying the current mode (0==boost, 1==normal, 2==quiet)
316
404
  :param id: The UUID of the HWS to query
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: emerald_hws
3
+ Version: 0.0.11
4
+ Summary: A package to manipulate and monitor Emerald Heat Pump Hot Water Systems
5
+ Author-email: Ross Williamson <ross@inertia.net.nz>
6
+ Project-URL: Homepage, https://github.com/ross-w/emerald_hws_py
7
+ Project-URL: Bug Tracker, https://github.com/ross-w/emerald_hws_py/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: boto3
15
+ Requires-Dist: awsiotsdk
16
+ Dynamic: license-file
17
+
18
+ # emerald_hws_py
19
+ Python package for controlling Emerald Heat Pump Hot Water Systems
20
+
21
+ ## Overview
22
+ This package provides an interface to control and monitor Emerald Heat Pump Hot Water Systems through their API and MQTT service.
23
+
24
+ ## Installation
25
+ ```bash
26
+ pip install emerald_hws
27
+ ```
28
+
29
+ ## Usage
30
+ ```python
31
+ from emerald_hws.emeraldhws import EmeraldHWS
32
+
33
+ # Basic usage with default connection settings
34
+ client = EmeraldHWS("your_email@example.com", "your_password")
35
+ client.connect()
36
+
37
+ # List all hot water systems
38
+ hws_list = client.listHWS()
39
+ print(f"Found {len(hws_list)} hot water systems")
40
+
41
+ # Get status of first HWS
42
+ hws_id = hws_list[0]
43
+ status = client.getFullStatus(hws_id)
44
+ print(f"Current temperature: {status['last_state'].get('temp_current')}")
45
+
46
+ # Turn on the hot water system
47
+ client.turnOn(hws_id)
48
+ ```
49
+
50
+ ## Configuration Options
51
+
52
+ ### Connection Timeout
53
+ The module will automatically reconnect to the MQTT service periodically to prevent stale connections. You can configure this timeout:
54
+
55
+ ```python
56
+ # Set connection timeout to 6 hours (360 minutes)
57
+ client = EmeraldHWS("your_email@example.com", "your_password", connection_timeout_minutes=360)
58
+ ```
59
+
60
+ ### Health Check
61
+ The module can proactively check for message activity and reconnect if no messages have been received for a specified period:
62
+
63
+ ```python
64
+ # Set health check to check every 30 minutes
65
+ client = EmeraldHWS("your_email@example.com", "your_password", health_check_minutes=30)
66
+
67
+ # Disable health check
68
+ client = EmeraldHWS("your_email@example.com", "your_password", health_check_minutes=0)
69
+ ```
70
+
71
+ ## Callback for Updates
72
+ You can register a callback function to be notified when the state of any hot water system changes:
73
+
74
+ ```python
75
+ def my_callback():
76
+ print("Hot water system state updated!")
77
+
78
+ client = EmeraldHWS("your_email@example.com", "your_password", update_callback=my_callback)
79
+ ```
@@ -0,0 +1,8 @@
1
+ emerald_hws/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
+ emerald_hws/emeraldhws.py,sha256=h8I-bJUq7RFlveWyzNsahjdt5h7zWPdrbmCX3Q99atg,19019
3
+ emerald_hws/__assets__/SFSRootCAG2.pem,sha256=hw9W0AnYrrlbcWsOewAgIl1ULEsoO57Ylu35dCjWcS4,1424
4
+ emerald_hws-0.0.11.dist-info/licenses/LICENSE,sha256=zzMi56JX7OO-epbXNfe_oFW6sfZHSlO7Yxm0Oh0V014,1072
5
+ emerald_hws-0.0.11.dist-info/METADATA,sha256=mezYMqZpFZ1RnAkWj17mQHl6c4_wqRTduE1aU5_SRZk,2472
6
+ emerald_hws-0.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ emerald_hws-0.0.11.dist-info/top_level.txt,sha256=ZCiUmnBkDr2n4QVkTet1s_AKiGJjuz3heuCR5w5ZqLY,12
8
+ emerald_hws-0.0.11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,18 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: emerald_hws
3
- Version: 0.0.9
4
- Summary: A package to manipulate and monitor Emerald Heat Pump Hot Water Systems
5
- Author-email: Ross Williamson <ross@inertia.net.nz>
6
- Project-URL: Homepage, https://github.com/ross-w/emerald_hws_py
7
- Project-URL: Bug Tracker, https://github.com/ross-w/emerald_hws_py/issues
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.7
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: boto3
15
- Requires-Dist: awsiotsdk
16
-
17
- # emerald_hws_py
18
- Python package for controlling Emerald Heat Pump Hot Water Systems
@@ -1,8 +0,0 @@
1
- emerald_hws/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
- emerald_hws/emeraldhws.py,sha256=w3gMffHc9g71r7VeA7710cO-s-DwXcGqTKG0KneC2PI,14088
3
- emerald_hws/__assets__/SFSRootCAG2.pem,sha256=hw9W0AnYrrlbcWsOewAgIl1ULEsoO57Ylu35dCjWcS4,1424
4
- emerald_hws-0.0.9.dist-info/LICENSE,sha256=zzMi56JX7OO-epbXNfe_oFW6sfZHSlO7Yxm0Oh0V014,1072
5
- emerald_hws-0.0.9.dist-info/METADATA,sha256=c2Y0d6CM_n2ve-nESs1k1inHnt9kbhFkqCXdaFxl7zs,688
6
- emerald_hws-0.0.9.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
7
- emerald_hws-0.0.9.dist-info/top_level.txt,sha256=ZCiUmnBkDr2n4QVkTet1s_AKiGJjuz3heuCR5w5ZqLY,12
8
- emerald_hws-0.0.9.dist-info/RECORD,,