Skip to content

Instantly share code, notes, and snippets.

@karlkar
Created June 28, 2018 19:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karlkar/60c0b65ab6b83a0dd9c56e4322ff1dc6 to your computer and use it in GitHub Desktop.
Save karlkar/60c0b65ab6b83a0dd9c56e4322ff1dc6 to your computer and use it in GitHub Desktop.
Climate component for controling LG Standard AC using scripts that send IRDA commands
"""
Adds support for AC being controlled via IRDA device.
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.climate import (
STATE_FAN_ONLY, STATE_DRY, STATE_COOL, STATE_IDLE, STATE_AUTO, STATE_PERFORMANCE, ClimateDevice,
SUPPORT_FAN_MODE, ATTR_SWING_MODE, ATTR_FAN_MODE, STATE_HEAT,
SUPPORT_SWING_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
from homeassistant.const import (
STATE_OFF, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT,
CONF_NAME, PRECISION_WHOLE)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import (async_track_state_change)
from homeassistant.helpers.restore_state import async_get_last_state
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['sensor']
DEFAULT_NAME = 'External Climate'
DEFAULT_TEMP = 22.0
DEFAULT_SENSOR = ""
CONF_MIN_TEMP = 'min_temp'
CONF_MAX_TEMP = 'max_temp'
CONF_SENSOR = 'target_sensor'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE |
SUPPORT_FAN_MODE |
SUPPORT_SWING_MODE)
OPERATION_LIST = [STATE_COOL, STATE_AUTO, STATE_DRY, STATE_HEAT, STATE_FAN_ONLY, STATE_PERFORMANCE, STATE_OFF]
FAN_AUTO = "auto"
FAN_1 = "1"
FAN_2 = "2"
FAN_3 = "3"
FAN_4 = "4"
FAN_5 = "5"
FAN_LIST = [FAN_AUTO, FAN_1, FAN_2, FAN_3, FAN_4, FAN_5]
SWING_AUTO = "auto"
SWING_HIGH = "high"
SWING_MEDIUM = "medium"
SWING_LOW = "low"
SWING_LIST = [SWING_AUTO, SWING_HIGH, SWING_MEDIUM, SWING_LOW]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SENSOR): cv.entity_id,
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the generic thermostat platform."""
name = config.get(CONF_NAME)
sensor_entity_id = config.get(CONF_SENSOR, None)
min_temp = config.get(CONF_MIN_TEMP)
max_temp = config.get(CONF_MAX_TEMP)
async_add_devices([ExternalClimate(
hass, name, sensor_entity_id, min_temp, max_temp)])
class ExternalClimate(ClimateDevice):
"""Representation of a Generic Thermostat device."""
def __init__(self, hass, name, sensor_entity_id, min_temp, max_temp):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
self._sensor_entity_id = sensor_entity_id
self._active = False
self._cur_temp = None
self._min_temp = min_temp
self._max_temp = max_temp
self._target_temp = None
self._unit = hass.config.units.temperature_unit
self._current_fan = FAN_AUTO
self._current_swing = SWING_AUTO
self._current_operation = STATE_OFF
if self._sensor_entity_id:
async_track_state_change(
hass, sensor_entity_id, self._async_sensor_changed)
@asyncio.coroutine
def async_added_to_hass(self):
"""Run when entity about to be added."""
# Check If we have an old state
old_state = yield from async_get_last_state(self.hass,
self.entity_id)
if old_state is not None:
# If we have a previously saved temperature
if old_state.attributes.get(ATTR_TEMPERATURE) is None:
self._target_temp = DEFAULT_TEMP
_LOGGER.warning("Undefined target temperature,"
"falling back to %s", self._target_temp)
else:
self._target_temp = float(
old_state.attributes[ATTR_TEMPERATURE])
if old_state.attributes.get(ATTR_SWING_MODE) is None:
self._current_swing = SWING_AUTO
else:
self._current_swing = old_state.attributes.get(ATTR_SWING_MODE)
if old_state.attributes.get(ATTR_FAN_MODE) is None:
self._current_fan = FAN_AUTO
else:
self._current_fan = old_state.attributes.get(ATTR_FAN_MODE)
else:
# No previous state, try and restore defaults
if self._target_temp is None:
self._target_temp = DEFAULT_TEMP
_LOGGER.warning("No previously saved temperature, setting to %s",
self._target_temp)
@property
def state(self):
"""Return the current state."""
if self._is_device_active:
return self.current_operation
return STATE_IDLE
@property
def should_poll(self):
"""Return the polling state."""
return False
@property
def name(self):
"""Return the name of the thermostat."""
return self._name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit
@property
def current_temperature(self):
"""Return the sensor temperature."""
return self._cur_temp
@property
def current_operation(self):
"""Return current operation."""
return self._current_operation
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temp
@property
def operation_list(self):
"""List of available operation modes."""
return OPERATION_LIST
async def async_set_operation_mode(self, operation_mode):
"""Set operation mode."""
self._current_operation = operation_mode
self._active = self._current_operation != STATE_OFF
script = self._get_proper_script()
self.hass.async_add_job(
self.hass.services.async_call("script", script))
self.hass.async_add_job(
self.hass.services.async_call("script", "remote_ac_lg_swing_" + self._current_swing))
# Ensure we update the current operation after changing the mode
self.schedule_update_ha_state()
@asyncio.coroutine
def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self._target_temp = temperature
if not self._active:
return
script = self._get_proper_script()
self.hass.async_add_job(
self.hass.services.async_call("script", script))
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode.
This method must be run in the event loop and returns a coroutine.
"""
self._current_fan = fan_mode
if not self._active:
return
script = self._get_proper_script()
self.hass.async_add_job(
self.hass.services.async_call("script", script))
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_set_swing_mode(self, swing_mode):
"""Set new target swing operation.
This method must be run in the event loop and returns a coroutine.
"""
self._current_swing = swing_mode
if not self._active:
return
self.hass.async_add_job(
self.hass.services.async_call("script", "remote_ac_lg_swing_" + swing_mode))
yield from self.async_update_ha_state()
def _get_proper_script(self):
"""This method returns the state which should be returned"""
if self._current_operation in [STATE_AUTO, STATE_HEAT, STATE_COOL]:
return "remote_ac_lg_power_" + self._current_operation + "_" + \
str(int(self._target_temp)) + "_fan_" + str(self._current_fan)
if self._current_operation == STATE_DRY:
return "remote_ac_lg_power_dry_fan_" + str(self._current_fan)
if self._current_operation == STATE_FAN_ONLY:
return "remote_ac_lg_power_fan_" + str(self._current_fan)
if self._current_operation == STATE_PERFORMANCE:
return "remote_ac_lg_jet_mode_on"
if self._current_operation == STATE_OFF:
return "remote_ac_lg_power_off"
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return PRECISION_WHOLE
@property
def min_temp(self):
"""Return the minimum temperature."""
if self._min_temp:
return self._min_temp
# get default temp from super class
return super().min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
if self._max_temp:
return self._max_temp
# Get default temp from super class
return super().max_temp
@asyncio.coroutine
def _async_sensor_changed(self, entity_id, old_state, new_state):
"""Handle temperature changes."""
if new_state is None:
return
unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
try:
self._cur_temp = self.hass.config.units.temperature(
float(new_state.state), unit)
except ValueError as ex:
_LOGGER.error("Unable to update from sensor: %s", ex)
yield from self.async_update_ha_state()
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
if self._max_temp:
return self._max_temp
# Get default temp from super class
return super().target_temperature_high
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
if self._min_temp:
return self._min_temp
# get default temp from super class
return super().target_temperature_low
@property
def _is_device_active(self):
"""If the toggleable device is currently active."""
return self._active
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return False
@property
def current_fan_mode(self):
"""Return the fan setting."""
return self._current_fan
@property
def fan_list(self):
"""Return the list of available fan modes."""
return FAN_LIST
@property
def current_swing_mode(self):
"""Return the fan setting."""
return self._current_swing
@property
def swing_list(self):
"""Return the list of available swing modes."""
return SWING_LIST
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment