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 +93 -5
- emerald_hws-0.0.11.dist-info/METADATA +79 -0
- emerald_hws-0.0.11.dist-info/RECORD +8 -0
- {emerald_hws-0.0.9.dist-info → emerald_hws-0.0.11.dist-info}/WHEEL +1 -1
- emerald_hws-0.0.9.dist-info/METADATA +0 -18
- emerald_hws-0.0.9.dist-info/RECORD +0 -8
- {emerald_hws-0.0.9.dist-info → emerald_hws-0.0.11.dist-info/licenses}/LICENSE +0 -0
- {emerald_hws-0.0.9.dist-info → emerald_hws-0.0.11.dist-info}/top_level.txt +0 -0
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
|
-
|
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
|
-
|
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,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,,
|
File without changes
|
File without changes
|