Skip to content

Commit be50323

Browse files
authored
Merge pull request #100 from plugwise/output_to_duc
Output data for HA Core DUC, restructure into more classes
2 parents 852ea07 + 6253d31 commit be50323

File tree

7 files changed

+347
-344
lines changed

7 files changed

+347
-344
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## v0.14.5 - Smile: prepare for using the HA Core DataUpdateCoordintor in Plugwise-beta
4+
5+
- Change the output to enable the use of the HA Core DUC in plugwise-beta.
6+
- Change state_class to "total" for interval- and net_cumulative sensors (following the HA Core sensor platform updates).
7+
- Remove all remnant code related to last_reset (log_date)
8+
- Restructure: introduce additional classes: SmileComm and SmileConnect
9+
310
## v0.14.2 - Smile: fix P1 legacy location handling error
411

512
## v0.14.1 - Smile: removing further `last_reset`s

plugwise/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Plugwise module."""
22

3-
__version__ = "0.14.2"
3+
__version__ = "0.14.5"
44

55
from plugwise.smile import Smile
66
from plugwise.stick import Stick

plugwise/constants.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@
611611
ATTR_NAME: "Electricity Consumed Interval",
612612
ATTR_STATE: None,
613613
ATTR_DEVICE_CLASS: "energy",
614-
ATTR_STATE_CLASS: "measurement",
614+
ATTR_STATE_CLASS: "total",
615615
ATTR_ICON: None,
616616
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
617617
}
@@ -631,7 +631,7 @@
631631
ATTR_NAME: "Electricity Consumed Off Peak Interval",
632632
ATTR_STATE: None,
633633
ATTR_DEVICE_CLASS: "energy",
634-
ATTR_STATE_CLASS: "measurement",
634+
ATTR_STATE_CLASS: "total",
635635
ATTR_ICON: None,
636636
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
637637
}
@@ -661,7 +661,7 @@
661661
ATTR_NAME: "Electricity Consumed Peak Interval",
662662
ATTR_STATE: None,
663663
ATTR_DEVICE_CLASS: "energy",
664-
ATTR_STATE_CLASS: "measurement",
664+
ATTR_STATE_CLASS: "total",
665665
ATTR_ICON: None,
666666
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
667667
}
@@ -701,7 +701,7 @@
701701
ATTR_NAME: "Electricity Produced Interval",
702702
ATTR_STATE: None,
703703
ATTR_DEVICE_CLASS: "energy",
704-
ATTR_STATE_CLASS: "measurement",
704+
ATTR_STATE_CLASS: "total",
705705
ATTR_ICON: None,
706706
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
707707
}
@@ -721,7 +721,7 @@
721721
ATTR_NAME: "Electricity Produced Off Peak Interval",
722722
ATTR_STATE: None,
723723
ATTR_DEVICE_CLASS: "energy",
724-
ATTR_STATE_CLASS: "measurement",
724+
ATTR_STATE_CLASS: "total",
725725
ATTR_ICON: None,
726726
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
727727
}
@@ -751,7 +751,7 @@
751751
ATTR_NAME: "Electricity Produced Peak Interval",
752752
ATTR_STATE: None,
753753
ATTR_DEVICE_CLASS: "energy",
754-
ATTR_STATE_CLASS: "measurement",
754+
ATTR_STATE_CLASS: "total",
755755
ATTR_ICON: None,
756756
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
757757
}
@@ -791,7 +791,7 @@
791791
ATTR_NAME: "Gas Consumed Interval",
792792
ATTR_STATE: None,
793793
ATTR_DEVICE_CLASS: "gas",
794-
ATTR_STATE_CLASS: "measurement",
794+
ATTR_STATE_CLASS: "total",
795795
ATTR_ICON: FLAME_ICON,
796796
ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS,
797797
}
@@ -841,7 +841,7 @@
841841
ATTR_NAME: "Net Electricity Cumulative",
842842
ATTR_STATE: None,
843843
ATTR_DEVICE_CLASS: "energy",
844-
ATTR_STATE_CLASS: "measurement",
844+
ATTR_STATE_CLASS: "total",
845845
ATTR_ICON: None,
846846
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
847847
}

plugwise/entities.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
class GWBinarySensor:
2222
""" Represent the Plugwise Smile/Stretch binary_sensor."""
2323

24-
def __init__(self, api, dev_id, binary_sensor):
24+
def __init__(self, data, dev_id, binary_sensor):
2525
"""Initialize the Gateway."""
26-
self._api = api
2726
self._binary_sensor = binary_sensor
27+
self._data = data
2828
self._dev_id = dev_id
2929
self._attributes = {}
3030
self._icon = None
@@ -65,7 +65,7 @@ def _icon_selector(arg, state):
6565

6666
def update_data(self):
6767
"""Handle update callbacks."""
68-
data = self._api.gw_devices[self._dev_id]
68+
data = self._data[1][self._dev_id]
6969

7070
for key, _ in data.items():
7171
if key != "binary_sensors":
@@ -81,7 +81,7 @@ def update_data(self):
8181
if self._binary_sensor != "plugwise_notification":
8282
continue
8383

84-
notify = self._api.notifications
84+
notify = self._data[0]["notifications"]
8585
self._notification = {}
8686
for severity in SEVERITIES:
8787
self._attributes[f"{severity.upper()}_msg"] = []
@@ -98,12 +98,12 @@ def update_data(self):
9898
class GWThermostat:
9999
"""Represent a Plugwise Thermostat Device."""
100100

101-
def __init__(self, api, dev_id):
101+
def __init__(self, data, dev_id):
102102
"""Initialize the Thermostat."""
103103

104-
self._api = api
105104
self._compressor_state = None
106105
self._cooling_state = None
106+
self._data = data
107107
self._dev_id = dev_id
108108
self._extra_state_attributes = None
109109
self._get_presets = None
@@ -120,9 +120,9 @@ def __init__(self, api, dev_id):
120120
self._smile_class = None
121121
self._temperature = None
122122

123-
self._active_device = self._api._active_device_present
124-
self._heater_id = self._api._heater_id
125-
self._sm_thermostat = self._api.single_master_thermostat()
123+
self._active_device = self._data[0]["active_device"]
124+
self._heater_id = self._data[0]["heater_id"]
125+
self._sm_thermostat = self._data[0]["single_master_thermostat"]
126126

127127
@property
128128
def compressor_state(self):
@@ -186,7 +186,7 @@ def extra_state_attributes(self):
186186

187187
def update_data(self):
188188
"""Handle update callbacks."""
189-
data = self._api.gw_devices[self._dev_id]
189+
data = self._data[1][self._dev_id]
190190

191191
# current & target_temps, heater_central data when required
192192
s_list = data["sensors"]
@@ -197,7 +197,7 @@ def update_data(self):
197197
self._setpoint = s_list[idx][ATTR_STATE]
198198
self._schedule_temp = data.get("schedule_temperature")
199199
if self._active_device:
200-
hc_data = self._api.gw_devices[self._heater_id]
200+
hc_data = self._data[1][self._heater_id]
201201
self._compressor_state = hc_data.get("compressor_state")
202202
if self._sm_thermostat:
203203
self._cooling_state = hc_data.get("cooling_state")

plugwise/helper.py

Lines changed: 44 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import datetime as dt
66
import logging
77

8+
import aiohttp
89
import async_timeout
910
from dateutil import tz
1011
from dateutil.parser import parse
@@ -68,7 +69,7 @@
6869

6970

7071
def device_state_updater(data, devs, d_id, d_dict):
71-
"""Helper-function for _update_gw_devices().
72+
"""Helper-function for async_update().
7273
Update the Device_State sensor state.
7374
"""
7475
for idx, item in enumerate(d_dict["sensors"]):
@@ -114,7 +115,7 @@ def update_device_state(data, d_dict):
114115

