Skip to content

Commit

Permalink
Merge pull request #100 from plugwise/output_to_duc
Browse files Browse the repository at this point in the history
Output data for HA Core DUC, restructure into more classes
  • Loading branch information
bouwew committed Sep 16, 2021
2 parents 852ea07 + 6253d31 commit be50323
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 344 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## v0.14.5 - Smile: prepare for using the HA Core DataUpdateCoordintor in Plugwise-beta

- Change the output to enable the use of the HA Core DUC in plugwise-beta.
- Change state_class to "total" for interval- and net_cumulative sensors (following the HA Core sensor platform updates).
- Remove all remnant code related to last_reset (log_date)
- Restructure: introduce additional classes: SmileComm and SmileConnect

## v0.14.2 - Smile: fix P1 legacy location handling error

## v0.14.1 - Smile: removing further `last_reset`s
Expand Down
2 changes: 1 addition & 1 deletion plugwise/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Plugwise module."""

__version__ = "0.14.2"
__version__ = "0.14.5"

from plugwise.smile import Smile
from plugwise.stick import Stick
16 changes: 8 additions & 8 deletions plugwise/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@
ATTR_NAME: "Electricity Consumed Interval",
ATTR_STATE: None,
ATTR_DEVICE_CLASS: "energy",
ATTR_STATE_CLASS: "measurement",
ATTR_STATE_CLASS: "total",
ATTR_ICON: None,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
}
Expand All @@ -631,7 +631,7 @@
ATTR_NAME: "Electricity Consumed Off Peak Interval",
ATTR_STATE: None,
ATTR_DEVICE_CLASS: "energy",
ATTR_STATE_CLASS: "measurement",
ATTR_STATE_CLASS: "total",
ATTR_ICON: None,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
}
Expand Down Expand Up @@ -661,7 +661,7 @@
ATTR_NAME: "Electricity Consumed Peak Interval",
ATTR_STATE: None,
ATTR_DEVICE_CLASS: "energy",
ATTR_STATE_CLASS: "measurement",
ATTR_STATE_CLASS: "total",
ATTR_ICON: None,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
}
Expand Down Expand Up @@ -701,7 +701,7 @@
ATTR_NAME: "Electricity Produced Interval",
ATTR_STATE: None,
ATTR_DEVICE_CLASS: "energy",
ATTR_STATE_CLASS: "measurement",
ATTR_STATE_CLASS: "total",
ATTR_ICON: None,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
}
Expand All @@ -721,7 +721,7 @@
ATTR_NAME: "Electricity Produced Off Peak Interval",
ATTR_STATE: None,
ATTR_DEVICE_CLASS: "energy",
ATTR_STATE_CLASS: "measurement",
ATTR_STATE_CLASS: "total",
ATTR_ICON: None,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
}
Expand Down Expand Up @@ -751,7 +751,7 @@
ATTR_NAME: "Electricity Produced Peak Interval",
ATTR_STATE: None,
ATTR_DEVICE_CLASS: "energy",
ATTR_STATE_CLASS: "measurement",
ATTR_STATE_CLASS: "total",
ATTR_ICON: None,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_WATT_HOUR,
}
Expand Down Expand Up @@ -791,7 +791,7 @@
ATTR_NAME: "Gas Consumed Interval",
ATTR_STATE: None,
ATTR_DEVICE_CLASS: "gas",
ATTR_STATE_CLASS: "measurement",
ATTR_STATE_CLASS: "total",
ATTR_ICON: FLAME_ICON,
ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS,
}
Expand Down Expand Up @@ -841,7 +841,7 @@
ATTR_NAME: "Net Electricity Cumulative",
ATTR_STATE: None,
ATTR_DEVICE_CLASS: "energy",
ATTR_STATE_CLASS: "measurement",
ATTR_STATE_CLASS: "total",
ATTR_ICON: None,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
}
Expand Down
22 changes: 11 additions & 11 deletions plugwise/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
class GWBinarySensor:
""" Represent the Plugwise Smile/Stretch binary_sensor."""

def __init__(self, api, dev_id, binary_sensor):
def __init__(self, data, dev_id, binary_sensor):
"""Initialize the Gateway."""
self._api = api
self._binary_sensor = binary_sensor
self._data = data
self._dev_id = dev_id
self._attributes = {}
self._icon = None
Expand Down Expand Up @@ -65,7 +65,7 @@ def _icon_selector(arg, state):

def update_data(self):
"""Handle update callbacks."""
data = self._api.gw_devices[self._dev_id]
data = self._data[1][self._dev_id]

for key, _ in data.items():
if key != "binary_sensors":
Expand All @@ -81,7 +81,7 @@ def update_data(self):
if self._binary_sensor != "plugwise_notification":
continue

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

def __init__(self, api, dev_id):
def __init__(self, data, dev_id):
"""Initialize the Thermostat."""

self._api = api
self._compressor_state = None
self._cooling_state = None
self._data = data
self._dev_id = dev_id
self._extra_state_attributes = None
self._get_presets = None
Expand All @@ -120,9 +120,9 @@ def __init__(self, api, dev_id):
self._smile_class = None
self._temperature = None

self._active_device = self._api._active_device_present
self._heater_id = self._api._heater_id
self._sm_thermostat = self._api.single_master_thermostat()
self._active_device = self._data[0]["active_device"]
self._heater_id = self._data[0]["heater_id"]
self._sm_thermostat = self._data[0]["single_master_thermostat"]

@property
def compressor_state(self):
Expand Down Expand Up @@ -186,7 +186,7 @@ def extra_state_attributes(self):

def update_data(self):
"""Handle update callbacks."""
data = self._api.gw_devices[self._dev_id]
data = self._data[1][self._dev_id]

# current & target_temps, heater_central data when required
s_list = data["sensors"]
Expand All @@ -197,7 +197,7 @@ def update_data(self):
self._setpoint = s_list[idx][ATTR_STATE]
self._schedule_temp = data.get("schedule_temperature")
if self._active_device:
hc_data = self._api.gw_devices[self._heater_id]
hc_data = self._data[1][self._heater_id]
self._compressor_state = hc_data.get("compressor_state")
if self._sm_thermostat:
self._cooling_state = hc_data.get("cooling_state")
Expand Down
112 changes: 44 additions & 68 deletions plugwise/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import datetime as dt
import logging

import aiohttp
import async_timeout
from dateutil import tz
from dateutil.parser import parse
Expand Down Expand Up @@ -68,7 +69,7 @@


def device_state_updater(data, devs, d_id, d_dict):
"""Helper-function for _update_gw_devices().
"""Helper-function for async_update().
Update the Device_State sensor state.
"""
for idx, item in enumerate(d_dict["sensors"]):
Expand Down Expand Up @@ -114,7 +115,7 @@ def update_device_state(data, d_dict):


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


