core/homeassistant/components/smappee/__init__.py

331 lines
12 KiB
Python

"""Support for Smappee energy monitor."""
from datetime import datetime, timedelta
import logging
import re
from requests.exceptions import RequestException
import smappy
import voluptuous as vol
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Smappee"
DEFAULT_HOST_PASSWORD = "admin"
CONF_CLIENT_ID = "client_id"
CONF_CLIENT_SECRET = "client_secret"
CONF_HOST_PASSWORD = "host_password"
DOMAIN = "smappee"
DATA_SMAPPEE = "SMAPPEE"
_SENSOR_REGEX = re.compile(r"(?P<key>([A-Za-z]+))\=" + r"(?P<value>([0-9\.]+))")
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Inclusive(CONF_CLIENT_ID, "Server credentials"): cv.string,
vol.Inclusive(CONF_CLIENT_SECRET, "Server credentials"): cv.string,
vol.Inclusive(CONF_USERNAME, "Server credentials"): cv.string,
vol.Inclusive(CONF_PASSWORD, "Server credentials"): cv.string,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(
CONF_HOST_PASSWORD, default=DEFAULT_HOST_PASSWORD
): cv.string,
}
)
},
extra=vol.ALLOW_EXTRA,
)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
def setup(hass, config):
"""Set up the Smapee component."""
client_id = config.get(DOMAIN).get(CONF_CLIENT_ID)
client_secret = config.get(DOMAIN).get(CONF_CLIENT_SECRET)
username = config.get(DOMAIN).get(CONF_USERNAME)
password = config.get(DOMAIN).get(CONF_PASSWORD)
host = config.get(DOMAIN).get(CONF_HOST)
host_password = config.get(DOMAIN).get(CONF_HOST_PASSWORD)
smappee = Smappee(client_id, client_secret, username, password, host, host_password)
if not smappee.is_local_active and not smappee.is_remote_active:
_LOGGER.error("Neither Smappee server or local integration enabled.")
return False
hass.data[DATA_SMAPPEE] = smappee
load_platform(hass, "switch", DOMAIN, {}, config)
load_platform(hass, "sensor", DOMAIN, {}, config)
return True
class Smappee:
"""Stores data retrieved from Smappee sensor."""
def __init__(
self, client_id, client_secret, username, password, host, host_password
):
"""Initialize the data."""
self._remote_active = False
self._local_active = False
if client_id is not None:
try:
self._smappy = smappy.Smappee(client_id, client_secret)
self._smappy.authenticate(username, password)
self._remote_active = True
except RequestException as error:
self._smappy = None
_LOGGER.exception("Smappee server authentication failed (%s)", error)
else:
_LOGGER.warning("Smappee server integration init skipped.")
if host is not None:
try:
self._localsmappy = smappy.LocalSmappee(host)
self._localsmappy.logon(host_password)
self._local_active = True
except RequestException as error:
self._localsmappy = None
_LOGGER.exception(
"Local Smappee device authentication failed (%s)", error
)
else:
_LOGGER.warning("Smappee local integration init skipped.")
self.locations = {}
self.info = {}
self.consumption = {}
self.sensor_consumption = {}
self.instantaneous = {}
if self._remote_active or self._local_active:
self.update()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update data from Smappee API."""
if self.is_remote_active:
service_locations = self._smappy.get_service_locations().get(
"serviceLocations"
)
for location in service_locations:
location_id = location.get("serviceLocationId")
if location_id is not None:
self.sensor_consumption[location_id] = {}
self.locations[location_id] = location.get("name")
self.info[location_id] = self._smappy.get_service_location_info(
location_id
)
_LOGGER.debug(
"Remote info %s %s", self.locations, self.info[location_id]
)
for sensors in self.info[location_id].get("sensors"):
sensor_id = sensors.get("id")
self.sensor_consumption[location_id].update(
{
sensor_id: self.get_sensor_consumption(
location_id, sensor_id, aggregation=3, delta=1440
)
}
)
_LOGGER.debug(
"Remote sensors %s %s",
self.locations,
self.sensor_consumption[location_id],
)
self.consumption[location_id] = self.get_consumption(
location_id, aggregation=3, delta=1440
)
_LOGGER.debug(
"Remote consumption %s %s",
self.locations,
self.consumption[location_id],
)
if self.is_local_active:
self.local_devices = self.get_switches()
_LOGGER.debug("Local switches %s", self.local_devices)
self.instantaneous = self.load_instantaneous()
_LOGGER.debug("Local values %s", self.instantaneous)
@property
def is_remote_active(self):
"""Return true if Smappe server is configured and working."""
return self._remote_active
@property
def is_local_active(self):
"""Return true if Smappe local device is configured and working."""
return self._local_active
def get_switches(self):
"""Get switches from local Smappee."""
if not self.is_local_active:
return
try:
return self._localsmappy.load_command_control_config()
except RequestException as error:
_LOGGER.error("Error getting switches from local Smappee. (%s)", error)
def get_consumption(self, location_id, aggregation, delta):
"""Update data from Smappee."""
# Start & End accept epoch (in milliseconds),
# datetime and pandas timestamps
# Aggregation:
# 1 = 5 min values (only available for the last 14 days),
# 2 = hourly values,
# 3 = daily values,
# 4 = monthly values,
# 5 = quarterly values
if not self.is_remote_active:
return
end = datetime.utcnow()
start = end - timedelta(minutes=delta)
try:
return self._smappy.get_consumption(location_id, start, end, aggregation)
except RequestException as error:
_LOGGER.error("Error getting comsumption from Smappee cloud. (%s)", error)
def get_sensor_consumption(self, location_id, sensor_id, aggregation, delta):
"""Update data from Smappee."""
# Start & End accept epoch (in milliseconds),
# datetime and pandas timestamps
# Aggregation:
# 1 = 5 min values (only available for the last 14 days),
# 2 = hourly values,
# 3 = daily values,
# 4 = monthly values,
# 5 = quarterly values
if not self.is_remote_active:
return
end = datetime.utcnow()
start = end - timedelta(minutes=delta)
try:
return self._smappy.get_sensor_consumption(
location_id, sensor_id, start, end, aggregation
)
except RequestException as error:
_LOGGER.error("Error getting comsumption from Smappee cloud. (%s)", error)
def actuator_on(self, location_id, actuator_id, is_remote_switch, duration=None):
"""Turn on actuator."""
# Duration = 300,900,1800,3600
# or any other value for an undetermined period of time.
#
# The comport plugs have a tendency to ignore the on/off signal.
# And because you can't read the status of a plug, it's more
# reliable to execute the command twice.
try:
if is_remote_switch:
self._smappy.actuator_on(location_id, actuator_id, duration)
self._smappy.actuator_on(location_id, actuator_id, duration)
else:
self._localsmappy.on_command_control(actuator_id)
self._localsmappy.on_command_control(actuator_id)
except RequestException as error:
_LOGGER.error("Error turning actuator on. (%s)", error)
return False
return True
def actuator_off(self, location_id, actuator_id, is_remote_switch, duration=None):
"""Turn off actuator."""
# Duration = 300,900,1800,3600
# or any other value for an undetermined period of time.
#
# The comport plugs have a tendency to ignore the on/off signal.
# And because you can't read the status of a plug, it's more
# reliable to execute the command twice.
try:
if is_remote_switch:
self._smappy.actuator_off(location_id, actuator_id, duration)
self._smappy.actuator_off(location_id, actuator_id, duration)
else:
self._localsmappy.off_command_control(actuator_id)
self._localsmappy.off_command_control(actuator_id)
except RequestException as error:
_LOGGER.error("Error turning actuator on. (%s)", error)
return False
return True
def active_power(self):
"""Get sum of all instantaneous active power values from local hub."""
if not self.is_local_active:
return
try:
return self._localsmappy.active_power()
except RequestException as error:
_LOGGER.error("Error getting data from Local Smappee unit. (%s)", error)
def active_cosfi(self):
"""Get the average of all instantaneous cosfi values."""
if not self.is_local_active:
return
try:
return self._localsmappy.active_cosfi()
except RequestException as error:
_LOGGER.error("Error getting data from Local Smappee unit. (%s)", error)
def instantaneous_values(self):
"""ReportInstantaneousValues."""
if not self.is_local_active:
return
report_instantaneous_values = self._localsmappy.report_instantaneous_values()
report_result = report_instantaneous_values["report"].split("<BR>")
properties = {}
for lines in report_result:
lines_result = lines.split(",")
for prop in lines_result:
match = _SENSOR_REGEX.search(prop)
if match:
properties[match.group("key")] = match.group("value")
_LOGGER.debug(properties)
return properties
def active_current(self):
"""Get current active Amps."""
if not self.is_local_active:
return
properties = self.instantaneous_values()
return float(properties["current"])
def active_voltage(self):
"""Get current active Voltage."""
if not self.is_local_active:
return
properties = self.instantaneous_values()
return float(properties["voltage"])
def load_instantaneous(self):
"""LoadInstantaneous."""
if not self.is_local_active:
return
try:
return self._localsmappy.load_instantaneous()
except RequestException as error:
_LOGGER.error("Error getting data from Local Smappee unit. (%s)", error)