mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
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:
committed by
Paulus Schoutsen
parent
c1b197419d
commit
78c302855a
@ -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
|
||||
|
||||
|
95
homeassistant/components/climate/toon.py
Normal file
95
homeassistant/components/climate/toon.py
Normal 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()
|
256
homeassistant/components/sensor/toon.py
Normal file
256
homeassistant/components/sensor/toon.py
Normal 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()
|
77
homeassistant/components/switch/toon.py
Normal file
77
homeassistant/components/switch/toon.py
Normal 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()
|
149
homeassistant/components/toon.py
Normal file
149
homeassistant/components/toon.py
Normal 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
|
@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user