def update_helper(data, devs, d_dict, d_id, e_type, key):
"""Helper-function for _update_gw_devices()."""
"""Helper-function for async_update()."""
for dummy in d_dict[e_type]:
if key != dummy[ATTR_ID]:
continue
for idx, item in enumerate(devs[d_id][e_type]):
if key != item[ATTR_ID]:
continue
devs[d_id][e_type][idx][ATTR_STATE] = data[key]
if isinstance(data[key], list):
devs[d_id][e_type][idx][ATTR_STATE] = data[key][0]


def check_model(name, v_name):
Expand Down Expand Up @@ -217,38 +216,37 @@ def power_data_energy_diff(measurement, net_string, f_val, direct_data):
return direct_data


class SmileHelper:
"""The SmileHelper class."""
class SmileComm:
"""The SmileComm class."""

def __init__(self):
def __init__(
self,
host,
password,
username,
port,
timeout,
websession,
):
"""Set the constructor for this class."""
self._active_device_present = None
self._appl_data = {}
self._appliances = None
self._auth = None
self._cp_state = None
self._domain_objects = None
self._endpoint = None
self._heater_id = None
self._home_location = None
self._locations = None
self._modules = None
self._smile_legacy = False
self._host = None
self._loc_data = {}
self._port = None
self._stretch_v2 = False
self._stretch_v3 = False
self._thermo_locs = None
self._timeout = None
self._websession = None