115116

116117
def pw_notification_updater(devs, d_id, d_dict, notifs):
117-
"""Helper-function for _update_gw_devices().
118+
"""Helper-function for async_update().
118119
Update the PW_Notification binary_sensor state.
119120
"""
120121
for idx, item in enumerate(d_dict["binary_sensors"]):
@@ -123,16 +124,14 @@ def pw_notification_updater(devs, d_id, d_dict, notifs):
123124

124125

125126
def update_helper(data, devs, d_dict, d_id, e_type, key):
126-
"""Helper-function for _update_gw_devices()."""
127+
"""Helper-function for async_update()."""
127128
for dummy in d_dict[e_type]:
128129
if key != dummy[ATTR_ID]:
129130
continue
130131
for idx, item in enumerate(devs[d_id][e_type]):
131132
if key != item[ATTR_ID]:
132133
continue
133134
devs[d_id][e_type][idx][ATTR_STATE] = data[key]
134-
if isinstance(data[key], list):
135-
devs[d_id][e_type][idx][ATTR_STATE] = data[key][0]
136135

137136

138137
def check_model(name, v_name):
@@ -217,38 +216,37 @@ def power_data_energy_diff(measurement, net_string, f_val, direct_data):
217216
return direct_data
218217

219218

220-
class SmileHelper:
221-
"""The SmileHelper class."""
219+
class SmileComm:
220+
"""The SmileComm class."""
222221

223-
def __init__(self):
222+
def __init__(
223+
self,
224+
host,
225+
password,
226+
username,
227+
port,
228+
timeout,
229+
websession,
230+
):
224231
"""Set the constructor for this class."""
225-
self._active_device_present = None
226-
self._appl_data = {}
227-
self._appliances = None
228-
self._auth = None
229-
self._cp_state = None
230-
self._domain_objects = None
231-
self._endpoint = None
232-
self._heater_id = None
233-
self._home_location = None
234-
self._locations = None
235-
self._modules = None
236-
self._smile_legacy = False
237-
self._host = None
238-
self._loc_data = {}
239-
self._port = None
240-
self._stretch_v2 = False
241-
self._stretch_v3 = False
242-
self._thermo_locs = None
243-
self._timeout = None
244-
self._websession = None
245-
246-
self.gateway_id = None
247-
self.notifications = {}
248-
self.smile_hostname = None
249-
self.smile_name = None
250-
self.smile_type = None
251-
self.smile_version = ()
232+
if not websession:
233+
234+
async def _create_session() -> aiohttp.ClientSession:
235+
return aiohttp.ClientSession() # pragma: no cover
236+
237+
loop = asyncio.get_event_loop()
238+
if loop.is_running():
239+
self._websession = aiohttp.ClientSession()
240+
else:
241+
self._websession = loop.run_until_complete(
242+
_create_session()
243+
) # pragma: no cover
244+
else:
245+
self._websession = websession
246+
247+
self._auth = aiohttp.BasicAuth(username, password=password)
248+
self._endpoint = f"http://{host}:{str(port)}"
249+
self._timeout = timeout
252250

253251
async def _request_validate(self, resp, method):
254252
"""Helper-function for _request(): validate the returned data."""
@@ -289,7 +287,7 @@ async def _request(
289287
url = f"{self._endpoint}{command}"
290288

291289
try:
292-
with async_timeout.timeout(self._timeout):
290+
async with async_timeout.timeout(self._timeout):
293291
if method == "get":
294292
# Work-around for Stretchv2, should not hurt the other smiles
295293
headers = {"Accept-Encoding": "gzip"}
@@ -311,6 +309,14 @@ async def _request(
311309

312310
return await self._request_validate(resp, method)
313311

312+
async def close_connection(self):
313+
"""Close the Plugwise connection."""
314+
await self._websession.close()
315+
316+
317+
class SmileHelper:
318+
"""The SmileHelper class."""
319+
314320
def _locations_legacy(self):
315321
"""Helper-function for _all_locations().
316322
Create locations for legacy devices.
@@ -513,6 +519,7 @@ def _appliance_types_finder(self, appliance, appl):
513519
def _all_appliances(self):
514520
"""Collect all appliances with relevant info."""
515521
self._appl_data = {}
522+
self._cp_state = None
516523

