Advertisement
Guest User

climate:mqtt.py

a guest
Nov 5th, 2018
667
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 36.58 KB | None | 0 0
  1. """
  2. Support for MQTT climate devices.
  3.  
  4. For more details about this platform, please refer to the documentation
  5. https://home-assistant.io/components/climate.mqtt/
  6. """
  7. import logging
  8.  
  9. import voluptuous as vol
  10.  
  11. from homeassistant.core import callback
  12. from homeassistant.components import mqtt, climate
  13.  
  14. from homeassistant.components.climate import (
  15.     STATE_HEAT, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, ClimateDevice,
  16.     PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, STATE_AUTO,
  17.     ATTR_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
  18.     SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE,
  19.     SUPPORT_AUX_HEAT, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP,
  20.     SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
  21.     ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW)
  22. from homeassistant.const import (
  23.     STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE)
  24. from homeassistant.components.mqtt import (
  25.     ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN,
  26.     CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
  27.     MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability, MqttDiscoveryUpdate)
  28. from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
  29. import homeassistant.helpers.config_validation as cv
  30. from homeassistant.helpers.dispatcher import async_dispatcher_connect
  31. from homeassistant.helpers.typing import HomeAssistantType, ConfigType
  32. from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM,
  33.                                           SPEED_HIGH)
  34.  
  35. _LOGGER = logging.getLogger(__name__)
  36.  
  37. DEPENDENCIES = ['mqtt']
  38.  
  39. DEFAULT_NAME = 'MQTT HVAC'
  40.  
  41. CONF_POWER_COMMAND_TOPIC = 'power_command_topic'
  42. CONF_POWER_STATE_TOPIC = 'power_state_topic'
  43. CONF_POWER_STATE_TEMPLATE = 'power_state_template'
  44. CONF_MODE_COMMAND_TOPIC = 'mode_command_topic'
  45. CONF_MODE_COMMAND_TEMPLATE = 'mode_command_template'
  46. CONF_MODE_STATE_TOPIC = 'mode_state_topic'
  47. CONF_MODE_STATE_TEMPLATE = 'mode_state_template'
  48. CONF_TEMPERATURE_COMMAND_TOPIC = 'temperature_command_topic'
  49. CONF_TEMPERATURE_COMMAND_TEMPLATE = 'temperature_command_template'
  50. CONF_TEMPERATURE_STATE_TOPIC = 'temperature_state_topic'
  51. CONF_TEMPERATURE_STATE_TEMPLATE = 'temperature_state_template'
  52. CONF_TEMPERATURE_LOW_COMMAND_TOPIC = 'temperature_low_command_topic'
  53. CONF_TEMPERATURE_LOW_COMMAND_TEMPLATE = 'temperature_low_command_template'
  54. CONF_TEMPERATURE_LOW_STATE_TOPIC = 'temperature_low_state_topic'
  55. CONF_TEMPERATURE_LOW_STATE_TEMPLATE = 'temperature_low_state_template'
  56. CONF_TEMPERATURE_HIGH_COMMAND_TOPIC = 'temperature_high_command_topic'
  57. CONF_TEMPERATURE_HIGH_COMMAND_TEMPLATE = 'temperature_high_command_template'
  58. CONF_TEMPERATURE_HIGH_STATE_TOPIC = 'temperature_high_state_topic'
  59. CONF_TEMPERATURE_HIGH_STATE_TEMPLATE = 'temperature_high_state_template'
  60. CONF_FAN_MODE_COMMAND_TOPIC = 'fan_mode_command_topic'
  61. CONF_FAN_MODE_COMMAND_TEMPLATE = 'fan_mode_command_template'
  62. CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic'
  63. CONF_FAN_MODE_STATE_TEMPLATE = 'fan_mode_state_template'
  64. CONF_SWING_MODE_COMMAND_TOPIC = 'swing_mode_command_topic'
  65. CONF_SWING_MODE_COMMAND_TEMPLATE = 'swing_mode_command_template'
  66. CONF_SWING_MODE_STATE_TOPIC = 'swing_mode_state_topic'
  67. CONF_SWING_MODE_STATE_TEMPLATE = 'swing_mode_state_template'
  68. CONF_AWAY_MODE_COMMAND_TOPIC = 'away_mode_command_topic'
  69. CONF_AWAY_MODE_STATE_TOPIC = 'away_mode_state_topic'
  70. CONF_AWAY_MODE_STATE_TEMPLATE = 'away_mode_state_template'
  71. CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic'
  72. CONF_HOLD_COMMAND_TEMPLATE = 'hold_command_template'
  73. CONF_HOLD_STATE_TOPIC = 'hold_state_topic'
  74. CONF_HOLD_STATE_TEMPLATE = 'hold_state_template'
  75. CONF_AUX_COMMAND_TOPIC = 'aux_command_topic'
  76. CONF_AUX_STATE_TOPIC = 'aux_state_topic'
  77. CONF_AUX_STATE_TEMPLATE = 'aux_state_template'
  78.  
  79. CONF_CURRENT_TEMPERATURE_TEMPLATE = 'current_temperature_template'
  80. CONF_CURRENT_TEMPERATURE_TOPIC = 'current_temperature_topic'
  81.  
  82. CONF_PAYLOAD_ON = 'payload_on'
  83. CONF_PAYLOAD_OFF = 'payload_off'
  84.  
  85. CONF_FAN_MODE_LIST = 'fan_modes'
  86. CONF_MODE_LIST = 'modes'
  87. CONF_SWING_MODE_LIST = 'swing_modes'
  88. CONF_INITIAL = 'initial'
  89. CONF_SEND_IF_OFF = 'send_if_off'
  90.  
  91. CONF_MIN_TEMP = 'min_temp'
  92. CONF_MAX_TEMP = 'max_temp'
  93. CONF_TEMP_STEP = 'temp_step'
  94.  
  95. SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema)
  96. PLATFORM_SCHEMA = SCHEMA_BASE.extend({
  97.     vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic,
  98.     vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
  99.     vol.Optional(CONF_TEMPERATURE_COMMAND_TOPIC): mqtt.valid_publish_topic,
  100.     vol.Optional(CONF_TEMPERATURE_LOW_COMMAND_TOPIC):
  101.         mqtt.valid_publish_topic,
  102.     vol.Optional(CONF_TEMPERATURE_HIGH_COMMAND_TOPIC):
  103.         mqtt.valid_publish_topic,
  104.     vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
  105.     vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
  106.     vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
  107.     vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic,
  108.     vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic,
  109.  
  110.     vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic,
  111.     vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
  112.     vol.Optional(CONF_TEMPERATURE_STATE_TOPIC): mqtt.valid_subscribe_topic,
  113.     vol.Optional(CONF_TEMPERATURE_LOW_STATE_TOPIC):
  114.         mqtt.valid_subscribe_topic,
  115.     vol.Optional(CONF_TEMPERATURE_HIGH_STATE_TOPIC):
  116.         mqtt.valid_subscribe_topic,
  117.     vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
  118.     vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
  119.     vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
  120.     vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
  121.     vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic,
  122.  
  123.     vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
  124.     vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template,
  125.     vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
  126.     vol.Optional(CONF_TEMPERATURE_STATE_TEMPLATE): cv.template,
  127.     vol.Optional(CONF_TEMPERATURE_LOW_STATE_TEMPLATE): cv.template,
  128.     vol.Optional(CONF_TEMPERATURE_HIGH_STATE_TEMPLATE): cv.template,
  129.     vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template,
  130.     vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template,
  131.     vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template,
  132.     vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
  133.     vol.Optional(CONF_AUX_STATE_TEMPLATE): cv.template,
  134.     vol.Optional(CONF_CURRENT_TEMPERATURE_TEMPLATE): cv.template,
  135.  
  136.     vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template,
  137.     vol.Optional(CONF_TEMPERATURE_COMMAND_TEMPLATE): cv.template,
  138.     vol.Optional(CONF_TEMPERATURE_LOW_COMMAND_TEMPLATE): cv.template,
  139.     vol.Optional(CONF_TEMPERATURE_HIGH_COMMAND_TEMPLATE): cv.template,
  140.     vol.Optional(CONF_FAN_MODE_COMMAND_TEMPLATE): cv.template,
  141.     vol.Optional(CONF_SWING_MODE_COMMAND_TEMPLATE): cv.template,
  142.     vol.Optional(CONF_HOLD_COMMAND_TEMPLATE): cv.template,
  143.  
  144.     vol.Optional(CONF_CURRENT_TEMPERATURE_TOPIC):
  145.         mqtt.valid_subscribe_topic,
  146.     vol.Optional(CONF_FAN_MODE_LIST,
  147.                  default=[STATE_AUTO, SPEED_LOW,
  148.                           SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
  149.     vol.Optional(CONF_SWING_MODE_LIST,
  150.                  default=[STATE_ON, STATE_OFF]): cv.ensure_list,
  151.     vol.Optional(CONF_MODE_LIST,
  152.                  default=[STATE_AUTO, STATE_OFF, STATE_COOL, STATE_HEAT,
  153.                           STATE_DRY, STATE_FAN_ONLY]): cv.ensure_list,
  154.     vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
  155.     vol.Optional(CONF_INITIAL, default=21): cv.positive_int,
  156.     vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
  157.     vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
  158.     vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
  159.  
  160.     vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
  161.     vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float),
  162.     vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float)
  163.  
  164. }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
  165.  
  166.  
  167. async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
  168.                                async_add_entities, discovery_info=None):
  169.     """Set up MQTT climate device through configuration.yaml."""
  170.     await _async_setup_entity(hass, config, async_add_entities)
  171.  
  172.  
  173. async def async_setup_entry(hass, config_entry, async_add_entities):
  174.     """Set up MQTT climate device dynamically through MQTT discovery."""
  175.     async def async_discover(discovery_payload):
  176.         """Discover and add a MQTT climate device."""
  177.         config = PLATFORM_SCHEMA(discovery_payload)
  178.         await _async_setup_entity(hass, config, async_add_entities,
  179.                                   discovery_payload[ATTR_DISCOVERY_HASH])
  180.  
  181.     async_dispatcher_connect(
  182.         hass, MQTT_DISCOVERY_NEW.format(climate.DOMAIN, 'mqtt'),
  183.         async_discover)
  184.  
  185.  
  186. async def _async_setup_entity(hass, config, async_add_entities,
  187.                               discovery_hash=None):
  188.     """Set up the MQTT climate devices."""
  189.     template_keys = (
  190.         CONF_POWER_STATE_TEMPLATE,
  191.         CONF_MODE_STATE_TEMPLATE,
  192.         CONF_TEMPERATURE_STATE_TEMPLATE,
  193.         CONF_TEMPERATURE_LOW_STATE_TEMPLATE,
  194.         CONF_TEMPERATURE_HIGH_STATE_TEMPLATE,
  195.         CONF_FAN_MODE_STATE_TEMPLATE,
  196.         CONF_SWING_MODE_STATE_TEMPLATE,
  197.         CONF_AWAY_MODE_STATE_TEMPLATE,
  198.         CONF_HOLD_STATE_TEMPLATE,
  199.         CONF_AUX_STATE_TEMPLATE,
  200.         CONF_MODE_COMMAND_TEMPLATE,
  201.         CONF_TEMPERATURE_COMMAND_TEMPLATE,
  202.         CONF_TEMPERATURE_LOW_COMMAND_TEMPLATE,
  203.         CONF_TEMPERATURE_HIGH_COMMAND_TEMPLATE,
  204.         CONF_FAN_MODE_COMMAND_TEMPLATE,
  205.         CONF_SWING_MODE_COMMAND_TEMPLATE,
  206.         CONF_HOLD_COMMAND_TEMPLATE,
  207.         CONF_CURRENT_TEMPERATURE_TEMPLATE
  208.     )
  209.     value_templates = {}
  210.     if CONF_VALUE_TEMPLATE in config:
  211.         value_template = config.get(CONF_VALUE_TEMPLATE)
  212.         value_template.hass = hass
  213.         value_templates = {key: value_template for key in template_keys}
  214.     for key in template_keys & config.keys():
  215.         value_templates[key] = config.get(key)
  216.         value_templates[key].hass = hass
  217.  
  218.     async_add_entities([
  219.         MqttClimate(
  220.             hass,
  221.             config.get(CONF_NAME),
  222.             {
  223.                 key: config.get(key) for key in (
  224.                     CONF_POWER_COMMAND_TOPIC,
  225.                     CONF_MODE_COMMAND_TOPIC,
  226.                     CONF_TEMPERATURE_COMMAND_TOPIC,
  227.                     CONF_TEMPERATURE_LOW_COMMAND_TOPIC,
  228.                     CONF_TEMPERATURE_HIGH_COMMAND_TOPIC,
  229.                     CONF_FAN_MODE_COMMAND_TOPIC,
  230.                     CONF_SWING_MODE_COMMAND_TOPIC,
  231.                     CONF_AWAY_MODE_COMMAND_TOPIC,
  232.                     CONF_HOLD_COMMAND_TOPIC,
  233.                     CONF_AUX_COMMAND_TOPIC,
  234.                     CONF_POWER_STATE_TOPIC,
  235.                     CONF_MODE_STATE_TOPIC,
  236.                     CONF_TEMPERATURE_STATE_TOPIC,
  237.                     CONF_TEMPERATURE_LOW_STATE_TOPIC,
  238.                     CONF_TEMPERATURE_HIGH_STATE_TOPIC,
  239.                     CONF_FAN_MODE_STATE_TOPIC,
  240.                     CONF_SWING_MODE_STATE_TOPIC,
  241.                     CONF_AWAY_MODE_STATE_TOPIC,
  242.                     CONF_HOLD_STATE_TOPIC,
  243.                     CONF_AUX_STATE_TOPIC,
  244.                     CONF_CURRENT_TEMPERATURE_TOPIC
  245.                 )
  246.             },
  247.             value_templates,
  248.             config.get(CONF_QOS),
  249.             config.get(CONF_RETAIN),
  250.             config.get(CONF_MODE_LIST),
  251.             config.get(CONF_FAN_MODE_LIST),
  252.             config.get(CONF_SWING_MODE_LIST),
  253.             config.get(CONF_INITIAL),
  254.             config.get(CONF_INITIAL),
  255.             config.get(CONF_INITIAL),
  256.             False, None, SPEED_LOW,
  257.             STATE_OFF, STATE_OFF, False,
  258.             config.get(CONF_SEND_IF_OFF),
  259.             config.get(CONF_PAYLOAD_ON),
  260.             config.get(CONF_PAYLOAD_OFF),
  261.             config.get(CONF_AVAILABILITY_TOPIC),
  262.             config.get(CONF_PAYLOAD_AVAILABLE),
  263.             config.get(CONF_PAYLOAD_NOT_AVAILABLE),
  264.             config.get(CONF_MIN_TEMP),
  265.             config.get(CONF_MAX_TEMP),
  266.             config.get(CONF_TEMP_STEP),
  267.             discovery_hash,
  268.         )])
  269.  
  270.  
  271. class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
  272.     """Representation of an MQTT climate device."""
  273.  
  274.     def __init__(self, hass, name, topic, value_templates, qos, retain,
  275.                  mode_list, fan_mode_list, swing_mode_list,
  276.                  target_temperature, target_temperature_low,
  277.                  target_temperature_high, away, hold, current_fan_mode,
  278.                  current_swing_mode, current_operation, aux, send_if_off,
  279.                  payload_on, payload_off, availability_topic,
  280.                  payload_available, payload_not_available,
  281.                  min_temp, max_temp, temp_step, discovery_hash):
  282.         """Initialize the climate device."""
  283.         MqttAvailability.__init__(self, availability_topic, qos,
  284.                                   payload_available, payload_not_available)
  285.         MqttDiscoveryUpdate.__init__(self, discovery_hash)
  286.         self.hass = hass
  287.         self._name = name
  288.         self._topic = topic
  289.         self._value_templates = value_templates
  290.         self._qos = qos
  291.         self._retain = retain
  292.         # set to None in non-optimistic mode
  293.         self._target_temperature = self._current_fan_mode = \
  294.             self._current_operation = self._current_swing_mode = \
  295.             self._target_temperature_low = self._target_temperature_high = None
  296.         if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is None:
  297.             self._target_temperature = target_temperature
  298.         self._unit_of_measurement = hass.config.units.temperature_unit
  299.         self._away = away
  300.         self._hold = hold
  301.         self._current_temperature = None
  302.         if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
  303.             self._current_fan_mode = current_fan_mode
  304.         if self._topic[CONF_MODE_STATE_TOPIC] is None:
  305.             self._current_operation = current_operation
  306.         self._aux = aux
  307.         if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None:
  308.             self._current_swing_mode = current_swing_mode
  309.         self._fan_list = fan_mode_list
  310.         self._operation_list = mode_list
  311.         self._swing_list = swing_mode_list
  312.         self._target_temperature_step = temp_step
  313.         self._send_if_off = send_if_off
  314.         self._payload_on = payload_on
  315.         self._payload_off = payload_off
  316.         self._min_temp = min_temp
  317.         self._max_temp = max_temp
  318.         if self._topic[CONF_TEMPERATURE_LOW_STATE_TOPIC] is None:
  319.             self._target_temperature_low = target_temperature_low
  320.         if self._topic[CONF_TEMPERATURE_HIGH_STATE_TOPIC] is None:
  321.             self._target_temperature_high = target_temperature_high
  322.         self._discovery_hash = discovery_hash
  323.  
  324.     async def async_added_to_hass(self):
  325.         """Handle being added to home assistant."""
  326.         await MqttAvailability.async_added_to_hass(self)
  327.         await MqttDiscoveryUpdate.async_added_to_hass(self)
  328.  
  329.         @callback
  330.         def handle_current_temp_received(topic, payload, qos):
  331.             """Handle current temperature coming via MQTT."""
  332.             if CONF_CURRENT_TEMPERATURE_TEMPLATE in self._value_templates:
  333.                 payload =\
  334.                   self._value_templates[CONF_CURRENT_TEMPERATURE_TEMPLATE].\
  335.                   async_render_with_possible_json_value(payload)
  336.  
  337.             try:
  338.                 self._current_temperature = float(payload)
  339.                 self.async_schedule_update_ha_state()
  340.             except ValueError:
  341.                 _LOGGER.error("Could not parse temperature from %s", payload)
  342.  
  343.         if self._topic[CONF_CURRENT_TEMPERATURE_TOPIC] is not None:
  344.             await mqtt.async_subscribe(
  345.                 self.hass, self._topic[CONF_CURRENT_TEMPERATURE_TOPIC],
  346.                 handle_current_temp_received, self._qos)
  347.  
  348.         @callback
  349.         def handle_mode_received(topic, payload, qos):
  350.             """Handle receiving mode via MQTT."""
  351.             if CONF_MODE_STATE_TEMPLATE in self._value_templates:
  352.                 payload = self._value_templates[CONF_MODE_STATE_TEMPLATE].\
  353.                   async_render_with_possible_json_value(payload)
  354.  
  355.             if payload not in self._operation_list:
  356.                 _LOGGER.error("Invalid mode: %s", payload)
  357.             else:
  358.                 self._current_operation = payload
  359.                 self.async_schedule_update_ha_state()
  360.  
  361.         if self._topic[CONF_MODE_STATE_TOPIC] is not None:
  362.             await mqtt.async_subscribe(
  363.                 self.hass, self._topic[CONF_MODE_STATE_TOPIC],
  364.                 handle_mode_received, self._qos)
  365.  
  366.         @callback
  367.         def handle_temperature_received(topic, payload, qos):
  368.             """Handle target temperature coming via MQTT."""
  369.             if CONF_TEMPERATURE_STATE_TEMPLATE in self._value_templates:
  370.                 payload = \
  371.                   self._value_templates[CONF_TEMPERATURE_STATE_TEMPLATE].\
  372.                   async_render_with_possible_json_value(payload)
  373.  
  374.             try:
  375.                 self._target_temperature = float(payload)
  376.                 self.async_schedule_update_ha_state()
  377.             except ValueError:
  378.                 _LOGGER.error("Could not parse temperature from %s", payload)
  379.  
  380.         if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None:
  381.             await mqtt.async_subscribe(
  382.                 self.hass, self._topic[CONF_TEMPERATURE_STATE_TOPIC],
  383.                 handle_temperature_received, self._qos)
  384.  
  385.         @callback
  386.         def handle_temperature_low_received(topic, payload, qos):
  387.             """Handle target temperature low coming via MQTT."""
  388.             if CONF_TEMPERATURE_LOW_STATE_TEMPLATE in self._value_templates:
  389.                 payload = \
  390.                   self._value_templates[CONF_TEMPERATURE_LOW_STATE_TEMPLATE].\
  391.                   async_render_with_possible_json_value(payload)
  392.  
  393.             try:
  394.                 self._target_temperature_low = float(payload)
  395.                 self.async_schedule_update_ha_state()
  396.             except ValueError:
  397.                 _LOGGER.error("Could not parse temperature from %s", payload)
  398.  
  399.         if self._topic[CONF_TEMPERATURE_LOW_STATE_TOPIC] is not None:
  400.             await mqtt.async_subscribe(
  401.                 self.hass, self._topic[CONF_TEMPERATURE_LOW_STATE_TOPIC],
  402.                 handle_temperature_low_received, self._qos)
  403.  
  404.         @callback
  405.         def handle_temperature_high_received(topic, payload, qos):
  406.             """Handle target temperature high coming via MQTT."""
  407.             if CONF_TEMPERATURE_HIGH_STATE_TEMPLATE in self._value_templates:
  408.                 payload = \
  409.                   self._value_templates[CONF_TEMPERATURE_HIGH_STATE_TEMPLATE].\
  410.                   async_render_with_possible_json_value(payload)
  411.  
  412.             try:
  413.                 self._target_temperature_high = float(payload)
  414.                 self.async_schedule_update_ha_state()
  415.             except ValueError:
  416.                 _LOGGER.error("Could not parse temperature from %s", payload)
  417.  
  418.         if self._topic[CONF_TEMPERATURE_HIGH_STATE_TOPIC] is not None:
  419.             await mqtt.async_subscribe(
  420.                 self.hass, self._topic[CONF_TEMPERATURE_HIGH_STATE_TOPIC],
  421.                 handle_temperature_high_received, self._qos)
  422.  
  423.         @callback
  424.         def handle_fan_mode_received(topic, payload, qos):
  425.             """Handle receiving fan mode via MQTT."""
  426.             if CONF_FAN_MODE_STATE_TEMPLATE in self._value_templates:
  427.                 payload = \
  428.                   self._value_templates[CONF_FAN_MODE_STATE_TEMPLATE].\
  429.                   async_render_with_possible_json_value(payload)
  430.  
  431.             if payload not in self._fan_list:
  432.                 _LOGGER.error("Invalid fan mode: %s", payload)
  433.             else:
  434.                 self._current_fan_mode = payload
  435.                 self.async_schedule_update_ha_state()
  436.  
  437.         if self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None:
  438.             await mqtt.async_subscribe(
  439.                 self.hass, self._topic[CONF_FAN_MODE_STATE_TOPIC],
  440.                 handle_fan_mode_received, self._qos)
  441.  
  442.         @callback
  443.         def handle_swing_mode_received(topic, payload, qos):
  444.             """Handle receiving swing mode via MQTT."""
  445.             if CONF_SWING_MODE_STATE_TEMPLATE in self._value_templates:
  446.                 payload = \
  447.                   self._value_templates[CONF_SWING_MODE_STATE_TEMPLATE].\
  448.                   async_render_with_possible_json_value(payload)
  449.  
  450.             if payload not in self._swing_list:
  451.                 _LOGGER.error("Invalid swing mode: %s", payload)
  452.             else:
  453.                 self._current_swing_mode = payload
  454.                 self.async_schedule_update_ha_state()
  455.  
  456.         if self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None:
  457.             await mqtt.async_subscribe(
  458.                 self.hass, self._topic[CONF_SWING_MODE_STATE_TOPIC],
  459.                 handle_swing_mode_received, self._qos)
  460.  
  461.         @callback
  462.         def handle_away_mode_received(topic, payload, qos):
  463.             """Handle receiving away mode via MQTT."""
  464.             if CONF_AWAY_MODE_STATE_TEMPLATE in self._value_templates:
  465.                 payload = \
  466.                   self._value_templates[CONF_AWAY_MODE_STATE_TEMPLATE].\
  467.                   async_render_with_possible_json_value(payload)
  468.                 if payload == "True":
  469.                     payload = self._payload_on
  470.                 elif payload == "False":
  471.                     payload = self._payload_off
  472.  
  473.             if payload == self._payload_on:
  474.                 self._away = True
  475.             elif payload == self._payload_off:
  476.                 self._away = False
  477.             else:
  478.                 _LOGGER.error("Invalid away mode: %s", payload)
  479.  
  480.             self.async_schedule_update_ha_state()
  481.  
  482.         if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None:
  483.             await mqtt.async_subscribe(
  484.                 self.hass, self._topic[CONF_AWAY_MODE_STATE_TOPIC],
  485.                 handle_away_mode_received, self._qos)
  486.  
  487.         @callback
  488.         def handle_aux_mode_received(topic, payload, qos):
  489.             """Handle receiving aux mode via MQTT."""
  490.             if CONF_AUX_STATE_TEMPLATE in self._value_templates:
  491.                 payload = self._value_templates[CONF_AUX_STATE_TEMPLATE].\
  492.                   async_render_with_possible_json_value(payload)
  493.                 if payload == "True":
  494.                     payload = self._payload_on
  495.                 elif payload == "False":
  496.                     payload = self._payload_off
  497.  
  498.             if payload == self._payload_on:
  499.                 self._aux = True
  500.             elif payload == self._payload_off:
  501.                 self._aux = False
  502.             else:
  503.                 _LOGGER.error("Invalid aux mode: %s", payload)
  504.  
  505.             self.async_schedule_update_ha_state()
  506.  
  507.         if self._topic[CONF_AUX_STATE_TOPIC] is not None:
  508.             await mqtt.async_subscribe(
  509.                 self.hass, self._topic[CONF_AUX_STATE_TOPIC],
  510.                 handle_aux_mode_received, self._qos)
  511.  
  512.         @callback
  513.         def handle_hold_mode_received(topic, payload, qos):
  514.             """Handle receiving hold mode via MQTT."""
  515.             if CONF_HOLD_STATE_TEMPLATE in self._value_templates:
  516.                 payload = self._value_templates[CONF_HOLD_STATE_TEMPLATE].\
  517.                   async_render_with_possible_json_value(payload)
  518.  
  519.             self._hold = payload
  520.             self.async_schedule_update_ha_state()
  521.  
  522.         if self._topic[CONF_HOLD_STATE_TOPIC] is not None:
  523.             await mqtt.async_subscribe(
  524.                 self.hass, self._topic[CONF_HOLD_STATE_TOPIC],
  525.                 handle_hold_mode_received, self._qos)
  526.  
  527.     @property
  528.     def should_poll(self):
  529.         """Return the polling state."""
  530.         return False
  531.  
  532.     @property
  533.     def name(self):
  534.         """Return the name of the climate device."""
  535.         return self._name
  536.  
  537.     @property
  538.     def temperature_unit(self):
  539.         """Return the unit of measurement."""
  540.         return self._unit_of_measurement
  541.  
  542.     @property
  543.     def current_temperature(self):
  544.         """Return the current temperature."""
  545.         return self._current_temperature
  546.  
  547.     @property
  548.     def target_temperature(self):
  549.         """Return the temperature we try to reach."""
  550.         return self._target_temperature
  551.  
  552.     @property
  553.     def target_temperature_low(self):
  554.         """Return the temperature we try to reach."""
  555.         return self._target_temperature_low
  556.  
  557.     @property
  558.     def target_temperature_high(self):
  559.         """Return the temperature we try to reach."""
  560.         return self._target_temperature_high
  561.  
  562.     @property
  563.     def current_operation(self):
  564.         """Return current operation ie. heat, cool, idle."""
  565.         return self._current_operation
  566.  
  567.     @property
  568.     def operation_list(self):
  569.         """Return the list of available operation modes."""
  570.         return self._operation_list
  571.  
  572.     @property
  573.     def target_temperature_step(self):
  574.         """Return the supported step of target temperature."""
  575.         return self._target_temperature_step
  576.  
  577.     @property
  578.     def is_away_mode_on(self):
  579.         """Return if away mode is on."""
  580.         return self._away
  581.  
  582.     @property
  583.     def current_hold_mode(self):
  584.         """Return hold mode setting."""
  585.         return self._hold
  586.  
  587.     @property
  588.     def is_aux_heat_on(self):
  589.         """Return true if away mode is on."""
  590.         return self._aux
  591.  
  592.     @property
  593.     def current_fan_mode(self):
  594.         """Return the fan setting."""
  595.         return self._current_fan_mode
  596.  
  597.     @property
  598.     def fan_list(self):
  599.         """Return the list of available fan modes."""
  600.         return self._fan_list
  601.  
  602.     async def async_set_temperature(self, **kwargs):
  603.         """Set new target temperatures."""
  604.         if kwargs.get(ATTR_OPERATION_MODE) is not None:
  605.             operation_mode = kwargs.get(ATTR_OPERATION_MODE)
  606.             await self.async_set_operation_mode(operation_mode)
  607.  
  608.         if kwargs.get(ATTR_TEMPERATURE) is not None:
  609.             if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is None:
  610.                 # optimistic mode
  611.                 self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
  612.  
  613.             if self._send_if_off or self._current_operation != STATE_OFF:
  614.                 if CONF_TEMPERATURE_COMMAND_TEMPLATE in self._value_templates:
  615.                     temperature_command = self._value_templates[CONF_TEMPERATURE_COMMAND_TEMPLATE].\
  616.                       async_render_with_possible_json_value(str(kwargs.get(ATTR_TEMPERATURE)))
  617.                 _LOGGER.info("temperature_command: %s", temperature_command)
  618.                 mqtt.async_publish(
  619.                     self.hass, self._topic[CONF_TEMPERATURE_COMMAND_TOPIC],
  620.                     temperature_command, self._qos, self._retain)
  621.  
  622.         if kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None and \
  623.            kwargs.get(ATTR_TARGET_TEMP_LOW) is not None:
  624.             if self._topic[CONF_TEMPERATURE_LOW_STATE_TOPIC] is None:
  625.                 # optimistic mode
  626.                 self._target_temperature_low = \
  627.                     kwargs.get(ATTR_TARGET_TEMP_LOW)
  628.  
  629.             if self._topic[CONF_TEMPERATURE_HIGH_STATE_TOPIC] is None:
  630.                 # optimistic mode
  631.                 self._target_temperature_high = \
  632.                     kwargs.get(ATTR_TARGET_TEMP_HIGH)
  633.  
  634.             if self._send_if_off or self._current_operation != STATE_OFF:
  635.                 if CONF_TEMPERATURE_LOW_COMMAND_TEMPLATE in self._value_templates:
  636.                     temperature_low_command = self._value_templates[CONF_TEMPERATURE_LOW_COMMAND_TEMPLATE].\
  637.                       async_render_with_possible_json_value(str(kwargs.get(ATTR_TARGET_TEMP_LOW)))
  638.                 _LOGGER.info("temperature_low_command: %s", temperature_low_command)
  639.                 if CONF_TEMPERATURE_HIGH_COMMAND_TEMPLATE in self._value_templates:
  640.                     temperature_high_command = self._value_templates[CONF_TEMPERATURE_HIGH_COMMAND_TEMPLATE].\
  641.                       async_render_with_possible_json_value(str(kwargs.get(ATTR_TARGET_TEMP_HIGH)))
  642.                 _LOGGER.info("temperature_high_command: %s", temperature_high_command)
  643.                 mqtt.async_publish(
  644.                     self.hass,
  645.                     self._topic[CONF_TEMPERATURE_LOW_COMMAND_TOPIC],
  646.                     temperature_low_command,
  647.                     self._qos, self._retain)
  648.                 mqtt.async_publish(
  649.                     self.hass,
  650.                     self._topic[CONF_TEMPERATURE_HIGH_COMMAND_TOPIC],
  651.                     temperature_high_command,
  652.                     self._qos, self._retain)
  653.  
  654.         self.async_schedule_update_ha_state()
  655.  
  656.     async def async_set_swing_mode(self, swing_mode):
  657.         """Set new swing mode."""
  658.         if self._send_if_off or self._current_operation != STATE_OFF:
  659.             if CONF_SWING_MODE_COMMAND_TEMPLATE in self._value_templates:
  660.                 swing__mode_command = self._value_templates[CONF_SWING_MODE_COMMAND_TEMPLATE].\
  661.                   async_render_with_possible_json_value(swing_mode)
  662.             _LOGGER.info("Swing_mode_command: %s", swing_mode_command)
  663.             mqtt.async_publish(
  664.                 self.hass, self._topic[CONF_SWING_MODE_COMMAND_TOPIC],
  665.                 swing_mode_command, self._qos, self._retain)
  666.  
  667.         if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None:
  668.             self._current_swing_mode = swing_mode
  669.             self.async_schedule_update_ha_state()
  670.  
  671.     async def async_set_fan_mode(self, fan_mode):
  672.         """Set new target temperature."""
  673.         if self._send_if_off or self._current_operation != STATE_OFF:
  674.             if CONF_FAN_MODE_COMMAND_TEMPLATE in self._value_templates:
  675.                 fan_mode_command = self._value_templates[CONF_FAN_MODE_COMMAND_TEMPLATE].\
  676.                   async_render_with_possible_json_value(fan_mode)
  677.             _LOGGER.info("Fan_mode_command: %s", fan_mode_command)
  678.             mqtt.async_publish(
  679.                 self.hass, self._topic[CONF_FAN_MODE_COMMAND_TOPIC],
  680.                 fan_mode_command, self._qos, self._retain)
  681.  
  682.         if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
  683.             self._current_fan_mode = fan_mode
  684.             self.async_schedule_update_ha_state()
  685.  
  686.     async def async_set_operation_mode(self, operation_mode) -> None:
  687.         """Set new operation mode."""
  688.         if self._topic[CONF_POWER_COMMAND_TOPIC] is not None:
  689.             if (self._current_operation == STATE_OFF and
  690.                     operation_mode != STATE_OFF):
  691.                 mqtt.async_publish(
  692.                     self.hass, self._topic[CONF_POWER_COMMAND_TOPIC],
  693.                     self._payload_on, self._qos, self._retain)
  694.             elif (self._current_operation != STATE_OFF and
  695.                   operation_mode == STATE_OFF):
  696.                 mqtt.async_publish(
  697.                     self.hass, self._topic[CONF_POWER_COMMAND_TOPIC],
  698.                     self._payload_off, self._qos, self._retain)
  699.  
  700.         if self._topic[CONF_MODE_COMMAND_TOPIC] is not None:
  701.             if CONF_MODE_COMMAND_TEMPLATE in self._value_templates:
  702.                 operation_mode_command = self._value_templates[CONF_MODE_COMMAND_TEMPLATE].\
  703.                   async_render_with_possible_json_value(operation_mode)
  704.             _LOGGER.info("Operation_mode_command: %s", operation_mode_command)
  705.             mqtt.async_publish(
  706.                 self.hass, self._topic[CONF_MODE_COMMAND_TOPIC],
  707.                 operation_mode_command, self._qos, self._retain)
  708.  
  709.         if self._topic[CONF_MODE_STATE_TOPIC] is None:
  710.             self._current_operation = operation_mode
  711.             self.async_schedule_update_ha_state()
  712.  
  713.     @property
  714.     def current_swing_mode(self):
  715.         """Return the swing setting."""
  716.         return self._current_swing_mode
  717.  
  718.     @property
  719.     def swing_list(self):
  720.         """List of available swing modes."""
  721.         return self._swing_list
  722.  
  723.     async def async_turn_away_mode_on(self):
  724.         """Turn away mode on."""
  725.         if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None:
  726.             mqtt.async_publish(self.hass,
  727.                                self._topic[CONF_AWAY_MODE_COMMAND_TOPIC],
  728.                                self._payload_on, self._qos, self._retain)
  729.  
  730.         if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
  731.             self._away = True
  732.             self.async_schedule_update_ha_state()
  733.  
  734.     async def async_turn_away_mode_off(self):
  735.         """Turn away mode off."""
  736.         if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None:
  737.             mqtt.async_publish(self.hass,
  738.                                self._topic[CONF_AWAY_MODE_COMMAND_TOPIC],
  739.                                self._payload_off, self._qos, self._retain)
  740.  
  741.         if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
  742.             self._away = False
  743.             self.async_schedule_update_ha_state()
  744.  
  745.     async def async_set_hold_mode(self, hold_mode):
  746.         """Update hold mode on."""
  747.         if self._topic[CONF_HOLD_COMMAND_TOPIC] is not None:
  748.             if CONF_HOLD_COMMAND_TEMPLATE in self._value_templates:
  749.                 hold_mode_command = self._value_templates[CONF_HOLD_COMMAND_TEMPLATE].\
  750.                   async_render_with_possible_json_value(hold_mode)
  751.             _LOGGER.info("Operation_mode_command: %s", hold_mode_command)
  752.             mqtt.async_publish(self.hass,
  753.                                self._topic[CONF_HOLD_COMMAND_TOPIC],
  754.                                hold_mode_command, self._qos, self._retain)
  755.  
  756.         if self._topic[CONF_HOLD_STATE_TOPIC] is None:
  757.             self._hold = hold_mode
  758.             self.async_schedule_update_ha_state()
  759.  
  760.     async def async_turn_aux_heat_on(self):
  761.         """Turn auxiliary heater on."""
  762.         if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
  763.             mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
  764.                                self._payload_on, self._qos, self._retain)
  765.  
  766.         if self._topic[CONF_AUX_STATE_TOPIC] is None:
  767.             self._aux = True
  768.             self.async_schedule_update_ha_state()
  769.  
  770.     async def async_turn_aux_heat_off(self):
  771.         """Turn auxiliary heater off."""
  772.         if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
  773.             mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
  774.                                self._payload_off, self._qos, self._retain)
  775.  
  776.         if self._topic[CONF_AUX_STATE_TOPIC] is None:
  777.             self._aux = False
  778.             self.async_schedule_update_ha_state()
  779.  
  780.     @property
  781.     def supported_features(self):
  782.         """Return the list of supported features."""
  783.         support = 0
  784.  
  785.         if (self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None) or \
  786.            (self._topic[CONF_TEMPERATURE_COMMAND_TOPIC] is not None):
  787.             support |= SUPPORT_TARGET_TEMPERATURE
  788.  
  789.         if (self._topic[CONF_TEMPERATURE_LOW_STATE_TOPIC] is not None) or \
  790.            (self._topic[CONF_TEMPERATURE_LOW_COMMAND_TOPIC] is not None):
  791.             support |= SUPPORT_TARGET_TEMPERATURE_LOW
  792.  
  793.         if (self._topic[CONF_TEMPERATURE_HIGH_STATE_TOPIC] is not None) or \
  794.            (self._topic[CONF_TEMPERATURE_HIGH_COMMAND_TOPIC] is not None):
  795.             support |= SUPPORT_TARGET_TEMPERATURE_HIGH
  796.  
  797.         if (self._topic[CONF_MODE_COMMAND_TOPIC] is not None) or \
  798.            (self._topic[CONF_MODE_STATE_TOPIC] is not None):
  799.             support |= SUPPORT_OPERATION_MODE
  800.  
  801.         if (self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None) or \
  802.            (self._topic[CONF_FAN_MODE_COMMAND_TOPIC] is not None):
  803.             support |= SUPPORT_FAN_MODE
  804.  
  805.         if (self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None) or \
  806.            (self._topic[CONF_SWING_MODE_COMMAND_TOPIC] is not None):
  807.             support |= SUPPORT_SWING_MODE
  808.  
  809.         if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or \
  810.            (self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None):
  811.             support |= SUPPORT_AWAY_MODE
  812.  
  813.         if (self._topic[CONF_HOLD_STATE_TOPIC] is not None) or \
  814.            (self._topic[CONF_HOLD_COMMAND_TOPIC] is not None):
  815.             support |= SUPPORT_HOLD_MODE
  816.  
  817.         if (self._topic[CONF_AUX_STATE_TOPIC] is not None) or \
  818.            (self._topic[CONF_AUX_COMMAND_TOPIC] is not None):
  819.             support |= SUPPORT_AUX_HEAT
  820.  
  821.         return support
  822.  
  823.     @property
  824.     def min_temp(self):
  825.         """Return the minimum temperature."""
  826.         return self._min_temp
  827.  
  828.     @property
  829.     def max_temp(self):
  830.         """Return the maximum temperature."""
  831.         return self._max_temp
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement