Add Toon support (#9483)

* Added Toon support again

* Forgot about .coveragerc

* Fixed style issues

* More styling and importing fixes

* Implemented the suggestions made by @pvizeli

* The smallest fix possible

* Removed custom names for Toon states

* Fix last push with 2 outdated lines

* Removed HOME and NOT_HOME, moved to just climate states

* Bumped dependency for better handling of smartplugs that don't report power consumption

* Implemented changes as suggested by @balloob

* Rebase, gen_requirements_all.py finally working
This commit is contained in:
boltgolt
2017-10-19 17:59:57 +02:00
committed by Paulus Schoutsen
parent c1b197419d
commit 78c302855a
6 changed files with 583 additions and 0 deletions

View File

@ -190,6 +190,9 @@ omit =
homeassistant/components/*/thinkingcleaner.py
homeassistant/components/toon.py
homeassistant/components/*/toon.py
homeassistant/components/tradfri.py
homeassistant/components/*/tradfri.py

View File

@ -0,0 +1,95 @@
"""
Toon van Eneco Thermostat Support.
This provides a component for the rebranded Quby thermostat as provided by
Eneco.
"""
from homeassistant.components.climate import (ClimateDevice,
ATTR_TEMPERATURE,
STATE_PERFORMANCE,
STATE_HEAT,
STATE_ECO,
STATE_COOL)
from homeassistant.const import TEMP_CELSIUS
import homeassistant.components.toon as toon_main
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup thermostat."""
# Add toon
add_devices((ThermostatDevice(hass), ), True)
class ThermostatDevice(ClimateDevice):
"""Interface class for the toon module and HA."""
def __init__(self, hass):
"""Initialize the device."""
self._name = 'Toon van Eneco'
self.hass = hass
self.thermos = hass.data[toon_main.TOON_HANDLE]
# set up internal state vars
self._state = None
self._temperature = None
self._setpoint = None
self._operation_list = [STATE_PERFORMANCE,
STATE_HEAT,
STATE_ECO,
STATE_COOL]
@property
def name(self):
"""Name of this Thermostat."""
return self._name
@property
def should_poll(self):
"""Polling is required."""
return True
@property
def temperature_unit(self):
"""The unit of measurement used by the platform."""
return TEMP_CELSIUS
@property
def current_operation(self):
"""Return current operation i.e. comfort, home, away."""
state = self.thermos.get_data('state')
return state
@property
def operation_list(self):
"""List of available operation modes."""
return self._operation_list
@property
def current_temperature(self):
"""Return the current temperature."""
return self.thermos.get_data('temp')
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.thermos.get_data('setpoint')
def set_temperature(self, **kwargs):
"""Change the setpoint of the thermostat."""
temp = kwargs.get(ATTR_TEMPERATURE)
self.thermos.set_temp(temp)
def set_operation_mode(self, operation_mode):
"""Set new operation mode as toonlib requires it."""
toonlib_values = {STATE_PERFORMANCE: 'Comfort',
STATE_HEAT: 'Home',
STATE_ECO: 'Away',
STATE_COOL: 'Sleep'}
self.thermos.set_state(toonlib_values[operation_mode])
def update(self):
"""Update local state."""
self.thermos.update()

View File

@ -0,0 +1,256 @@
"""
Toon van Eneco Utility Gages.
This provides a component for the rebranded Quby thermostat as provided by
Eneco.
"""
import logging
import datetime as datetime
from homeassistant.helpers.entity import Entity
import homeassistant.components.toon as toon_main
_LOGGER = logging.getLogger(__name__)
STATE_ATTR_DEVICE_TYPE = "device_type"
STATE_ATTR_LAST_CONNECTED_CHANGE = "last_connected_change"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup sensors."""
_toon_main = hass.data[toon_main.TOON_HANDLE]
sensor_items = []
sensor_items.extend([ToonSensor(hass,
'Power_current',
'power-plug',
'Watt'),
ToonSensor(hass,
'Power_today',
'power-plug',
'kWh')])
if _toon_main.gas:
sensor_items.extend([ToonSensor(hass,
'Gas_current',
'gas-cylinder',
'CM3'),
ToonSensor(hass,
'Gas_today',
'gas-cylinder',
'M3')])
for plug in _toon_main.toon.smartplugs:
sensor_items.extend([
FibaroSensor(hass,
'{}_current_power'.format(plug.name),
plug.name,
'power-socket-eu',
'Watt'),
FibaroSensor(hass,
'{}_today_energy'.format(plug.name),
plug.name,
'power-socket-eu',
'kWh')])
if _toon_main.toon.solar.produced or _toon_main.solar:
sensor_items.extend([
SolarSensor(hass, 'Solar_maximum', 'kWh'),
SolarSensor(hass, 'Solar_produced', 'kWh'),
SolarSensor(hass, 'Solar_value', 'Watt'),
SolarSensor(hass, 'Solar_average_produced', 'kWh'),
SolarSensor(hass, 'Solar_meter_reading_low_produced', 'kWh'),
SolarSensor(hass, 'Solar_meter_reading_produced', 'kWh'),
SolarSensor(hass, 'Solar_daily_cost_produced', 'Euro')
])
for smokedetector in _toon_main.toon.smokedetectors:
sensor_items.append(
FibaroSmokeDetector(hass,
'{}_smoke_detector'.format(smokedetector.name),
smokedetector.device_uuid,
'alarm-bell',
'%'))
add_devices(sensor_items)
class ToonSensor(Entity):
"""Representation of a sensor."""
def __init__(self, hass, name, icon, unit_of_measurement):
"""Initialize the sensor."""
self._name = name
self._state = None
self._icon = "mdi:" + icon
self._unit_of_measurement = unit_of_measurement
self.thermos = hass.data[toon_main.TOON_HANDLE]
@property
def should_poll(self):
"""Polling required."""
return True
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def icon(self):
"""Return the mdi icon of the sensor."""
return self._icon
@property
def state(self):
"""Return the state of the sensor."""
return self.thermos.get_data(self.name.lower())
@property
def unit_of_measurement(self):
"""Return the unit this state is expressed in."""
return self._unit_of_measurement
def update(self):
"""Get the latest data from the sensor."""
self.thermos.update()
class FibaroSensor(Entity):
"""Representation of a sensor."""
def __init__(self, hass, name, plug_name, icon, unit_of_measurement):
"""Initialize the sensor."""
self._name = name
self._plug_name = plug_name
self._state = None
self._icon = "mdi:" + icon
self._unit_of_measurement = unit_of_measurement
self.toon = hass.data[toon_main.TOON_HANDLE]
@property
def should_poll(self):
"""Polling required."""
return True
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def icon(self):
"""Return the mdi icon of the sensor."""
return self._icon
@property
def state(self):
"""Return the state of the sensor."""
value = '_'.join(self.name.lower().split('_')[1:])
return self.toon.get_data(value, self._plug_name)
@property
def unit_of_measurement(self):
"""Return the unit this state is expressed in."""
return self._unit_of_measurement
def update(self):
"""Get the latest data from the sensor."""
self.toon.update()
class SolarSensor(Entity):
"""Representation of a sensor."""
def __init__(self, hass, name, unit_of_measurement):
"""Initialize the sensor."""
self._name = name
self._state = None
self._icon = "mdi:weather-sunny"
self._unit_of_measurement = unit_of_measurement
self.toon = hass.data[toon_main.TOON_HANDLE]
@property
def should_poll(self):
"""Polling required."""
return True
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def icon(self):
"""Return the mdi icon of the sensor."""
return self._icon
@property
def state(self):
"""Return the state of the sensor."""
return self.toon.get_data(self.name.lower())
@property
def unit_of_measurement(self):
"""Return the unit this state is expressed in."""
return self._unit_of_measurement
def update(self):
"""Get the latest data from the sensor."""
self.toon.update()
class FibaroSmokeDetector(Entity):
"""Representation of a smoke detector."""
def __init__(self, hass, name, uid, icon, unit_of_measurement):
"""Initialize the sensor."""
self._name = name
self._uid = uid
self._state = None
self._icon = "mdi:" + icon
self._unit_of_measurement = unit_of_measurement
self.toon = hass.data[toon_main.TOON_HANDLE]
@property
def should_poll(self):
"""Polling required."""
return True
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def icon(self):
"""Return the mdi icon of the sensor."""
return self._icon
@property
def state_attributes(self):
"""Return the state attributes of the smoke detectors."""
value = datetime.datetime.fromtimestamp(
int(self.toon.get_data('last_connected_change', self.name))
).strftime('%Y-%m-%d %H:%M:%S')
return {
STATE_ATTR_DEVICE_TYPE: self.toon.get_data('device_type',
self.name),
STATE_ATTR_LAST_CONNECTED_CHANGE: value
}
@property
def state(self):
"""Return the state of the sensor."""
value = self.name.lower().split('_', 1)[1]
return self.toon.get_data(value, self.name)
@property
def unit_of_measurement(self):
"""Return the unit this state is expressed in."""
return self._unit_of_measurement
def update(self):
"""Get the latest data from the sensor."""
self.toon.update()

View File

@ -0,0 +1,77 @@
"""
Support for Eneco Slimmer stekkers (Smart Plugs).
This provides controlls for the z-wave smart plugs Toon can control.
"""
import logging
from homeassistant.components.switch import SwitchDevice
import homeassistant.components.toon as toon_main
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup discovered Smart Plugs."""
_toon_main = hass.data[toon_main.TOON_HANDLE]
switch_items = []
for plug in _toon_main.toon.smartplugs:
switch_items.append(EnecoSmartPlug(hass, plug))
add_devices_callback(switch_items)
class EnecoSmartPlug(SwitchDevice):
"""Representation of a Smart Plug."""
def __init__(self, hass, plug):
"""Initialize the Smart Plug."""
self.smartplug = plug
self.toon_data_store = hass.data[toon_main.TOON_HANDLE]
@property
def should_poll(self):
"""No polling needed with subscriptions."""
return True
@property
def unique_id(self):
"""Return the ID of this switch."""
return self.smartplug.device_uuid
@property
def name(self):
"""Return the name of the switch if any."""
return self.smartplug.name
@property
def current_power_w(self):
"""Current power usage in W."""
return self.toon_data_store.get_data('current_power', self.name)
@property
def today_energy_kwh(self):
"""Today total energy usage in kWh."""
return self.toon_data_store.get_data('today_energy', self.name)
@property
def is_on(self):
"""Return true if switch is on. Standby is on."""
return self.toon_data_store.get_data('current_state', self.name)
@property
def available(self):
"""True if switch is available."""
return self.smartplug.can_toggle
def turn_on(self, **kwargs):
"""Turn the switch on."""
return self.smartplug.turn_on()
def turn_off(self):
"""Turn the switch off."""
return self.smartplug.turn_off()
def update(self):
"""Update state."""
self.toon_data_store.update()

View File

@ -0,0 +1,149 @@
"""
Toon van Eneco Support.
This provides a component for the rebranded Quby thermostat as provided by
Eneco.
"""
import logging
from datetime import datetime, timedelta
import voluptuous as vol
# Import the device class from the component that you want to support
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD)
from homeassistant.helpers.discovery import load_platform
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
# Home Assistant depends on 3rd party packages for API specific code.
REQUIREMENTS = ['toonlib==1.0.2']
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
DOMAIN = 'toon'
TOON_HANDLE = 'toon_handle'
CONF_GAS = 'gas'
DEFAULT_GAS = True
CONF_SOLAR = 'solar'
DEFAULT_SOLAR = False
# Validation of the user's configuration
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_GAS, default=DEFAULT_GAS): cv.boolean,
vol.Optional(CONF_SOLAR, default=DEFAULT_SOLAR): cv.boolean,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup toon."""
from toonlib import InvalidCredentials
gas = config['toon']['gas']
solar = config['toon']['solar']
try:
hass.data[TOON_HANDLE] = ToonDataStore(config['toon']['username'],
config['toon']['password'],
gas,
solar)
except InvalidCredentials:
return False
# Load all platforms
for platform in ('climate', 'sensor', 'switch'):
load_platform(hass, platform, DOMAIN, {}, config)
# Initialization successfull
return True
class ToonDataStore:
"""An object to store the toon data."""
def __init__(self, username, password, gas=DEFAULT_GAS,
solar=DEFAULT_SOLAR):
"""Initialize toon."""
from toonlib import Toon
# Creating the class
toon = Toon(username, password)
self.toon = toon
self.gas = gas
self.solar = solar
self.data = {}
self.last_update = datetime.min
self.update()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update toon data."""
self.last_update = datetime.now()
self.data['power_current'] = self.toon.power.value
self.data['power_today'] = round(
(float(self.toon.power.daily_usage) +
float(self.toon.power.daily_usage_low)) / 1000, 2)
self.data['temp'] = self.toon.temperature
if self.toon.thermostat_state:
self.data['state'] = self.toon.thermostat_state.name
else:
self.data['state'] = 'Manual'
self.data['setpoint'] = float(
self.toon.thermostat_info.current_set_point) / 100
self.data['gas_current'] = self.toon.gas.value
self.data['gas_today'] = round(float(self.toon.gas.daily_usage) /
1000, 2)
for plug in self.toon.smartplugs:
self.data[plug.name] = {'current_power': plug.current_usage,
'today_energy': round(
float(plug.daily_usage) / 1000, 2),
'current_state': plug.current_state,
'is_connected': plug.is_connected}
self.data['solar_maximum'] = self.toon.solar.maximum
self.data['solar_produced'] = self.toon.solar.produced
self.data['solar_value'] = self.toon.solar.value
self.data['solar_average_produced'] = self.toon.solar.average_produced
self.data['solar_meter_reading_low_produced'] = \
self.toon.solar.meter_reading_low_produced
self.data['solar_meter_reading_produced'] = \
self.toon.solar.meter_reading_produced
self.data['solar_daily_cost_produced'] = \
self.toon.solar.daily_cost_produced
for detector in self.toon.smokedetectors:
value = '{}_smoke_detector'.format(detector.name)
self.data[value] = {'smoke_detector': detector.battery_level,
'device_type': detector.device_type,
'is_connected': detector.is_connected,
'last_connected_change':
detector.last_connected_change}
def set_state(self, state):
"""Push a new state to the Toon unit."""
self.toon.thermostat_state = state
def set_temp(self, temp):
"""Push a new temperature to the Toon unit."""
self.toon.thermostat = temp
def get_data(self, data_id, plug_name=None):
"""Get the cached data."""
data = {'error': 'no data'}
if plug_name:
if data_id in self.data[plug_name]:
data = self.data[plug_name][data_id]
else:
if data_id in self.data:
data = self.data[data_id]
return data

View File

@ -1014,6 +1014,9 @@ tikteck==0.4
# homeassistant.components.calendar.todoist
todoist-python==7.0.17
# homeassistant.components.toon
toonlib==1.0.2
# homeassistant.components.alarm_control_panel.totalconnect
total_connect_client==0.11