517524
self._all_locations()
518525

@@ -674,29 +681,6 @@ def _rule_ids_by_tag(self, tag, loc_id):
674681
if schema_ids != {}:
675682
return schema_ids
676683

677-
async def _update_domain_objects(self):
678-
"""Helper-function for smile.py: full_update_device() and update_gw_devices().
679-
Request domain_objects data.
680-
"""
681-
self._domain_objects = await self._request(DOMAIN_OBJECTS)
682-
683-
# If Plugwise notifications present:
684-
self.notifications = {}
685-
url = f"{self._endpoint}{DOMAIN_OBJECTS}"
686-
notifications = self._domain_objects.findall(".//notification")
687-
for notification in notifications:
688-
try:
689-
msg_id = notification.attrib["id"]
690-
msg_type = notification.find("type").text
691-
msg = notification.find("message").text
692-
self.notifications.update({msg_id: {msg_type: msg}})
693-
_LOGGER.debug("Plugwise notifications: %s", self.notifications)
694-
except AttributeError: # pragma: no cover
695-
_LOGGER.info(
696-
"Plugwise notification present but unable to process, manually investigate: %s",
697-
url,
698-
)
699-
700684
def _appliance_measurements(self, appliance, data, measurements):
701685
"""Helper-function for _get_appliance_data() - collect appliance measurement data."""
702686
for measurement, attrs in measurements:
@@ -729,9 +713,7 @@ def _appliance_measurements(self, appliance, data, measurements):
729713
if appliance.find(i_locator) is not None:
730714
name = f"{measurement}_interval"
731715
measure = appliance.find(i_locator).text
732-
log_date = parse(appliance.find(i_locator).get("log_date"))
733-
log_date = log_date.astimezone(tz.gettz("UTC")).replace(tzinfo=None)
734-
data[name] = [format_measure(measure, ENERGY_WATT_HOUR), log_date]
716+
data[name] = format_measure(measure, ENERGY_WATT_HOUR)
735717

