diff --git a/README.md b/README.md index 6cc8ec5..c929c89 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Some info background information on the ThermIQ-MQTT interface can be found here https://thermiq.net **This integration will evolve over time** -Please note that in Release 1.0.0 the thermiq_automations.yaml and lovelace.yaml has changed and you need to update your automation.yaml and UI accordingly. +Please note that since Release 1.2.0 the setup has changed significantly and you need to update your setup accordingly. ## Requirements @@ -22,11 +22,12 @@ Please note that in Release 1.0.0 the thermiq_automations.yaml and lovelace.yaml 1. Make sure that ThermIQ is properly setup and communicating with the MQTT server 2. Make sure that MQTT Integration in Home Assistant is setup and communicating with the MQTT server 3. Make sure you have HACS set-up (https://github.com/custom-components/hacs). -4. Go to the HACS integrations page, add ThermIQ integration. +4. Make sure you have the required Lovelace plugins +5. Go to the HACS integrations page, add ThermIQ integration. ## Configuration #### Component Configuration: -The complete configuration entry can be found in [configuration_thermiq.yaml](https://github.com/ThermIQ/thermiq_mqtt-ha/blob/master/configuration_thermiq.yaml) and should be copied to your configuration.yaml file. +The complete configuration entry needed is: ```yaml # Begin ThermIQ ##### @@ -35,39 +36,14 @@ The complete configuration entry can be found in [configuration_thermiq.yaml](ht thermiq_mqtt: mqtt_node: ThermIQ/ThermIQ-mqtt #thermiq_dbg: True - - -#Input entities for ThermIQ -> configuration.yaml -input_number: - thermiq_rum_bor2: - name: 'Indoor target temp.' - initial: 0 - min: 0 - max: 50 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_kurva: - name: 'Curve' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: '' - icon: 'mdi:speedometer' - mode: box - ``` #### Automations configuration -A complete automation between frontend input entities to the backend control is included in [automations_thermiq.yaml](https://github.com/ThermIQ/thermiq_mqtt-ha/blob/master/automations_thermiq.yaml) and should be copied to your automations.yaml file. - +No setup of automations is needed #### Lovelace Configuration: -The file [lovelace_config.yaml](https://github.com/ThermIQ/thermiq_mqtt-ha/blob/master/lovelace_config.yaml) contains a status view and a comprehensive set of variables. The lovelace_config.yaml should be inserted in the lovelace raw configuration editor. A short example could look like: +The file [lovelace_config.yaml](https://github.com/ThermIQ/thermiq_mqtt-ha/blob/master/lovelace_config.yaml) contains a status view and a comprehensive set of variables. The lovelace_config.yaml should be inserted in the lovelace raw configuration editor. It should look something like this: ```yaml # ################################################ @@ -112,7 +88,7 @@ The Home Assistant server needs to be restarted once all configuration is done ## Contributing Contributions are welcome! If you'd like to contribute, feel free to pick up anything on the current [GitHub issues](https://github.com/ThermIQ/thermiq_mqtt-ha/issues) list! -Also, help improving the lovelace card would be great! +All help improving the integration is appreciated! diff --git a/automations_thermiq.yaml b/automations_thermiq.yaml deleted file mode 100644 index 08f52e5..0000000 --- a/automations_thermiq.yaml +++ /dev/null @@ -1,94 +0,0 @@ -# ############################################################################ -# Automations for ThermIQ -> automation.yaml -# ### ThermIQ, From Input number -> MQTT - - alias: ThermIQ, Input number to MQTT - trigger: - platform: state - entity_id: -#input_number entity_ids for ThermIQ - - input_number.thermiq_indoor_requested_t - - input_number.thermiq_integral1_curve_slope - - input_number.thermiq_integral1_curve_min - - input_number.thermiq_integral1_curve_max - - input_number.thermiq_integral1_curve_p5 - - input_number.thermiq_integral1_curve_0 - - input_number.thermiq_integral1_curve_n5 - - input_number.thermiq_heating_stop_t - - input_number.thermiq_reduction_t - - input_number.thermiq_room_factor - - input_number.thermiq_integral2_curve_slope - - input_number.thermiq_integral2_curve_min - - input_number.thermiq_integral2_curve_max - - input_number.thermiq_integral2_curve_target - - input_number.thermiq_integral2_curve_actual - - input_number.thermiq_outdoor_stop_t - - input_number.thermiq_preassure_pipe_limit_t - - input_number.thermiq_hotwater_start_t - - input_number.thermiq_hotwater_runtime_m - - input_number.thermiq_heatpump_runtime_m - - input_number.thermiq_legionella_runtime_m - - input_number.thermiq_legionella_stop_t - - input_number.thermiq_integral1_a_limit - - input_number.thermiq_integral1_hysteresis_t - - input_number.thermiq_returnline_max_t - - input_number.thermiq_start_interval_min_m - - input_number.thermiq_brine_min_t - - input_number.thermiq_cooling_target_t - - input_number.thermiq_integral2_a_limit - - input_number.thermiq_integral2_hysteresis_t - - input_number.thermiq_elect_boiler_steps_max - - input_number.thermiq_current_consumption_max_a - - input_number.thermiq_shunt_time_s - - input_number.thermiq_hotwater_stop_t -# ###### #condition: - #condition: template - # This condition does not work... Temporary fix in write_id - # value_template: "{{ ( (states(trigger.to_state.state ) | int) != (states('input_number.' + trigger.to_state.object_id) | int) ) }}" - action: - # Send an MQTT message - - service: thermiq_mqtt.write_id - data_template: - value_id: "{{ trigger.entity_id }}" - value: '{{ trigger.to_state.state | int }}' - bitmask: 0xffff - - -# # ###### - -# ############################################################################ -# ### ThermIQ, From Input select -> MQTT - - - alias: ThermIQ, Inputs select to MQTT - trigger: - platform: state - entity_id: - - input_select.thermiq_main_mode - action: - # Send an MQTT message - - service: thermiq_mqtt.write_id - data_template: - value_id: "{{ trigger.entity_id }}" - value: "{{ state_attr(trigger.entity_id,'options').index(states(trigger.entity_id)) | int }}" - bitmask: 0xffff - - - - -# ############################################################################ -# ### ThermIQ, From Sensor to -> Input select - - - alias: ThermIQ, Sensor to Inputs Select - trigger: - platform: state - entity_id: - - thermiq_mqtt.main_mode - condition: - condition: template - value_template: "{{ ( ((trigger.to_state.state | int ) >=0 ) and ((trigger.to_state.state | int ) <5 ) ) }}" - action: - service: input_select.select_option - data_template: - entity_id: 'input_select.thermiq_main_mode' - option: "{{ state_attr('input_select.thermiq_main_mode','options')[(trigger.to_state.state | int )] }}" -# ############################################################################ - diff --git a/configuration_thermiq.yaml b/configuration_thermiq.yaml index b2dc5e0..708279e 100644 --- a/configuration_thermiq.yaml +++ b/configuration_thermiq.yaml @@ -1,364 +1,12 @@ +# ################## # Begin ThermIQ ##### # ThermIQ-MQTT configuration.yaml entry # Set the mqtt_node name here. Thats all! + thermiq_mqtt: mqtt_node: ThermIQ/ThermIQ-mqtt thermiq_dbg: False - -#Input entities for ThermIQ -> configuration.yaml -input_number: - thermiq_indoor_requested_t: - name: 'Indoor target temp.' - initial: 0 - min: 0 - max: 50 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral1_curve_slope: - name: 'Curve' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral1_curve_min: - name: 'Curve min' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral1_curve_max: - name: 'Curve max' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral1_curve_p5: - name: 'Curve +5' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral1_curve_0: - name: 'Curve 0' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral1_curve_n5: - name: 'Curve -5' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_heating_stop_t: - name: 'Heatstop' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_reduction_t: - name: 'Temp. reduction' - initial: 0 - min: 0 - max: 100 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_room_factor: - name: 'Room factor' - initial: 0 - min: 0 - max: 2 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral2_curve_slope: - name: 'Curve 2' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral2_curve_min: - name: 'Curve 2 min' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral2_curve_max: - name: 'Curve 2 max' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral2_curve_target: - name: 'Curve 2, Target' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral2_curve_actual: - name: 'Curve 2, Actual' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_outdoor_stop_t: - name: 'Outdoor stop temp. (20=-20C)' - initial: 0 - min: 0 - max: 100 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_preassure_pipe_limit_t: - name: 'Pressurepipe, temp. limit' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_hotwater_start_t: - name: 'Hotwater starttemp.' - initial: 0 - min: 0 - max: 100 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_hotwater_runtime_m: - name: 'Hotwater operating time' - initial: 0 - min: 0 - max: 32767 - step: 1 - unit_of_measurement: 'min' - icon: 'mdi:timer' - mode: box - - thermiq_heatpump_runtime_m: - name: 'Heatpump operating time' - initial: 0 - min: 0 - max: 32767 - step: 1 - unit_of_measurement: 'min' - icon: 'mdi:timer' - mode: box - - thermiq_legionella_runtime_m: - name: 'Legionella interval' - initial: 0 - min: 0 - max: 32767 - step: 1 - unit_of_measurement: 'days' - icon: 'mdi:timer' - mode: box - - thermiq_legionella_stop_t: - name: 'Legionella stop temp.' - initial: 0 - min: 0 - max: 100 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral1_a_limit: - name: 'Integral limit A1' - initial: 0 - min: -32768 - max: 32767 - step: 1 - unit_of_measurement: 'Cmin' - icon: 'mdi:speedometer' - mode: box - - thermiq_integral1_hysteresis_t: - name: 'Hysteresis, heatpump' - initial: 0 - min: 0 - max: 100 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_returnline_max_t: - name: 'Returnline temp., max limit' - initial: 0 - min: 0 - max: 100 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_start_interval_min_m: - name: 'Minimum starting interval' - initial: 0 - min: 0 - max: 32767 - step: 1 - unit_of_measurement: 'min' - icon: 'mdi:timer' - mode: box - - thermiq_brine_min_t: - name: 'Brinetemp., min limit (-15=OFFV)' - initial: 0 - min: -25 - max: 100 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_cooling_target_t: - name: 'Cooling, target' - initial: 0 - min: 0 - max: 50 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_integral2_a_limit: - name: 'Integral limit A2' - initial: 0 - min: 0 - max: 200 - step: 1 - unit_of_measurement: '10 Cmin' - icon: 'mdi:speedometer' - mode: box - - thermiq_integral2_hysteresis_t: - name: 'Hysteresis limit, aux' - initial: 0 - min: 0 - max: 100 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - - thermiq_elect_boiler_steps_max: - name: 'Max step, aux' - initial: 0 - min: -32768 - max: 32767 - step: 1 - unit_of_measurement: 'steps' - icon: 'mdi:speedometer' - mode: box - - thermiq_current_consumption_max_a: - name: 'Electrical current, max limit' - initial: 0 - min: -32768 - max: 32767 - step: 1 - unit_of_measurement: 'A' - icon: 'mdi:speedometer' - mode: box - - thermiq_shunt_time_s: - name: 'Shunt time' - initial: 0 - min: 0 - max: 32767 - step: 1 - unit_of_measurement: 's' - icon: 'mdi:timer' - mode: box - - thermiq_hotwater_stop_t: - name: 'Hotwater stop temp.' - initial: 0 - min: 0 - max: 100 - step: 1 - unit_of_measurement: 'ºC' - icon: 'mdi:temperature-celsius' - mode: slider - -# ##### -input_select: - thermiq_main_mode: - name: 'ThermIQ Mode' - options: - - 'Off' - - 'Auto' - - 'Heatpump only' - - 'Electric only' - - 'Hotwater only' - icon: mdi:power - # End ThermIQ ###### +# ################## diff --git a/custom_components/thermiq/__init__.py b/custom_components/thermiq/__init__.py index 89be819..0161bfa 100644 --- a/custom_components/thermiq/__init__.py +++ b/custom_components/thermiq/__init__.py @@ -3,6 +3,7 @@ from datetime import timedelta import voluptuous as vol +from collections import OrderedDict # import ThermIQ register defines from custom_components.thermiq_mqtt.thermiq_regs import ( @@ -57,14 +58,31 @@ from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery +from homeassistant.helpers.template import Template from homeassistant.util import Throttle -from .helper import create_automations, create_entities_and_automations, CONFIG_INPUT_BOOLEAN, COMPONENT_INPUT_BOOLEAN, \ - CONFIG_INPUT_DATETIME, COMPONENT_INPUT_DATETIME, CONFIG_INPUT_NUMBER, COMPONENT_INPUT_NUMBER, CONFIG_INPUT_TEXT, \ - COMPONENT_INPUT_TEXT, CONFIG_TIMER, COMPONENT_TIMER +from .helper import ( + create_automations, + create_entities_and_automations, + CONFIG_INPUT_BOOLEAN, + COMPONENT_INPUT_BOOLEAN, + CONFIG_INPUT_DATETIME, + COMPONENT_INPUT_DATETIME, + CONFIG_INPUT_NUMBER, + COMPONENT_INPUT_NUMBER, + CONFIG_INPUT_TEXT, + COMPONENT_INPUT_TEXT, + CONFIG_TIMER, + COMPONENT_TIMER, +) -from .helper import create_input_datetime, create_input_number, create_automation +from .helper import ( + create_input_datetime, + create_input_number, + create_input_select, + create_automation, +) from homeassistant.components.automation import EVENT_AUTOMATION_RELOADED from homeassistant.const import CONF_ENTITY_ID, CONF_STATE, EVENT_HOMEASSISTANT_STARTED @@ -88,14 +106,13 @@ CONF_CMD = "cmd_msg" DEFAULT_CMD = "/WRITE" DEFAULT_DBG = False -MSG_RECEIVED_STATE = 'thermiq_mqtt.last_msg_time' +MSG_RECEIVED_STATE = "thermiq_mqtt.last_msg_time" CONFIG_SCHEMA = vol.Schema( { vol.Required(CONF_MQTT_NODE, default=DEFAULT_NODE): cv.string, vol.Optional(CONF_MQTT_DBG, default=False): cv.boolean, - # vol.Optional(CONF_CMD,default=DEFAULT_CMD): cv.string, }, extra=vol.ALLOW_EXTRA, ) @@ -105,25 +122,29 @@ async def async_setup(hass, config): - """Set up the ThermIQ_ MQTT component.""" - conf = config.get(DOMAIN,{}) - conf.entity_id = "thermiq_mqtt.timestamp" - conf.data_topic = conf.get(CONF_MQTT_NODE) + "/data" - dbg=conf.get(CONF_MQTT_DBG) + + conf = config.get(DOMAIN, {}) + mqtt_base = conf.get(CONF_MQTT_NODE) + dbg = conf.get(CONF_MQTT_DBG) + + conf.entity_id = "thermiq_mqtt" + + _LOGGER.debug("mqtt base: " + mqtt_base) + conf.data_topic = mqtt_base + "/data" + conf.cmd_topic = mqtt_base + "/write" + conf.set_topic = mqtt_base + "/set" + if dbg == True: - conf.cmd_topic = conf.get(CONF_MQTT_NODE) + "/mqtt_dbg" + conf.cmd_topic += "_dbg" + conf.set_topic += "_dbg" _LOGGER.debug("MQTT Debug write enabled") - else: - conf.cmd_topic = conf.get(CONF_MQTT_NODE) + "/write" - _LOGGER.info("data:" + conf.data_topic) - _LOGGER.info("cmd:" + conf.cmd_topic) hass.data[DOMAIN] = {} hass.data[DOMAIN] = ThermIQ_MQTT(config[DOMAIN]) - hass.states.async_set('thermiq_mqtt.time_str','Waiting on '+conf.data_topic) - hass.data[DOMAIN]._data['mqtt_counter']=0 + hass.states.async_set("thermiq_mqtt.time_str", "Waiting on " + conf.data_topic) + hass.data[DOMAIN]._data["mqtt_counter"] = 0 -# ### Setup our input helpers + # ### Setup the input helper ############################# CONFIG_INPUT_BOOLEAN.update(config.get(COMPONENT_INPUT_BOOLEAN, {})) CONFIG_INPUT_DATETIME.update(config.get(COMPONENT_INPUT_DATETIME, {})) CONFIG_INPUT_NUMBER.update(config.get(COMPONENT_INPUT_NUMBER, {})) @@ -136,32 +157,41 @@ async def handle_home_assistant_started_event(event: Event): async def handle_automation_reload_event(event: Event): await create_automations(hass) - hass.bus.async_listen(EVENT_HOMEASSISTANT_STARTED, handle_home_assistant_started_event) - + hass.bus.async_listen( + EVENT_HOMEASSISTANT_STARTED, handle_home_assistant_started_event + ) hass.bus.async_listen(EVENT_AUTOMATION_RELOADED, handle_automation_reload_event) -# ### -# ### Load our sensors + # ######################################################### + # ### Load sensors for platform in THERMIQ_PLATFORMS: _LOGGER.debug("platform:" + platform) discovery.load_platform(hass, platform, DOMAIN, {}, config) - -# Create reverse lookup dictionary (id_reg->reg_number) - id_reg= {} - for k,v in reg_id.items(): - id_reg[v[0]]=k - hass.states.async_set('thermiq_mqtt.'+v[0],-1) - _LOGGER.debug("id_reg[%s] => %s",v[0],k) - -# ### Lets create the inputs - icon_list={'time_input':'mdi:timer','sensor_input':'mdi:speedometer','temperature_input':'mdi:temperature-celsius'} - mode_list={'time_input':'slider','sensor_input':'box','temperature_input':'box'} + + # Create reverse lookup dictionary (id_reg->reg_number) + id_reg = {} + for k, v in reg_id.items(): + id_reg[v[0]] = k + hass.states.async_set("thermiq_mqtt." + v[0], -1) + # _LOGGER.debug("id_reg[%s] => %s", v[0], k) + + # ### Create the inputs and automations ############################# + icon_list = { + "time_input": "mdi:timer", + "sensor_input": "mdi:speedometer", + "temperature_input": "mdi:temperature-celsius", + } + mode_list = { + "time_input": "slider", + "sensor_input": "box", + "temperature_input": "box", + } + entity_list = [] for key in reg_id: if reg_id[key][1] in [ "temperature_input", "time_input", "sensor_input", - ]: device_id = key if key in id_names: @@ -173,9 +203,56 @@ async def handle_automation_reload_event(event: Event): input_unit = reg_id[key][2] input_min = reg_id[key][3] input_max = reg_id[key][4] + input_step = 1 + input_initial = -1 + # Check if room_sensor then allow for decimals + if reg_id[key][0] == "rf0": + input_step = 0.1 + + await create_input_number( + "thermiq_" + key, + friendly_name, + input_min, + input_max, + input_step, + input_initial, + mode_list[input_type], + input_unit, + icon=icon_list[input_type], + ) + entity_list.append("input_number.thermiq_" + key) + + if reg_id[key][1] in [ + "select_input", + ]: + device_id = key + if key in id_names: + friendly_name = id_names[key] + else: + friendly_name = None + input_reg = reg_id[key][0] + input_options = [ + "Off", + "Auto", + "Heatpump only", + "Elecric only", + "Hotwater only", + ] + input_initial = None + + await create_input_select( + "thermiq_" + key, + friendly_name, + input_options, + input_initial, + icon="mdi:power", + ) + + entity_list.remove("input_number.thermiq_room_sensor_set_t") + await create_automation_for_input_numbers(entity_list) + await create_automation_for_room_sensor() + # ### ################################################################## - await create_input_number('thermiq_'+key, friendly_name, input_min, input_max, 1, mode_list[input_type], input_unit, icon=icon_list[input_type]) - # ### @callback def message_received(message): @@ -185,52 +262,92 @@ def message_received(message): json_dict = json.loads(message.payload) if json_dict["Client_Name"][:8] == "ThermIQ_": for k in json_dict.keys(): - kstore=k.lower() - dstore=k - # Make internal register hex if incoming register is decimal format - if k[0]=='d': - reg=int(k[1:]) - kstore="r"+format(reg,'02x') - dstore="d"+format(reg,'03d') + kstore = k.lower() + dstore = k + # Make INDR_T and timestamp appear as normal register + if kstore == "indr_t": + kstore = "rf0" + if kstore == "timestamp": + kstore = "rf1" + # Create hex notation if incoming register is decimal format + if k[0] == "d": + reg = int(k[1:]) + kstore = "r" + format(reg, "02x") + dstore = "d" + format(reg, "03d") if len(kstore) != 3: - kstore=k - if k[0]=='r' and len(k)==3: - reg= int(k[1:],16) - dstore="d"+format(reg,'03d') + kstore = k + # Create decimal notation if incoming register is hex format + if k[0] == "r" and len(k) == 3: + reg = int(k[1:], 16) + dstore = "d" + format(reg, "03d") + + # Internal mapping of ThermIQ_MQTT regs, used to create update events hass.data[DOMAIN]._data[kstore] = json_dict[k] + + _LOGGER.debug("[%s] [%s] [%s]", kstore, json_dict[k], dstore) + # Map incomming registers to named settings based on id_reg (thermiq_regs) if kstore in id_reg: - if kstore!='r01' and kstore!='r03': - hass.states.async_set("thermiq_mqtt."+id_reg[kstore],json_dict[k]) - a=reg_id[id_reg[kstore]] - if (a[1]=='temperature_input') or (a[1]=='time_input') or (a[1]=='sensor_input') : - cntxt= { INP_ATTR_VALUE: json_dict[k] , ATTR_ENTITY_ID: 'input_number.thermiq_'+id_reg[kstore]} - #_LOGGER.debug(cntxt) - hass.async_create_task(hass.services.async_call(INP_DOMAIN,INP_SERVICE_SET_VALUE, cntxt, blocking=False)) - #if (a[1]=='select_input') : - #hass.states.async_set("input_select.thermiq_main_mode",'Auto'); - _LOGGER.debug("[%s] [%s] [%s]", kstore, json_dict[k],dstore) - -# Do some post processing of data eceived - hass.data[DOMAIN]._data['r01'] = ( + # r01 and r03 should be combined with respective decimal part r02 and r04 + if kstore != "r01" and kstore != "r03": + hass.states.async_set( + "thermiq_mqtt." + id_reg[kstore], json_dict[k] + ) + ## Set the corresponding input_number if applicable, incomming message always rules over UI settings + if reg_id[id_reg[kstore]][1] in [ + "temperature_input", + "time_input", + "sensor_input", + ]: + context = { + INP_ATTR_VALUE: json_dict[k], + ATTR_ENTITY_ID: "input_number.thermiq_" + + id_reg[kstore], + } + hass.async_create_task( + hass.services.async_call( + INP_DOMAIN, + INP_SERVICE_SET_VALUE, + context, + blocking=False, + ) + ) + if reg_id[id_reg[kstore]][1] == "input_select": + + context = { + INP_ATTR_VALUE: json_dict[k], + ATTR_ENTITY_ID: "input_select.thermiq_" + + id_reg[kstore], + } + hass.async_create_task( + hass.services.async_call( + INP_DOMAIN, + INP_SERVICE_SET_VALUE, + context, + blocking=False, + ) + ) + + # Do some post processing of data received + hass.data[DOMAIN]._data["r01"] = ( hass.data[DOMAIN]._data["r01"] + hass.data[DOMAIN]._data["r02"] / 10 ) - hass.states.async_set("thermiq_mqtt.t_rum_ar",hass.data[DOMAIN]._data['r01']) - + hass.states.async_set( + "thermiq_mqtt." + id_reg["r01"], hass.data[DOMAIN]._data["r01"] + ) + hass.data[DOMAIN]._data["r03"] = ( hass.data[DOMAIN]._data["r03"] + hass.data[DOMAIN]._data["r04"] / 10 ) - hass.states.async_set("thermiq_mqtt.t_rum_bor",hass.data[DOMAIN]._data["r03"]) - try: - hass.data[DOMAIN]._data['rf0']=hass.data[DOMAIN]._data['indr_t'] - except: - hass.data[DOMAIN]._data['rf0']=-1 - hass.data[DOMAIN]._data['indr_t']=-1 - - hass.data[DOMAIN]._data['mqtt_counter']+=1 - hass.states.async_set(MSG_RECEIVED_STATE,json_dict['timestamp']) - hass.states.async_set('thermiq_mqtt.time_str',json_dict['time']) + hass.states.async_set( + "thermiq_mqtt." + id_reg["r03"], hass.data[DOMAIN]._data["r03"] + ) + + hass.data[DOMAIN]._data["mqtt_counter"] += 1 + + hass.states.async_set("thermiq_mqtt.time_str", json_dict["time"]) + hass.bus.fire("thermiq_mqtt_msg_rec_event", {}) - + else: _LOGGER.error("JSON result was not from ThermIQ-mqtt") except ValueError: @@ -242,101 +359,297 @@ def message_received(message): def write_msg_service(call): """Service to send a message.""" _LOGGER.debug("message.entity_id:[%s]", call.data.get("entity_id")) - hass.async_create_task(hass.components.mqtt.async_publish(conf.cmd_topic, call.data.get("msg"))) + hass.async_create_task( + hass.components.mqtt.async_publish( + hass, conf.cmd_topic, call.data.get("msg"), qos=2, retain=False + ) + ) # Service to write specific reg with data, value_id will be translated to register number. @callback def write_reg_service(call): reg = call.data.get("reg") + reg = reg.lower() value = call.data.get("value") bitmask = call.data.get("bitmask") + + # We should check that reg is of format rxx (hex) or dnnn (decimal with possible leading zeroes) and is btwn 0-127, regardless case of x and d + # and give error here. ThermIQ-mqtt will throw away the message anyway + ## + + if not (isinstance(value, int)) or value is None: + _LOGGER.error("no value message sent due to missing value:[%s]", value) + return + if bitmask is None: bitmask = 0xFFFF - val = value | bitmask - msg = f'{{"r{reg:x}": {value} }}' - _LOGGER.debug("message.reg:[%s]", call.data.get("reg")) - _LOGGER.debug("message.value:[%s]", call.data.get("value")) - _LOGGER.debug("message.bitmask:[%s]", call.data.get("bitmask")) - _LOGGER.debug("msg:[%s]", msg) - hass.async_create_task(hass.components.mqtt.async_publish(conf.cmd_topic, msg)) + ## check the bitmask + # value = value | bitmask + value = int(value) & int(bitmask) + + # Lets use the decimal register notation in the MQTT message towards ThermIQ-MQTT to improve human readability + if reg[0] == "d": + dreg = reg + if reg[0] == "r" and len(reg) == 3: + reg = int(k[1:], 16) + dreg = "d" + format(reg, "03d") + + if dreg == "d240": + topic = conf.set_topic + else: + conf.cmd_topic + + # Make up the JSON payload + payload = json.dumps({dreg: value}) + + _LOGGER.debug("topic:[%s]", conf.cmd_topic) + _LOGGER.debug("payload:[%s]", payload) + hass.async_create_task( + hass.components.mqtt.async_publish( + hass, topic, payload, qos=2, retain=False + ) + ) # Service to write specific value_id with data, value_id will be translated to register number. @callback def write_id_service(call): """Service to send a message.""" - _LOGGER.debug("message.value_id:[%s]", call.data.get("value_id")) - _LOGGER.debug("message.payload:[%s]", call.data.get("value")) + register_id = call.data.get("register_id") + register_id = register_id.lower() + value = call.data.get("value") + bitmask = call.data.get("bitmask") + + if not (isinstance(value, int) or isinstance(value, float)) or value is None: + _LOGGER.error("no value message sent due to missing value:[%s]", value) + return - value_id = call.data.get("value_id").lower() - idx = len(value_id) - (value_id.find(".thermiq") + 9) + if bitmask is None: + bitmask = 0xFFFF + + ## check the bitmask + # value = value | bitmask + value = int(value) & int(bitmask) + + # Strip any leading instance names, then strip thermiq_ + idx = register_id.find(".") + 1 + idx = len(register_id) - (register_id.find("thermiq_", idx) + 8) if idx > 0: - value_id = value_id[-idx:] - _LOGGER.debug("message.value_id:[%s]", value_id) - if value_id in reg_id: - reg = reg_id[value_id][0] - kstore="r"+format(int(reg[1:],16),'02x') - dstore="d"+format(int(reg[1:],16),'03d') - value = call.data.get("value") - if not(isinstance(value, int)) or value is None: - return - bitmask = call.data.get("bitmask") - if bitmask is None: - bitmask = 0xFFFF - value = int(value) & int(bitmask) - msg = f'{{"{reg}": {value} }}' - _LOGGER.debug("message.value:[%s]", call.data.get("value")) - _LOGGER.debug("message.bitmask:[%s]", call.data.get("bitmask")) - _LOGGER.debug("msg:[%s]", msg) - - if (value != hass.data[DOMAIN]._data[reg]): - hass.data[DOMAIN]._data[reg]=value - hass.states.async_set("thermiq_mqtt."+id_reg[kstore],value) - _LOGGER.debug("set reg[%s]=%d",reg,hass.data[DOMAIN]._data[reg]) - if (hass.data[DOMAIN]._data['mqtt_counter']>3): - hass.async_create_task(hass.components.mqtt.async_publish(conf.cmd_topic, msg,2,False)) - else: - _LOGGER.debug( - "No need to write" - ) # Service to write specific value_id with data, value_id will be translated to register number. + register_id = register_id[-idx:] + + _LOGGER.debug("register_id:[%s]", register_id) + _LOGGER.debug("value:[%s]", value) + + if not (register_id in reg_id): + _LOGGER.error("no value message sent due to faulty reg:[%s]", register_id) + return + + reg = reg_id[register_id][0] + + _LOGGER.debug("reg:[%s]", reg) + # Lets use the decimal register notation in the MQTT message towards ThermIQ-MQTT to improve human readability + dreg = "d" + format(int(reg[1:], 16), "03d") + if dreg == "d240": + topic = conf.set_topic + else: + conf.cmd_topic + + # Make up the JSON payload + payload = json.dumps({dreg: value}) + + _LOGGER.debug("topic:[%s]", conf.cmd_topic) + _LOGGER.debug("payload:[%s]", payload) + if value != hass.data[DOMAIN]._data[reg]: + # Lets update the internal state, + hass.data[DOMAIN]._data[reg] = value + hass.states.async_set("thermiq_mqtt." + id_reg[reg], value) + _LOGGER.debug("set _data[%s]=%d", reg, hass.data[DOMAIN]._data[reg]) + if hass.data[DOMAIN]._data["mqtt_counter"] > 3: + hass.async_create_task( + hass.components.mqtt.async_publish( + hass, conf.cmd_topic, payload, qos=2, retain=False + ) + ) + else: + _LOGGER.debug("No need to write") @callback def write_mode_service(call): """Service to send a message.""" - - _LOGGER.debug("message.payload:[%s]", call.data.get("value")) - reg = "r33" value = int(call.data.get("value")) - if value is None: + if not (isinstance(value, int)) or value is None: + _LOGGER.error("no value message sent due to missing value:[%s]", value) return - bits = 2 ** value + + reg = "d51" bitmask = 0x01F - if bits is None: + if (value < 0) or (value > 5): + _LOGGER.error("Mode value is out of range:[%s]", value) return - value = int(bits) & int(bitmask) - msg = f'{{"t{reg}": {value} }}' - _LOGGER.debug("message.value:[%s]", call.data.get("value")) - _LOGGER.debug("message.value:[%s]", value) - _LOGGER.debug("msg:[%s]", msg) + + value = int(2 ** value) & int(bitmask) + + # Make up the JSON payload + payload = json.dumps({dreg: value}) + _LOGGER.debug("topic:[%s]", conf.cmd_topic) + _LOGGER.debug("payload:[%s]", payload) + if value != hass.data[DOMAIN]._data[reg]: - hass.states.async_set("thermiq_mqtt.d51") - hass.async_create_task(hass.components.mqtt.async_publish(conf.cmd_topic, msg)) + hass.states.async_set("thermiq_mqtt.r33", value) + hass.async_create_task( + hass.components.mqtt.async_publish( + hass, conf.cmd_topic, payload, qos=2, retain=False + ) + ) + else: + _LOGGER.debug("No need to write") + + @callback + def set_indr_t_service(call): + """Service to send a message.""" + value = float(call.data.get("value")) + if not (isinstance(value, float)) or value is None: + _LOGGER.error("no value message sent due to missing value:[%s]", value) + return + + reg = "rf0" + if (value < 10) or (value > 30): + _LOGGER.error("Mode value is out of range:[%s]", value) + return + + # Make up the JSON payload + payload = json.dumps({"INDR_T": value}) + _LOGGER.debug("topic:[%s]", conf.cmd_topic) + _LOGGER.debug("payload:[%s]", payload) + + if value != hass.data[DOMAIN]._data[reg]: + hass.states.async_set("thermiq_mqtt.rf0", value) + hass.async_create_task( + hass.components.mqtt.async_publish( + hass, conf.set_topic, payload, qos=2, retain=False + ) + ) else: _LOGGER.debug("No need to write") - # ### # Register our service with Home Assistant. hass.services.async_register(DOMAIN, "write_msg", write_msg_service) hass.services.async_register(DOMAIN, "write_id", write_id_service) hass.services.async_register(DOMAIN, "write_reg", write_reg_service) hass.services.async_register(DOMAIN, "write_mode", write_mode_service) + hass.services.async_register(DOMAIN, "set_indr_t", set_indr_t_service) _LOGGER.info("Subscribe:" + conf.data_topic) await hass.components.mqtt.async_subscribe(conf.data_topic, message_received) # Return boolean to indicate that initialization was successfully. return True +# #### automate it +async def create_automation_for_input_numbers(entities: list): + data = { + "alias": "ThermIQ Input numbers to MQTT", + "trigger": [{"platform": "state", "entity_id": entities}], + # 'condition': [ + # { + # } + # ], + "action": [ + { + "service": "thermiq_mqtt.write_id", + "data_template": { + "register_id": Template("{{ trigger.entity_id }}"), + "value": Template("{{ trigger.to_state.state | int }}"), + "bitmask": 0xFFFF, + }, + } + ], + "mode": "single", + "max_exceeded": "WARNING", + "max": 1, + "trace": {"stored_traces": 5}, + } + # _LOGGER.debug(data) + await create_automation(OrderedDict(data)) + + +async def create_automation_for_room_sensor(): + data = { + "alias": "ThermIQ Room sensor to MQTT", + "trigger": [ + {"platform": "state", "entity_id": "input_number.thermiq_room_sensor_set_t"} + ], + # 'condition': [ + # { + # } + # ], + "action": [ + { + "service": "thermiq_mqtt.set_indr_t", + "data_template": { + "value": Template("{{ trigger.to_state.state | float }}"), + }, + } + ], + "mode": "single", + "max_exceeded": "WARNING", + "max": 1, + "trace": {"stored_traces": 5}, + } + # _LOGGER.debug(data) + await create_automation(OrderedDict(data)) + + # ### Select -> MQTT + data = { + "alias": "ThermIQ, Inputs select MOde to MQTT", + "trigger": [ + {"platform": "state", "entity_id": ["input_select.thermiq_main_mode"],} + ], + "action": [ + { + "service": "thermiq_mqtt.write_id", + "data_template": { + "register_id": Template("{{ trigger.entity_id }}"), + "value": Template( + "{{ state_attr(trigger.entity_id,'options').index(states(trigger.entity_id)) | int }}" + ), + "bitmask": 0xFFFF, + }, + }, + ], + "mode": "single", + "max_exceeded": "WARNING", + "max": 1, + "trace": {"stored_traces": 5}, + } + await create_automation(OrderedDict(data)) + + data = { + "alias": "ThermIQ, Mode to Inputs Select", + "trigger": [{"platform": "state", "entity_id": ["thermiq_mqtt.main_mode"],}], + # 'condition': { + # 'condition': 'template', + # 'value_template': Template("{{ ( ((trigger.to_state.state | int ) >=0 ) and ((trigger.to_state.state | int ) <5 ) ) }}"), + # }, + "action": [ + { + "service": "input_select.select_option", + "data_template": { + "entity_id": ["input_select.thermiq_main_mode"], + "option": Template( + "{{ state_attr('input_select.thermiq_main_mode','options')[(trigger.to_state.state | int )] }}" + ), + }, + }, + ], + "mode": "single", + "max_exceeded": "WARNING", + "max": 1, + "trace": {"stored_traces": 5}, + } + await create_automation(OrderedDict(data)) + + class ThermIQ_MQTT: # Listener to be called when we receive a message. # The msg parameter is a Message object with the following members: @@ -350,15 +663,19 @@ def __init__(self, host): def get_value(self, item): """Get value for sensor.""" - res=self._data.get(item) - _LOGGER.debug("get_value(" + item + ")=%d",res) + res = self._data.get(item) + _LOGGER.debug("get_value(" + item + ")=%d", res) return res def update_state(self, command, state_command): """Send update command to ThermIQ.""" _LOGGER.debug("update_state:" + command + " " + state_command) self._data[state_command] = self._client.command(command) - hass.async_create_task(hass.components.mqtt.async_publish(conf.cmd_topic, self._data[state_command])) + hass.async_create_task( + hass.components.mqtt.async_publish( + conf.cmd_topic, self._data[state_command] + ) + ) async def async_update(self): _LOGGER.debug("Fetching data from ThermIQ-MQTT") diff --git a/custom_components/thermiq/binary_sensor.py b/custom_components/thermiq/binary_sensor.py index 4792405..08b9c8c 100644 --- a/custom_components/thermiq/binary_sensor.py +++ b/custom_components/thermiq/binary_sensor.py @@ -103,7 +103,7 @@ def vp_reg(self): @property def is_on(self): - return self._state + return self._state @property def state(self): diff --git a/custom_components/thermiq/helper.py b/custom_components/thermiq/helper.py index 0b3b198..3ae4017 100644 --- a/custom_components/thermiq/helper.py +++ b/custom_components/thermiq/helper.py @@ -2,12 +2,16 @@ from datetime import timedelta from logging import getLogger -from homeassistant.components.automation import async_setup as setup_automation, \ - _async_process_config as add_automation, AutomationConfig +from homeassistant.components.automation import ( + async_setup as setup_automation, + _async_process_config as add_automation, + AutomationConfig, +) from homeassistant.components.input_boolean import async_setup as setup_input_boolean from homeassistant.components.input_datetime import async_setup as setup_input_datetime from homeassistant.components.input_number import async_setup as setup_input_number from homeassistant.components.input_text import async_setup as setup_input_text +from homeassistant.components.input_select import async_setup as setup_input_select from homeassistant.components.timer import async_setup as setup_timer from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity @@ -17,18 +21,17 @@ _LOGGER = getLogger(__name__) -COMPONENT_AUTOMATION = 'automation' -COMPONENT_INPUT_BOOLEAN = 'input_boolean' -COMPONENT_INPUT_DATETIME = 'input_datetime' -COMPONENT_INPUT_NUMBER = 'input_number' -COMPONENT_INPUT_TEXT = 'input_text' -COMPONENT_TIMER = 'timer' +COMPONENT_AUTOMATION = "automation" +COMPONENT_INPUT_BOOLEAN = "input_boolean" +COMPONENT_INPUT_DATETIME = "input_datetime" +COMPONENT_INPUT_NUMBER = "input_number" +COMPONENT_INPUT_TEXT = "input_text" +COMPONENT_INPUT_SELECT = "input_select" +COMPONENT_TIMER = "timer" CUSTOM_ENTITY_COMPONENTS = {} -SETUP_FUNCTION = { - COMPONENT_AUTOMATION: setup_automation -} +SETUP_FUNCTION = {COMPONENT_AUTOMATION: setup_automation} AUTOMATIONS = [] @@ -37,6 +40,7 @@ CONFIG_INPUT_DATETIME = {} CONFIG_INPUT_NUMBER = {} CONFIG_INPUT_TEXT = {} +CONFIG_INPUT_SELECT = {} CONFIG_TIMER = {} # save whether custom inputs were declared, so they can be added to HA @@ -44,6 +48,7 @@ CUSTOM_INPUT_DATETIME = False CUSTOM_INPUT_NUMBER = False CUSTOM_INPUT_TEXT = False +CUSTOM_INPUT_SELECT = False CUSTOM_TIMER = False @@ -72,13 +77,11 @@ async def _get_platform(hass: HomeAssistant, domain: str): return CUSTOM_ENTITY_COMPONENTS[domain] -async def create_input_boolean(name: str, icon=None) -> str: - data = { - 'name': name - } +async def create_input_boolean(name: str, friendly_name: str, icon=None) -> str: + data = {"name": friendly_name} if icon: - data['icon'] = icon + data["icon"] = icon internal_name = slugify(name) @@ -87,22 +90,24 @@ async def create_input_boolean(name: str, icon=None) -> str: global CUSTOM_INPUT_BOOLEAN CUSTOM_INPUT_BOOLEAN = True - return 'input_boolean.{}'.format(internal_name) + return "input_boolean.{}".format(internal_name) -async def create_input_datetime(name: str, has_date: bool, has_time: bool, initial=None, - icon=None) -> str: - data = { - 'name': name, - 'has_date': has_date, - 'has_time': has_time - } +async def create_input_datetime( + name: str, + friendly_name: str, + has_date: bool, + has_time: bool, + initial=None, + icon=None, +) -> str: + data = {"name": friendly_name, "has_date": has_date, "has_time": has_time} if initial: - data['initial'] = initial + data["initial"] = initial if icon: - data['icon'] = icon + data["icon"] = icon internal_name = slugify(name) @@ -111,23 +116,32 @@ async def create_input_datetime(name: str, has_date: bool, has_time: bool, initi global CUSTOM_INPUT_DATETIME CUSTOM_INPUT_DATETIME = True - return 'input_datetime.{}'.format(internal_name) + return "input_datetime.{}".format(internal_name) -async def create_input_number(name: str, friendly_name:str, _min: int, _max: int, step: int, mode: str, - unit_of_measurement: str, icon=None) -> str: +async def create_input_number( + name: str, + friendly_name: str, + _min: int, + _max: int, + step: int, + initial: int, + mode: str, + unit_of_measurement: str, + icon=None, +) -> str: data = { - 'name': friendly_name, - 'min': _min, - 'max': _max, - 'step': step, - 'mode': mode, - 'friendly_name': friendly_name, - 'unit_of_measurement': unit_of_measurement + "name": friendly_name, + "min": _min, + "max": _max, + "step": step, + "initial": initial, + "mode": mode, + "unit_of_measurement": unit_of_measurement, } if icon: - data['icon'] = icon + data["icon"] = icon internal_name = slugify(name) @@ -136,22 +150,30 @@ async def create_input_number(name: str, friendly_name:str, _min: int, _max: int global CUSTOM_INPUT_NUMBER CUSTOM_INPUT_NUMBER = True - return 'input_number.{}'.format(internal_name) + return "input_number.{}".format(internal_name) -async def create_input_text(name: str, _min=0, _max=100, initial=None, - pattern='', mode='text', icon=None) -> str: +async def create_input_text( + name: str, + friendly_name: str, + _min=0, + _max=100, + initial=None, + pattern="", + mode="text", + icon=None, +) -> str: data = { - 'name': name, - 'min': _min, - 'max': _max, - 'initial': initial, - 'pattern': pattern, - 'mode': mode + "name": friendly_name, + "min": _min, + "max": _max, + "initial": initial, + "pattern": pattern, + "mode": mode, } if icon: - data['icon'] = icon + data["icon"] = icon internal_name = slugify(name) @@ -160,15 +182,34 @@ async def create_input_text(name: str, _min=0, _max=100, initial=None, global CUSTOM_INPUT_TEXT CUSTOM_INPUT_TEXT = True - return 'input_text.{}'.format(internal_name) + return "input_text.{}".format(internal_name) -async def create_timer(name: str, duration='00:00:00') -> str: +async def create_input_select( + name: str, friendly_name: str, options: list, initial=None, icon=None +) -> str: data = { - 'name': name, - 'duration': duration + "name": friendly_name, + "options": options, + "initial": initial, } + if icon: + data["icon"] = icon + + internal_name = slugify(name) + + CONFIG_INPUT_SELECT[internal_name] = data + + global CUSTOM_INPUT_SELECT + CUSTOM_INPUT_SELECT = True + + return "input_select.{}".format(internal_name) + + +async def create_timer(name: str, friendly_name: str, duration="00:00:00") -> str: + data = {"name": friendly_name, "duration": duration} + internal_name = slugify(name) CONFIG_TIMER[internal_name] = data @@ -176,41 +217,73 @@ async def create_timer(name: str, duration='00:00:00') -> str: global CUSTOM_TIMER CUSTOM_TIMER = True - return 'timer.{}'.format(internal_name) + return "timer.{}".format(internal_name) async def create_entities_and_automations(hass: HomeAssistant): if CUSTOM_INPUT_BOOLEAN: for entity_id in list( - filter(lambda eid: COMPONENT_INPUT_BOOLEAN == eid.split('.')[0], hass.states.async_entity_ids())): + filter( + lambda eid: COMPONENT_INPUT_BOOLEAN == eid.split(".")[0], + hass.states.async_entity_ids(), + ) + ): hass.states.async_remove(entity_id) await setup_input_boolean(hass, {COMPONENT_INPUT_BOOLEAN: CONFIG_INPUT_BOOLEAN}) if CUSTOM_INPUT_DATETIME: for entity_id in list( - filter(lambda eid: COMPONENT_INPUT_DATETIME == eid.split('.')[0], hass.states.async_entity_ids())): + filter( + lambda eid: COMPONENT_INPUT_DATETIME == eid.split(".")[0], + hass.states.async_entity_ids(), + ) + ): hass.states.async_remove(entity_id) - await setup_input_datetime(hass, {COMPONENT_INPUT_DATETIME: CONFIG_INPUT_DATETIME}) + await setup_input_datetime( + hass, {COMPONENT_INPUT_DATETIME: CONFIG_INPUT_DATETIME} + ) if CUSTOM_INPUT_NUMBER: for entity_id in list( - filter(lambda eid: COMPONENT_INPUT_NUMBER == eid.split('.')[0], hass.states.async_entity_ids())): + filter( + lambda eid: COMPONENT_INPUT_NUMBER == eid.split(".")[0], + hass.states.async_entity_ids(), + ) + ): hass.states.async_remove(entity_id) await setup_input_number(hass, {COMPONENT_INPUT_NUMBER: CONFIG_INPUT_NUMBER}) if CUSTOM_INPUT_TEXT: for entity_id in list( - filter(lambda eid: COMPONENT_INPUT_TEXT == eid.split('.')[0], hass.states.async_entity_ids())): + filter( + lambda eid: COMPONENT_INPUT_TEXT == eid.split(".")[0], + hass.states.async_entity_ids(), + ) + ): hass.states.async_remove(entity_id) await setup_input_text(hass, {COMPONENT_INPUT_TEXT: CONFIG_INPUT_TEXT}) + if CUSTOM_INPUT_SELECT: + for entity_id in list( + filter( + lambda eid: COMPONENT_INPUT_SELECT == eid.split(".")[0], + hass.states.async_entity_ids(), + ) + ): + hass.states.async_remove(entity_id) + + await setup_input_select(hass, {COMPONENT_INPUT_SELECT: CONFIG_INPUT_SELECT}) if CUSTOM_TIMER: for entity_id in list( - filter(lambda eid: COMPONENT_TIMER == eid.split('.')[0], hass.states.async_entity_ids())): + filter( + lambda eid: COMPONENT_TIMER == eid.split(".")[0], + hass.states.async_entity_ids(), + ) + ): hass.states.async_remove(entity_id) await setup_timer(hass, {COMPONENT_TIMER: CONFIG_TIMER}) @@ -228,8 +301,6 @@ async def create_automation(data: dict): async def create_automations(hass: HomeAssistant): platform = await _get_platform(hass, "automation") - data = { - 'automation': AUTOMATIONS - } + data = {"automation": AUTOMATIONS} - await add_automation(hass, OrderedDict(data), platform) \ No newline at end of file + await add_automation(hass, OrderedDict(data), platform) diff --git a/custom_components/thermiq/manifest.json b/custom_components/thermiq/manifest.json index b7bb8c7..bf3250c 100644 --- a/custom_components/thermiq/manifest.json +++ b/custom_components/thermiq/manifest.json @@ -6,6 +6,7 @@ "dependencies": ["mqtt"], "config_flow": false, "codeowners": ["@ThermIQ"], - "version": "0.99.3", - "requirements":[] + "version": "1.0.3", + "requirements":[], + "iot_class": "local_push" } diff --git a/custom_components/thermiq/sensor.py b/custom_components/thermiq/sensor.py index 8314e8b..20c01d6 100644 --- a/custom_components/thermiq/sensor.py +++ b/custom_components/thermiq/sensor.py @@ -21,7 +21,7 @@ id_names, id_units, reg_id, - id_names + id_names, ) _LOGGER = logging.getLogger(__name__) @@ -56,7 +56,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= vp_type = reg_id[key][1] vp_unit = reg_id[key][2] - dev.append(ThermIQ_MQTT(hass, data, device_id, vp_reg, friendly_name,vp_type,vp_unit,)) + dev.append( + ThermIQ_MQTT( + hass, data, device_id, vp_reg, friendly_name, vp_type, vp_unit, + ) + ) async_add_entities(dev) @@ -64,7 +68,7 @@ class ThermIQ_MQTT(Entity): """Representation of a Sensor.""" def __init__( - self, hass, data, device_id, vp_reg, friendly_name,vp_type,vp_unit, + self, hass, data, device_id, vp_reg, friendly_name, vp_type, vp_unit, ): """Initialize the Template switch.""" self.hass = hass @@ -75,20 +79,17 @@ def __init__( _LOGGER.debug("entity_id:" + self.entity_id) _LOGGER.debug("idx:" + device_id) self._name = friendly_name - self._state = False + self._state = None self._icon = None - if ((vp_type in [ - "temperature", - "temperature_input",]) or - (vp_unit in ["C",])) : + if (vp_type in ["temperature", "temperature_input",]) or (vp_unit in ["C",]): self._icon = "mdi:temperature-celsius" self._unit = TEMP_CELSIUS elif vp_type in [ - "sensor_boolean", + "sensor_boolean", ]: self._unit = "" self._icon = "mdi:alert" - else: + else: self._unit = vp_unit self._icon = "mdi:gauge" # "mdi:thermometer" ,"mdi:oil-temperature", "mdi:gauge", "mdi:speedometer", "mdi:alert" diff --git a/custom_components/thermiq/services.yaml b/custom_components/thermiq/services.yaml index a7ed0b3..089ec78 100644 --- a/custom_components/thermiq/services.yaml +++ b/custom_components/thermiq/services.yaml @@ -9,39 +9,58 @@ write_msg: description: Text to send example: 'A text that will be sent' +write_reg: + # Write value to thermiq_mqtt register + description: Write value to thermiq_mqtt register + # Different fields that your service accepts + fields: + # Key of the field + + reg: + description: Register number (in decimal) to write to + example: '40' + value: + description: Value to write (Accepts json formatted numbers) + example: '0x14' + bitmask: + description: Optional bitmask (defaults to 0xffff) + example: '0x02' write_id: # Write value to thermiq_mqtt id - description: Write value to thermiq_mqtt entity_id + description: Write value to thermiq_mqtt register_id, will be translated to register number # Different fields that your service accepts fields: # Key of the field - value_id: + register_id: # Description of the field - description: Name(s) of the entities to set + description: Name of the HP register to set # Example value that can be passed for this field example: "sensor.thermiq_rum_bor2" value: - description: Value to write (Accepts json formatted numbers) + description: Value to write (Accepts all json formatted numbers) example: '0x14' bitmask: description: Optional bitmask (defaults to 0xffff) example: '0x02' -write_reg: - # Write value to thermiq_mqtt register - description: Write value to thermiq_mqtt register - # Different fields that your service accepts + +write_mode: + # Set Mode to input_select thermiq_mqtt + description: set value 0-5 of mode + fields: - # Key of the field + value: + description: Select hetpump Mode + example: '2' - reg: - description: Register number (in decimal) to write to - example: '40' + +set_indr_t: + # Set value INDR_T to thermiq_mqtt + description: set value to thermiq_mqtt register_id, will be translated to register number + # Different fields that your service accepts + fields: value: - description: Value to write (Accepts json formatted numbers) - example: '0x14' - bitmask: - description: Optional bitmask (defaults to 0xffff) - example: '0x02' + description: Value to write (Accepts one decimal) + example: '22.3' diff --git a/custom_components/thermiq/thermiq_regs.py b/custom_components/thermiq/thermiq_regs.py index f26a542..6e53fcc 100644 --- a/custom_components/thermiq/thermiq_regs.py +++ b/custom_components/thermiq/thermiq_regs.py @@ -141,7 +141,7 @@ 'boiler_6kw_on_runtime_h' : ['r72', 'time', 'h', ], 'msd1_d' : ['r73', 'sensor', '', ], 'graph_display_offset' : ['r74', 'sensor', '', ], - 'room_sensor_set_t' : ['rf0', 'temperature', 'ºC', 0, 50 ], + 'room_sensor_set_t' : ['rf0', 'temperature_input', 'ºC', 0, 50 ], 'time' : ['rf1', 'time', 's', 0, 50 ], } @@ -279,7 +279,7 @@ 'boiler_6kw_on_runtime_h' : 'Runtime 6 kW', 'msd1_d' : 'DTS2_MSD1', 'graph_display_offset' : 'GrafCounterOffSet   ', - 'room_sensor_set_t' : 'Room sensor, Set', + 'room_sensor_set_t' : 'Room sensor, Set target', } diff --git a/hacs.json b/hacs.json index 422eee7..6fc9d6b 100644 --- a/hacs.json +++ b/hacs.json @@ -7,6 +7,6 @@ "switch" ], "iot_class": "Local Push", - "homeassistant": "2021.11.5", + "homeassistant": "2021.12", "render_readme": true } diff --git a/lovelace_config.yaml b/lovelace_config.yaml index 8c68c2d..ca6cbaf 100644 --- a/lovelace_config.yaml +++ b/lovelace_config.yaml @@ -59,6 +59,7 @@ - entity: input_select.thermiq_main_mode + - entity: input_number.thermiq_room_sensor_set_t - type: custom:fold-entity-row head: type: section @@ -167,4 +168,3 @@ - entity: sensor.thermiq_internal_logging_t - entity: input_number.thermiq_elect_boiler_steps_max - entity: input_number.thermiq_current_consumption_max_a - - entity: sensor.thermiq_room_sensor_set_t