kiwijunglist

broadlink.py

Apr 1st, 2018
160
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.27 KB | None | 0 0
  1. import asyncio
  2. import logging
  3. import binascii
  4. import socket
  5. import os.path
  6. import voluptuous as vol
  7. import homeassistant.helpers.config_validation as cv
  8.  
  9. from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA, STATE_IDLE, STATE_HEAT, STATE_COOL, STATE_AUTO,
  10. SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE)
  11. from homeassistant.const import (ATTR_UNIT_OF_MEASUREMENT, ATTR_TEMPERATURE, CONF_NAME, CONF_HOST, CONF_MAC, CONF_TIMEOUT, CONF_CUSTOMIZE)
  12. from homeassistant.helpers.event import (async_track_state_change)
  13. from homeassistant.core import callback
  14. from homeassistant.helpers.restore_state import async_get_last_state
  15. from configparser import ConfigParser
  16. from base64 import b64encode, b64decode
  17.  
  18. REQUIREMENTS = [
  19.     'https://github.com/balloob/python-broadlink/archive/'
  20.     '3580ff2eaccd267846f14246d6ede6e30671f7c6.zip#broadlink==0.5.1']
  21.  
  22. _LOGGER = logging.getLogger(__name__)
  23.  
  24. SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE
  25.  
  26. CONF_IRCODES_INI = 'ircodes_ini'
  27. CONF_MIN_TEMP = 'min_temp'
  28. CONF_MAX_TEMP = 'max_temp'
  29. CONF_TARGET_TEMP = 'target_temp'
  30. CONF_TEMP_SENSOR = 'temp_sensor'
  31. CONF_OPERATIONS = 'operations'
  32. CONF_FAN_MODES = 'fan_modes'
  33. CONF_DEFAULT_OPERATION = 'default_operation'
  34. CONF_DEFAULT_FAN_MODE = 'default_fan_mode'
  35.  
  36. CONF_DEFAULT_OPERATION_FROM_IDLE = 'default_operation_from_idle'
  37.  
  38. DEFAULT_NAME = 'Broadlink IR Climate'
  39. DEFAULT_TIMEOUT = 10
  40. DEFAULT_RETRY = 3
  41. DEFAULT_MIN_TEMP = 16
  42. DEFAULT_MAX_TEMP = 30
  43. DEFAULT_TARGET_TEMP = 20
  44. DEFAULT_OPERATION_LIST = [STATE_IDLE, STATE_HEAT, STATE_COOL, STATE_AUTO]
  45. DEFAULT_FAN_MODE_LIST = ['low', 'mid', 'high', 'auto']
  46. DEFAULT_OPERATION = 'idle'
  47. DEFAULT_FAN_MODE = 'auto'
  48.  
  49. CUSTOMIZE_SCHEMA = vol.Schema({
  50.     vol.Optional(CONF_OPERATIONS): vol.All(cv.ensure_list, [cv.string]),
  51.     vol.Optional(CONF_FAN_MODES): vol.All(cv.ensure_list, [cv.string])
  52. })
  53.  
  54. PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
  55.     vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
  56.     vol.Required(CONF_HOST): cv.string,
  57.     vol.Required(CONF_MAC): cv.string,
  58.     vol.Required(CONF_IRCODES_INI): cv.string,
  59.     vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
  60.     vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): cv.positive_int,
  61.     vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): cv.positive_int,
  62.     vol.Optional(CONF_TARGET_TEMP, default=DEFAULT_TARGET_TEMP): cv.positive_int,
  63.     vol.Optional(CONF_TEMP_SENSOR): cv.entity_id,
  64.     vol.Optional(CONF_CUSTOMIZE, default={}): CUSTOMIZE_SCHEMA,
  65.     vol.Optional(CONF_DEFAULT_OPERATION, default=DEFAULT_OPERATION): cv.string,
  66.     vol.Optional(CONF_DEFAULT_FAN_MODE, default=DEFAULT_FAN_MODE): cv.string,
  67.     vol.Optional(CONF_DEFAULT_OPERATION_FROM_IDLE): cv.string
  68. })
  69.  
  70. @asyncio.coroutine
  71. def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
  72.     """Set up the Broadlink IR Climate platform."""
  73.     name = config.get(CONF_NAME)
  74.     ip_addr = config.get(CONF_HOST)
  75.     mac_addr = binascii.unhexlify(config.get(CONF_MAC).encode().replace(b':', b''))
  76.      
  77.     min_temp = config.get(CONF_MIN_TEMP)
  78.     max_temp = config.get(CONF_MAX_TEMP)
  79.     target_temp = config.get(CONF_TARGET_TEMP)
  80.     temp_sensor_entity_id = config.get(CONF_TEMP_SENSOR)
  81.     operation_list = config.get(CONF_CUSTOMIZE).get(CONF_OPERATIONS, []) or DEFAULT_OPERATION_LIST
  82.     fan_list = config.get(CONF_CUSTOMIZE).get(CONF_FAN_MODES, []) or DEFAULT_FAN_MODE_LIST
  83.     default_operation = config.get(CONF_DEFAULT_OPERATION)
  84.     default_fan_mode = config.get(CONF_DEFAULT_FAN_MODE)
  85.    
  86.     default_operation_from_idle = config.get(CONF_DEFAULT_OPERATION_FROM_IDLE)
  87.    
  88.     import broadlink
  89.    
  90.     broadlink_device = broadlink.rm((ip_addr, 80), mac_addr)
  91.     broadlink_device.timeout = config.get(CONF_TIMEOUT)
  92.  
  93.     try:
  94.         broadlink_device.auth()
  95.     except socket.timeout:
  96.         _LOGGER.error("Failed to connect to Broadlink RM Device")
  97.    
  98.    
  99.     ircodes_ini_file = config.get(CONF_IRCODES_INI)
  100.    
  101.     if ircodes_ini_file.startswith("/"):
  102.         ircodes_ini_file = ircodes_ini_file[1:]
  103.        
  104.     ircodes_ini_path = hass.config.path(ircodes_ini_file)
  105.    
  106.     if os.path.exists(ircodes_ini_path):
  107.         ircodes_ini = ConfigParser()
  108.         ircodes_ini.read(ircodes_ini_path)
  109.     else:
  110.         _LOGGER.error("The ini file was not found. (" + ircodes_ini_path + ")")
  111.         return
  112.    
  113.     async_add_devices([
  114.         BroadlinkIRClimate(hass, name, broadlink_device, ircodes_ini, min_temp, max_temp, target_temp, temp_sensor_entity_id, operation_list, fan_list, default_operation, default_fan_mode, default_operation_from_idle)
  115.     ])
  116.  
  117. class BroadlinkIRClimate(ClimateDevice):
  118.  
  119.     def __init__(self, hass, name, broadlink_device, ircodes_ini, min_temp, max_temp, target_temp, temp_sensor_entity_id, operation_list, fan_list, default_operation, default_fan_mode, default_operation_from_idle):
  120.                  
  121.         """Initialize the Broadlink IR Climate device."""
  122.         self.hass = hass
  123.         self._name = name
  124.  
  125.         self._min_temp = min_temp
  126.         self._max_temp = max_temp
  127.         self._target_temperature = target_temp
  128.         self._target_temperature_step = 1
  129.         self._unit_of_measurement = hass.config.units.temperature_unit
  130.        
  131.         self._current_temperature = 0
  132.         self._temp_sensor_entity_id = temp_sensor_entity_id
  133.  
  134.         self._current_operation = default_operation
  135.         self._current_fan_mode = default_fan_mode
  136.        
  137.         self._operation_list = operation_list
  138.         self._fan_list = fan_list
  139.        
  140.         self._default_operation_from_idle = default_operation_from_idle
  141.                
  142.         self._broadlink_device = broadlink_device
  143.         self._commands_ini = ircodes_ini
  144.        
  145.         if temp_sensor_entity_id:
  146.             async_track_state_change(
  147.                 hass, temp_sensor_entity_id, self._async_temp_sensor_changed)
  148.                
  149.             sensor_state = hass.states.get(temp_sensor_entity_id)    
  150.                
  151.             if sensor_state:
  152.                 self._async_update_current_temp(sensor_state)
  153.    
  154.    
  155.     def send_ir(self):    
  156.         section = self._current_operation.lower()
  157.        
  158.         if section == 'off':
  159.             value = 'off_command'
  160.         elif section == 'idle':
  161.             value = 'idle_command'
  162.         else:
  163.             value = self._current_fan_mode.lower() + "_" + str(int(self._target_temperature)) if not section == 'off' else 'off_command'
  164.        
  165.         command = self._commands_ini.get(section, value)
  166.        
  167.         for retry in range(DEFAULT_RETRY):
  168.             try:
  169.                 payload = b64decode(command)
  170.                 self._broadlink_device.send_data(payload)
  171.                 break
  172.             except (socket.timeout, ValueError):
  173.                 try:
  174.                     self._broadlink_device.auth()
  175.                 except socket.timeout:
  176.                     if retry == DEFAULT_RETRY-1:
  177.                         _LOGGER.error("Failed to send packet to Broadlink RM Device")
  178.        
  179.    
  180.     @asyncio.coroutine
  181.     def _async_temp_sensor_changed(self, entity_id, old_state, new_state):
  182.         """Handle temperature changes."""
  183.         if new_state is None:
  184.             return
  185.  
  186.         self._async_update_current_temp(new_state)
  187.         yield from self.async_update_ha_state()
  188.        
  189.     @callback
  190.     def _async_update_current_temp(self, state):
  191.         """Update thermostat with latest state from sensor."""
  192.         unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
  193.  
  194.         try:
  195.             _state = state.state
  196.             if self.represents_float(_state):
  197.                 self._current_temperature = self.hass.config.units.temperature(
  198.                     float(_state), unit)
  199.         except ValueError as ex:
  200.             _LOGGER.error('Unable to update from sensor: %s', ex)    
  201.  
  202.     def represents_float(self, s):
  203.         try:
  204.             float(s)
  205.             return True
  206.         except ValueError:
  207.             return False    
  208.  
  209.    
  210.     @property
  211.     def should_poll(self):
  212.         """Return the polling state."""
  213.         return False
  214.  
  215.     @property
  216.     def name(self):
  217.         """Return the name of the climate device."""
  218.         return self._name
  219.  
  220.     @property
  221.     def temperature_unit(self):
  222.         """Return the unit of measurement."""
  223.         return self._unit_of_measurement
  224.  
  225.     @property
  226.     def current_temperature(self):
  227.         """Return the current temperature."""
  228.         return self._current_temperature
  229.        
  230.     @property
  231.     def min_temp(self):
  232.         """Return the polling state."""
  233.         return self._min_temp
  234.        
  235.     @property
  236.     def max_temp(self):
  237.         """Return the polling state."""
  238.         return self._max_temp    
  239.        
  240.     @property
  241.     def target_temperature(self):
  242.         """Return the temperature we try to reach."""
  243.         return self._target_temperature
  244.        
  245.     @property
  246.     def target_temperature_step(self):
  247.         """Return the supported step of target temperature."""
  248.         return self._target_temperature_step
  249.  
  250.     @property
  251.     def current_operation(self):
  252.         """Return current operation ie. heat, cool, idle."""
  253.         return self._current_operation
  254.  
  255.     @property
  256.     def operation_list(self):
  257.         """Return the list of available operation modes."""
  258.         return self._operation_list
  259.  
  260.     @property
  261.     def current_fan_mode(self):
  262.         """Return the fan setting."""
  263.         return self._current_fan_mode
  264.  
  265.     @property
  266.     def fan_list(self):
  267.         """Return the list of available fan modes."""
  268.         return self._fan_list
  269.        
  270.     @property
  271.     def supported_features(self):
  272.         """Return the list of supported features."""
  273.         return SUPPORT_FLAGS        
  274.  
  275.     def set_temperature(self, **kwargs):
  276.         """Set new target temperatures."""
  277.         if kwargs.get(ATTR_TEMPERATURE) is not None:
  278.             self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
  279.            
  280.             if not (self._current_operation.lower() == 'off' or self._current_operation.lower() == 'idle'):
  281.                 self.send_ir()
  282.             elif self._default_operation_from_idle is not None:
  283.                 self.set_operation_mode(self._default_operation_from_idle)
  284.                
  285.                    
  286.             self.schedule_update_ha_state()
  287.  
  288.     def set_fan_mode(self, fan):
  289.         """Set new target temperature."""
  290.         self._current_fan_mode = fan
  291.        
  292.         if not (self._current_operation.lower() == 'off' or self._current_operation.lower() == 'idle'):
  293.             self.send_ir()
  294.            
  295.         self.schedule_update_ha_state()
  296.  
  297.     def set_operation_mode(self, operation_mode):
  298.         """Set new target temperature."""
  299.         self._current_operation = operation_mode
  300.  
  301.         self.send_ir()
  302.         self.schedule_update_ha_state()
  303.        
  304.     @asyncio.coroutine
  305.     def async_added_to_hass(self):
  306.         state = yield from async_get_last_state(self.hass, self.entity_id)
  307.        
  308.         if state is not None:
  309.             self._target_temperature = state.attributes['temperature']
  310.             self._current_operation = state.attributes['operation_mode']
  311.             self._current_fan_mode = state.attributes['fan_mode']
Add Comment
Please, Sign In to add comment