Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update SMA sensor #17988

Merged
merged 3 commits into from Oct 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
102 changes: 38 additions & 64 deletions homeassistant/components/sensor/sma.py
Expand Up @@ -12,14 +12,14 @@

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_SSL,
CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP)
CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_SSL, CONF_VERIFY_SSL,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval

REQUIREMENTS = ['pysma==0.2']
REQUIREMENTS = ['pysma==0.2.2']

_LOGGER = logging.getLogger(__name__)

Expand All @@ -30,35 +30,28 @@
CONF_SENSORS = 'sensors'
CONF_UNIT = 'unit'

GROUP_INSTALLER = 'installer'
GROUP_USER = 'user'
GROUPS = [GROUP_USER, GROUP_INSTALLER]

SENSOR_OPTIONS = [
'current_consumption',
'current_power',
'total_consumption',
'total_yield',
]
GROUPS = ['user', 'installer']


def _check_sensor_schema(conf):
"""Check sensors and attributes are valid."""
import pysma
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requirements are not available when config validation is taking place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx, I asked in the chat, but had no reply there. Will update & move this to the setup routine


valid = list(conf[CONF_CUSTOM].keys())
valid.extend(SENSOR_OPTIONS)
for sensor, attrs in conf[CONF_SENSORS].items():
if sensor not in valid:
raise vol.Invalid("{} does not exist".format(sensor))
valid.extend([s.name for s in pysma.SENSORS])
for sname, attrs in conf[CONF_SENSORS].items():
if sname not in valid:
raise vol.Invalid("{} does not exist".format(sname))
for attr in attrs:
if attr in valid:
continue
raise vol.Invalid("{} does not exist [{}]".format(attr, sensor))
raise vol.Invalid("{} does not exist [{}]".format(attr, sname))
return conf


CUSTOM_SCHEMA = vol.Any({
vol.Required(CONF_KEY):
vol.All(cv.string, vol.Length(min=13, max=13)),
vol.All(cv.string, vol.Length(min=13, max=15)),
vol.Required(CONF_UNIT): cv.string,
vol.Optional(CONF_FACTOR, default=1): vol.Coerce(float),
})
Expand All @@ -80,37 +73,26 @@ async def async_setup_platform(
"""Set up SMA WebConnect sensor."""
import pysma

# Sensor_defs from the library
sensor_defs = dict(zip(SENSOR_OPTIONS, [
(pysma.KEY_CURRENT_CONSUMPTION_W, 'W', 1),
(pysma.KEY_CURRENT_POWER_W, 'W', 1),
(pysma.KEY_TOTAL_CONSUMPTION_KWH, 'kWh', 1000),
(pysma.KEY_TOTAL_YIELD_KWH, 'kWh', 1000)]))

# Sensor_defs from the custom config
for name, prop in config[CONF_CUSTOM].items():
if name in sensor_defs:
_LOGGER.warning("Custom sensor %s replace built-in sensor", name)
sensor_defs[name] = (prop['key'], prop['unit'], prop['factor'])
n_s = pysma.Sensor(name, prop['key'], prop['unit'], prop['factor'])
pysma.add_sensor(n_s)

# Prepare all HASS sensor entities
hass_sensors = []
used_sensors = []
for name, attr in config[CONF_SENSORS].items():
hass_sensors.append(SMAsensor(name, attr, sensor_defs))
sub_sensors = [pysma.get_sensor(s) for s in attr]
hass_sensors.append(SMAsensor(pysma.get_sensor(name), sub_sensors))
used_sensors.append(name)
used_sensors.extend(attr)

# Remove sensor_defs not in use
sensor_defs = {name: val for name, val in sensor_defs.items()
if name in used_sensors}

async_add_entities(hass_sensors)
used_sensors = [pysma.get_sensor(s) for s in set(used_sensors)]

# Init the SMA interface
session = async_get_clientsession(hass, verify_ssl=config[CONF_VERIFY_SSL])
grp = {GROUP_INSTALLER: pysma.GROUP_INSTALLER,
GROUP_USER: pysma.GROUP_USER}[config[CONF_GROUP]]
grp = config[CONF_GROUP]

url = "http{}://{}".format(
"s" if config[CONF_SSL] else "", config[CONF_HOST])
Expand All @@ -124,10 +106,6 @@ async def async_close_session(event):

hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, async_close_session)

# Read SMA values periodically & update sensors
names_to_query = list(sensor_defs.keys())
keys_to_query = [sensor_defs[name][0] for name in names_to_query]

backoff = 0

async def async_sma(event):
Expand All @@ -137,17 +115,14 @@ async def async_sma(event):
backoff -= 1
return

values = await sma.read(keys_to_query)
values = await sma.read(used_sensors)
if values is None:
backoff = 3
backoff = 10
return
values = [0 if val is None else val for val in values]
res = dict(zip(names_to_query, values))
res = {key: val // sensor_defs[key][2] for key, val in res.items()}
_LOGGER.debug("Update sensors %s %s %s", keys_to_query, values, res)

tasks = []
for sensor in hass_sensors:
task = sensor.async_update_values(res)
task = sensor.async_update_values()
if task:
tasks.append(task)
if tasks:
Expand All @@ -160,18 +135,18 @@ async def async_sma(event):
class SMAsensor(Entity):
"""Representation of a SMA sensor."""

def __init__(self, sensor_name, attr, sensor_defs):
def __init__(self, pysma_sensor, sub_sensors):
"""Initialize the sensor."""
self._name = sensor_name
self._key, self._unit_of_measurement, _ = sensor_defs[sensor_name]
self._state = None
self._sensor_defs = sensor_defs
self._attr = {att: "" for att in attr}
self._sensor = pysma_sensor
self._sub_sensors = sub_sensors

self._attr = {s.name: "" for s in sub_sensors}
self._state = self._sensor.value

@property
def name(self):
"""Return the name of the sensor."""
return self._name
return self._sensor.name

@property
def state(self):
Expand All @@ -181,7 +156,7 @@ def state(self):
@property
def unit_of_measurement(self):
"""Return the unit the value is expressed in."""
return self._unit_of_measurement
return self._sensor.unit

@property
def device_state_attributes(self):
Expand All @@ -193,19 +168,18 @@ def poll(self):
"""SMA sensors are updated & don't poll."""
return False

def async_update_values(self, key_values):
"""Update this sensor using the data."""
def async_update_values(self):
"""Update this sensor."""
update = False

for key, val in self._attr.items():
newval = '{} {}'.format(key_values[key], self._sensor_defs[key][1])
if val != newval:
for sens in self._sub_sensors:
newval = '{} {}'.format(sens.value, sens.unit)
if self._attr[sens.name] != newval:
update = True
self._attr[key] = newval
self._attr[sens.name] = newval

new_state = key_values[self._name]
if new_state != self._state:
if self._sensor.value != self._state:
update = True
self._state = new_state
self._state = self._sensor.value

return self.async_update_ha_state() if update else None
2 changes: 1 addition & 1 deletion requirements_all.txt
Expand Up @@ -1084,7 +1084,7 @@ pysesame==0.1.0
pysher==1.0.4

# homeassistant.components.sensor.sma
pysma==0.2
pysma==0.2.2

# homeassistant.components.device_tracker.snmp
# homeassistant.components.sensor.snmp
Expand Down