self.gateway_id = None
self.notifications = {}
self.smile_hostname = None
self.smile_name = None
self.smile_type = None
self.smile_version = ()
if not websession:

async def _create_session() -> aiohttp.ClientSession:
return aiohttp.ClientSession() # pragma: no cover

loop = asyncio.get_event_loop()
if loop.is_running():
self._websession = aiohttp.ClientSession()
else:
self._websession = loop.run_until_complete(
_create_session()
) # pragma: no cover
else:
self._websession = websession

self._auth = aiohttp.BasicAuth(username, password=password)
self._endpoint = f"http://{host}:{str(port)}"
self._timeout = timeout

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

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

return await self._request_validate(resp, method)

async def close_connection(self):
"""Close the Plugwise connection."""
await self._websession.close()


class SmileHelper:
"""The SmileHelper class."""

def _locations_legacy(self):
"""Helper-function for _all_locations().
Create locations for legacy devices.
Expand Down Expand Up @@ -513,6 +519,7 @@ def _appliance_types_finder(self, appliance, appl):
def _all_appliances(self):
"""Collect all appliances with relevant info."""
self._appl_data = {}
self._cp_state = None

self._all_locations()

Expand Down Expand Up @@ -674,29 +681,6 @@ def _rule_ids_by_tag(self, tag, loc_id):
if schema_ids != {}:
return schema_ids

async def _update_domain_objects(self):
"""Helper-function for smile.py: full_update_device() and update_gw_devices().
Request domain_objects data.
"""
self._domain_objects = await self._request(DOMAIN_OBJECTS)

# If Plugwise notifications present:
self.notifications = {}
url = f"{self._endpoint}{DOMAIN_OBJECTS}"
notifications = self._domain_objects.findall(".//notification")
for notification in notifications:
try:
msg_id = notification.attrib["id"]
msg_type = notification.find("type").text
msg = notification.find("message").text
self.notifications.update({msg_id: {msg_type: msg}})
_LOGGER.debug("Plugwise notifications: %s", self.notifications)
except AttributeError: # pragma: no cover
_LOGGER.info(
"Plugwise notification present but unable to process, manually investigate: %s",
url,
)

def _appliance_measurements(self, appliance, data, measurements):
"""Helper-function for _get_appliance_data() - collect appliance measurement data."""
for measurement, attrs in measurements:
Expand Down Expand Up @@ -729,9 +713,7 @@ def _appliance_measurements(self, appliance, data, measurements):
if appliance.find(i_locator) is not None:
name = f"{measurement}_interval"
measure = appliance.find(i_locator).text
log_date = parse(appliance.find(i_locator).get("log_date"))
log_date = log_date.astimezone(tz.gettz("UTC")).replace(tzinfo=None)
data[name] = [format_measure(measure, ENERGY_WATT_HOUR), log_date]
data[name] = format_measure(measure, ENERGY_WATT_HOUR)

return data

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

return loc
Expand Down Expand Up @@ -1002,8 +982,6 @@ def _power_data_from_location(self, loc_id):
)

direct_data[loc.key_string] = loc.f_val
if "interval" in loc.key_string:
direct_data[loc.key_string] = [loc.f_val, loc.log_date]

if direct_data != {}:
return direct_data
Expand Down Expand Up @@ -1178,8 +1156,6 @@ def _create_lists_from_data(self, data, bs_list, s_list, sw_list):
if item[ATTR_ID] == key:
data.pop(item[ATTR_ID])
item[ATTR_STATE] = value
if "interval" in item[ATTR_ID] and isinstance(value, list):
item[ATTR_STATE] = value[0]
s_list.append(item)
for item in SWITCHES:
if item[ATTR_ID] == key:
Expand Down

0 comments on commit be50323

Please sign in to comment.