Add climate platform to Insteon (#35763)

This commit is contained in:
Tom Harris 2020-06-02 21:16:44 -04:00 committed by GitHub
parent 544094af21
commit b5f12bd9c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 271 additions and 0 deletions

View file

@ -0,0 +1,228 @@
"""Support for Insteon thermostat."""
import logging
from typing import List, Optional
from pyinsteon.constants import ThermostatMode
from pyinsteon.operating_flag import CELSIUS
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_COOL,
CURRENT_HVAC_FAN,
CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE,
DOMAIN,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
SUPPORT_FAN_MODE,
SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
_LOGGER = logging.getLogger(__name__)
COOLING = 1
HEATING = 2
DEHUMIDIFYING = 3
HUMIDIFYING = 4
TEMPERATURE = 10
HUMIDITY = 11
SYSTEM_MODE = 12
FAN_MODE = 13
COOL_SET_POINT = 14
HEAT_SET_POINT = 15
HUMIDITY_HIGH = 16
HUMIDITY_LOW = 17
HVAC_MODES = {
0: HVAC_MODE_OFF,
1: HVAC_MODE_HEAT,
2: HVAC_MODE_COOL,
3: HVAC_MODE_HEAT_COOL,
}
FAN_MODES = {4: HVAC_MODE_AUTO, 8: HVAC_MODE_FAN_ONLY}
SUPPORTED_FEATURES = (
SUPPORT_FAN_MODE
| SUPPORT_TARGET_HUMIDITY
| SUPPORT_TARGET_TEMPERATURE
| SUPPORT_TARGET_TEMPERATURE_RANGE
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Insteon platform."""
async_add_insteon_entities(
hass, DOMAIN, InsteonClimateEntity, async_add_entities, discovery_info
)
class InsteonClimateEntity(InsteonEntity, ClimateEntity):
"""A Class for an Insteon climate entity."""
@property
def supported_features(self):
"""Return the supported features for this entity."""
return SUPPORTED_FEATURES
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
if self._insteon_device.properties[CELSIUS].value:
return TEMP_CELSIUS
return TEMP_FAHRENHEIT
@property
def current_humidity(self) -> Optional[int]:
"""Return the current humidity."""
return self._insteon_device.groups[HUMIDITY].value
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode."""
return HVAC_MODES[self._insteon_device.groups[SYSTEM_MODE].value]
@property
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes."""
return list(HVAC_MODES.values())
@property
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return self._insteon_device.groups[TEMPERATURE].value
@property
def target_temperature(self) -> Optional[float]:
"""Return the temperature we try to reach."""
if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.HEAT:
return self._insteon_device.groups[HEAT_SET_POINT].value
if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.COOL:
return self._insteon_device.groups[COOL_SET_POINT].value
return None
@property
def target_temperature_high(self) -> Optional[float]:
"""Return the highbound target temperature we try to reach."""
if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.AUTO:
return self._insteon_device.groups[COOL_SET_POINT].value
return None
@property
def target_temperature_low(self) -> Optional[float]:
"""Return the lowbound target temperature we try to reach."""
if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.AUTO:
return self._insteon_device.groups[HEAT_SET_POINT].value
return None
@property
def fan_mode(self) -> Optional[str]:
"""Return the fan setting."""
return FAN_MODES[self._insteon_device.groups[FAN_MODE].value]
@property
def fan_modes(self) -> Optional[List[str]]:
"""Return the list of available fan modes."""
return list(FAN_MODES.values())
@property
def target_humidity(self) -> Optional[int]:
"""Return the humidity we try to reach."""
high = self._insteon_device.groups[HUMIDITY_HIGH].value
low = self._insteon_device.groups[HUMIDITY_LOW].value
# May not be loaded yet so return a default if required
return (high + low) / 2 if high and low else None
@property
def min_humidity(self) -> int:
"""Return the minimum humidity."""
return 1
@property
def hvac_action(self) -> Optional[str]:
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
if self._insteon_device.groups[COOLING].value:
return CURRENT_HVAC_COOL
if self._insteon_device.groups[HEATING].value:
return CURRENT_HVAC_HEAT
if self._insteon_device.groups[FAN_MODE].value == ThermostatMode.FAN_ALWAYS_ON:
return CURRENT_HVAC_FAN
return CURRENT_HVAC_IDLE
@property
def device_state_attributes(self):
"""Provide attributes for display on device card."""
attr = super().device_state_attributes
humidifier = "off"
if self._insteon_device.groups[DEHUMIDIFYING].value:
humidifier = "dehumidifying"
if self._insteon_device.groups[HUMIDIFYING].value:
humidifier = "humidifying"
attr["humidifier"] = humidifier
return attr
async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if target_temp is not None:
if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.HEAT:
await self._insteon_device.async_set_heat_set_point(target_temp)
elif self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.COOL:
await self._insteon_device.async_set_cool_set_point(target_temp)
else:
await self._insteon_device.async_set_heat_set_point(target_temp_low)
await self._insteon_device.async_set_cool_set_point(target_temp_high)
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
mode = list(FAN_MODES.keys())[list(FAN_MODES.values()).index(fan_mode)]
await self._insteon_device.async_set_mode(mode)
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
mode = list(HVAC_MODES.keys())[list(HVAC_MODES.values()).index(hvac_mode)]
await self._insteon_device.async_set_mode(mode)
async def async_set_humidity(self, humidity):
"""Set new humidity level."""
change = humidity - self.target_humidity
high = self._insteon_device.groups[HUMIDITY_HIGH].value + change
low = self._insteon_device.groups[HUMIDITY_LOW].value + change
await self._insteon_device.async_set_humidity_low_set_point(low)
await self._insteon_device.async_set_humidity_high_set_point(high)
async def async_added_to_hass(self):
"""Register INSTEON update events."""
await super().async_added_to_hass()
await self._insteon_device.async_read_op_flags()
for group in [
COOLING,
HEATING,
DEHUMIDIFYING,
HUMIDIFYING,
HEAT_SET_POINT,
FAN_MODE,
SYSTEM_MODE,
TEMPERATURE,
HUMIDITY,
HUMIDITY_HIGH,
HUMIDITY_LOW,
]:
self._insteon_device.groups[group].subscribe(self.async_entity_update)

View file

@ -36,6 +36,7 @@ DOMAIN = "insteon"
INSTEON_COMPONENTS = [ INSTEON_COMPONENTS = [
"binary_sensor", "binary_sensor",
"climate",
"cover", "cover",
"fan", "fan",
"light", "light",
@ -76,10 +77,12 @@ SRV_RESPONDER = "responder"
SRV_HOUSECODE = "housecode" SRV_HOUSECODE = "housecode"
SRV_SCENE_ON = "scene_on" SRV_SCENE_ON = "scene_on"
SRV_SCENE_OFF = "scene_off" SRV_SCENE_OFF = "scene_off"
SRV_ADD_DEFAULT_LINKS = "add_default_links"
SIGNAL_LOAD_ALDB = "load_aldb" SIGNAL_LOAD_ALDB = "load_aldb"
SIGNAL_PRINT_ALDB = "print_aldb" SIGNAL_PRINT_ALDB = "print_aldb"
SIGNAL_SAVE_DEVICES = "save_devices" SIGNAL_SAVE_DEVICES = "save_devices"
SIGNAL_ADD_DEFAULT_LINKS = "add_default_links"
HOUSECODES = [ HOUSECODES = [
"a", "a",

View file

@ -9,6 +9,7 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from .const import ( from .const import (
SIGNAL_ADD_DEFAULT_LINKS,
SIGNAL_LOAD_ALDB, SIGNAL_LOAD_ALDB,
SIGNAL_PRINT_ALDB, SIGNAL_PRINT_ALDB,
SIGNAL_SAVE_DEVICES, SIGNAL_SAVE_DEVICES,
@ -96,6 +97,10 @@ class InsteonEntity(Entity):
) )
print_signal = f"{self.entity_id}_{SIGNAL_PRINT_ALDB}" print_signal = f"{self.entity_id}_{SIGNAL_PRINT_ALDB}"
async_dispatcher_connect(self.hass, print_signal, self._print_aldb) async_dispatcher_connect(self.hass, print_signal, self._print_aldb)
default_links_signal = f"{self.entity_id}_{SIGNAL_ADD_DEFAULT_LINKS}"
async_dispatcher_connect(
self.hass, default_links_signal, self._async_add_default_links
)
async def _async_read_aldb(self, reload): async def _async_read_aldb(self, reload):
"""Call device load process and print to log.""" """Call device load process and print to log."""
@ -116,3 +121,7 @@ class InsteonEntity(Entity):
else: else:
label = f"Group {self.group:d}" label = f"Group {self.group:d}"
return label return label
async def _async_add_default_links(self):
"""Add default links between the device and the modem."""
await self._insteon_device.async_add_default_links(self.address)

View file

@ -2,6 +2,8 @@
import logging import logging
from pyinsteon.device_types import ( from pyinsteon.device_types import (
ClimateControl_Thermostat,
ClimateControl_WirelessThermostat,
DimmableLightingControl, DimmableLightingControl,
DimmableLightingControl_DinRail, DimmableLightingControl_DinRail,
DimmableLightingControl_FanLinc, DimmableLightingControl_FanLinc,
@ -40,6 +42,7 @@ from pyinsteon.device_types import (
) )
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR
from homeassistant.components.climate import DOMAIN as CLIMATE
from homeassistant.components.cover import DOMAIN as COVER from homeassistant.components.cover import DOMAIN as COVER
from homeassistant.components.fan import DOMAIN as FAN from homeassistant.components.fan import DOMAIN as FAN
from homeassistant.components.light import DOMAIN as LIGHT from homeassistant.components.light import DOMAIN as LIGHT
@ -95,6 +98,8 @@ DEVICE_PLATFORM = {
SwitchedLightingControl_OutletLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, SwitchedLightingControl_OutletLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]},
SwitchedLightingControl_SwitchLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, SwitchedLightingControl_SwitchLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]},
SwitchedLightingControl_ToggleLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, SwitchedLightingControl_ToggleLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]},
ClimateControl_Thermostat: {CLIMATE: [1]},
ClimateControl_WirelessThermostat: {CLIMATE: [1]},
WindowCovering: {COVER: [1]}, WindowCovering: {COVER: [1]},
X10Dimmable: {LIGHT: [1]}, X10Dimmable: {LIGHT: [1]},
X10OnOff: {SWITCH: [1]}, X10OnOff: {SWITCH: [1]},

View file

@ -147,3 +147,6 @@ X10_HOUSECODE_SCHEMA = vol.Schema({vol.Required(SRV_HOUSECODE): vol.In(HOUSECODE
TRIGGER_SCENE_SCHEMA = vol.Schema( TRIGGER_SCENE_SCHEMA = vol.Schema(
{vol.Required(SRV_ALL_LINK_GROUP): vol.Range(min=0, max=255)} {vol.Required(SRV_ALL_LINK_GROUP): vol.Range(min=0, max=255)}
) )
ADD_DEFAULT_LINKS_SCHEMA = vol.Schema({vol.Required(CONF_ENTITY_ID): cv.entity_id})

View file

@ -60,3 +60,9 @@ scene_off:
group: group:
description: INSTEON group or scene number description: INSTEON group or scene number
example: 26 example: 26
add_default_links:
description: Add the default links between the device and the Insteon Modem (IM)
fields:
entity_id:
description: Name of the device to load. Use "all" to load the database of all devices.
example: "light.1a2b3c"

View file

@ -36,10 +36,12 @@ from .const import (
EVENT_GROUP_ON, EVENT_GROUP_ON,
EVENT_GROUP_ON_FAST, EVENT_GROUP_ON_FAST,
ON_OFF_EVENTS, ON_OFF_EVENTS,
SIGNAL_ADD_DEFAULT_LINKS,
SIGNAL_LOAD_ALDB, SIGNAL_LOAD_ALDB,
SIGNAL_PRINT_ALDB, SIGNAL_PRINT_ALDB,
SIGNAL_SAVE_DEVICES, SIGNAL_SAVE_DEVICES,
SRV_ADD_ALL_LINK, SRV_ADD_ALL_LINK,
SRV_ADD_DEFAULT_LINKS,
SRV_ALL_LINK_GROUP, SRV_ALL_LINK_GROUP,
SRV_ALL_LINK_MODE, SRV_ALL_LINK_MODE,
SRV_CONTROLLER, SRV_CONTROLLER,
@ -58,6 +60,7 @@ from .const import (
from .ipdb import get_device_platforms, get_platform_groups from .ipdb import get_device_platforms, get_platform_groups
from .schemas import ( from .schemas import (
ADD_ALL_LINK_SCHEMA, ADD_ALL_LINK_SCHEMA,
ADD_DEFAULT_LINKS_SCHEMA,
DEL_ALL_LINK_SCHEMA, DEL_ALL_LINK_SCHEMA,
LOAD_ALDB_SCHEMA, LOAD_ALDB_SCHEMA,
PRINT_ALDB_SCHEMA, PRINT_ALDB_SCHEMA,
@ -231,6 +234,13 @@ def async_register_services(hass):
group = service.data.get(SRV_ALL_LINK_GROUP) group = service.data.get(SRV_ALL_LINK_GROUP)
await async_trigger_scene_off(group) await async_trigger_scene_off(group)
@callback
def async_add_default_links(service):
"""Add the default All-Link entries to a device."""
entity_id = service.data[CONF_ENTITY_ID]
signal = f"{entity_id}_{SIGNAL_ADD_DEFAULT_LINKS}"
async_dispatcher_send(hass, signal)
hass.services.async_register( hass.services.async_register(
DOMAIN, SRV_ADD_ALL_LINK, async_srv_add_all_link, schema=ADD_ALL_LINK_SCHEMA DOMAIN, SRV_ADD_ALL_LINK, async_srv_add_all_link, schema=ADD_ALL_LINK_SCHEMA
) )
@ -268,6 +278,13 @@ def async_register_services(hass):
hass.services.async_register( hass.services.async_register(
DOMAIN, SRV_SCENE_OFF, async_srv_scene_off, schema=TRIGGER_SCENE_SCHEMA DOMAIN, SRV_SCENE_OFF, async_srv_scene_off, schema=TRIGGER_SCENE_SCHEMA
) )
hass.services.async_register(
DOMAIN,
SRV_ADD_DEFAULT_LINKS,
async_add_default_links,
schema=ADD_DEFAULT_LINKS_SCHEMA,
)
async_dispatcher_connect(hass, SIGNAL_SAVE_DEVICES, async_srv_save_devices) async_dispatcher_connect(hass, SIGNAL_SAVE_DEVICES, async_srv_save_devices)
_LOGGER.debug("Insteon Services registered") _LOGGER.debug("Insteon Services registered")