736718
return data
737719

@@ -955,8 +937,6 @@ def _power_data_peak_value(self, loc):
955937
loc.key_string = f"{loc.measurement}_{log_found}"
956938
loc.net_string = f"net_electricity_{log_found}"
957939
val = loc.logs.find(loc.locator).text
958-
log_date = parse(loc.logs.find(loc.locator).get("log_date"))
959-
loc.log_date = log_date.astimezone(tz.gettz("UTC")).replace(tzinfo=None)
960940
loc.f_val = power_data_local_format(loc.attrs, loc.key_string, val)
961941

962942
return loc
@@ -1002,8 +982,6 @@ def _power_data_from_location(self, loc_id):
1002982
)
1003983

1004984
direct_data[loc.key_string] = loc.f_val
1005-
if "interval" in loc.key_string:
1006-
direct_data[loc.key_string] = [loc.f_val, loc.log_date]
1007985

1008986
if direct_data != {}:
1009987
return direct_data
@@ -1178,8 +1156,6 @@ def _create_lists_from_data(self, data, bs_list, s_list, sw_list):
11781156
if item[ATTR_ID] == key:
11791157
data.pop(item[ATTR_ID])
11801158
item[ATTR_STATE] = value
1181-
if "interval" in item[ATTR_ID] and isinstance(value, list):
1182-
item[ATTR_STATE] = value[0]
11831159
s_list.append(item)
11841160
for item in SWITCHES:
11851161
if item[ATTR_ID] == key:

0 commit comments

Comments
 (0)