From 552de662cd0f23d1bda7bc37404e88d24bdde623 Mon Sep 17 00:00:00 2001 From: Gerald Amrhein Date: Fri, 26 Jun 2020 12:56:10 +0200 Subject: [PATCH] add a lot of data validation and debug entity --- climate_automation.py | 268 +++++++++++++++++++++++++++++------------- 1 file changed, 187 insertions(+), 81 deletions(-) diff --git a/climate_automation.py b/climate_automation.py index 3b5cdec..36ea71b 100644 --- a/climate_automation.py +++ b/climate_automation.py @@ -101,6 +101,15 @@ if DEBUG: logger.info("Python Script 'Climate Automation' debugging: %s", DEBUG) +# Defaults +DEFAULT_ECO = 16 +DEFAULT_COMFORT = 22 +DEFAULT_PRESENSE = True +DEFAULT_SWITCH_ON_OFF = True +DEFAULT_WINDOW_OPEN = False +DEFAULT_PARTY = False +DEFAULT_USE_ECO_GLOBAL = False + # climate entitiy attributes # get attributes ATTR_HVAC_MODES = "hvac_modes" @@ -142,49 +151,117 @@ SENSOR_OFF = "off" IN_TIME = False WINDOW_OPEN = False -SWITCH_ON_OFF = True PRESENSE = True +# define all sensors initial +ENTITY_IDS = [] +SWITCHES_ON_OFF = [] +SWITCH_ON_OFF = DEFAULT_SWITCH_ON_OFF +SENSORS_WINDOWS = [] +SENSORS_PRESENCE = [] +SENSOR_PARTY_MODE = DEFAULT_PARTY + + +SENSOR_PRESENCE = DEFAULT_PRESENSE +SCHEDULE1_START = NO_TIME +SCHEDULE1_END = NO_TIME +SCHEDULE2_START = NO_TIME +SCHEDULE2_END = NO_TIME +TEMPERATURE_ECO_GLOBAL = DEFAULT_ECO +TEMPERATURE_USE_GLOBAL = DEFAULT_USE_ECO_GLOBAL +TEMPERATURE_ECO = DEFAULT_ECO +TEMPERATURE_COMFORT = DEFAULT_COMFORT + +entity = "global" + if DEBUG: logger.info("### Start getting data ###") -try: # get automation data # the enitity to control - ENTITY_IDS = data.get("entity_ids", None) - # the list from OFF Sensors - SWITCHES_ON_OFF = data.get("switches_on_off", []) - # the list of window sensors - SENSORS_WINDOWS = data.get("windows", []) - # the presense sensor - SENSORS_PRESENCE = data.get("sensors_presence", []) - # Party Mode? - SENSOR_PARTY_MODE = hass.states.get(data.get("party_mode")).state - # Time schedule 1 - SCHEDULE1_START = hass.states.get(data.get("schedule1_start")).state - SCHEDULE1_END = hass.states.get(data.get("schedule1_end")).state - # Time schedule 2 - SCHEDULE2_START = hass.states.get(data.get("schedule2_start")).state - SCHEDULE2_END = hass.states.get(data.get("schedule2_end")).state - # the Global ECO Temperature - TEMPERATURE_ECO_GLOBAL = hass.states.get(data.get("eco_global_temperature")).state - # should the global eco used or the local - TEMPERATURE_USE_GLOBAL = hass.states.get(data.get("use_eco_global")).state - # the room eco temperature - TEMPERATURE_ECO = hass.states.get(data.get("eco_temperature")).state - # the rooms comfort temperatur - TEMPERATURE_COMFORT = hass.states.get(data.get("comfort_temperature")).state -except: - logger.info("Ther is an error retrieving the service_data") + ENTITY_IDS = data.get("entity_ids", []) + if len(ENTITY_IDS) > 0: + if DEBUG: + logger.info("OK Enitiys are defined") + + SWITCHES_ON_OFF = data.get("switches_on_off", []) + if SWITCHES_ON_OFF == []: + if DEBUG: + logger.info("INFO There are no ON/OFF Switches configured") + + SENSORS_WINDOWS = data.get("windows", []) + if SENSORS_WINDOWS == []: + if DEBUG: + logger.info("INFO There are no window sensors configured") + + SENSORS_PRESENCE = data.get("sensors_presence", []) + if SENSORS_PRESENCE == []: + if DEBUG: + logger.info("INFO There are no presense sensors configured") + + mypartyswitch = data.get("party_mode", None) + if mypartyswitch != None: + SENSOR_PARTY_MODE = hass.states.get(mypartyswitch).state + else: + logger.info("INFO There is no Party Mode Switch configured") + + mytime1start = data.get("schedule1_start", None) + if mytime1start != None: + SCHEDULE1_START = hass.states.get(mytime1start).state + else: + if DEBUG: + logger.info("INFO no schedule 1 start time") + + mytime1end = data.get("schedule1_end", None) + if mytime1end != None: + SCHEDULE1_END = hass.states.get(mytime1end).state + else: + if DEBUG: + logger.info("INFO no schdule 1 end time") + + mymytime2start = data.get("schedule2_start", None) + if mymytime2start != None: + SCHEDULE2_START = hass.states.get(mymytime2start).state + else: + if DEBUG: + logger.info("INFO no schedule 1 start time") -# set states if service_data is not configured -if SENSORS_PRESENCE is None: - SENSORS_PRESENCE = True + mymytime2end = data.get("schedule2_end", None) + if mymytime2end != None: + SCHEDULE2_END = hass.states.get(mymytime2end).state + else: + if DEBUG: + logger.info("INFO no schdule 2 end time") -if SENSOR_PARTY_MODE is None: - SENSOR_PARTY_MODE = False + mytempglobal = data.get("eco_global_temperature", None) + if mytempglobal != None: + TEMPERATURE_ECO_GLOBAL = hass.states.get(mytempglobal).state + else: + if DEBUG: + logger.info("INFO no global eco temperature") + + myuseglobal = data.get("use_eco_global", None) + if myuseglobal != None: + TEMPERATURE_USE_GLOBAL = hass.states.get(myuseglobal).state + else: + if DEBUG: + logger.info("INFO no eco / global eco switch") -if TEMPERATURE_ECO_GLOBAL is None: - TEMPERATURE_ECO_GLOBAL = False + myeco = data.get("eco_temperature", None) + if myeco != None: + TEMPERATURE_ECO = hass.states.get(myeco).state + else: + if DEBUG: + logger.info("INFO no eco temperature") + + mycomfort = data.get("comfort_temperature", None) + if mycomfort != None: + TEMPERATURE_COMFORT = hass.states.get(mycomfort).state + else: + if DEBUG: + logger.info("INFO no comfort temperature") + else: + logger.info("Error getting Entitys - Script Ends") + exit() ########################################################################################## # Functions which are independent the climate ENTITY_ID # @@ -199,9 +276,10 @@ # function to check the state in time schedules def is_time_between(begin_time, end_time) -> bool: + func = entity + ": is_time_between" if begin_time == end_time: if DEBUG: - logger.info("Schedule Times are equal") + logger.info("<%s> Schedule Times are equal", func) return False # get starttime xhour = int(begin_time.split(":")[0]) @@ -214,26 +292,27 @@ def is_time_between(begin_time, end_time) -> bool: now = dt_util.now().time() if DEBUG: - logger.info("check %s < %s < %s", begin, now, end) + logger.info("<%s> check %s < %s < %s", func, begin, now, end) try: if begin_time < end_time: return now >= begin and now <= end else: # crosses midnight return now >= begin or now <= end except ValueError: - logger.info("time format wrong") + logger.info("<%s> time format wrong", func) return False # function to check presense def is_at_home() -> bool: + func = entity + ": is_at_home" for presense in SENSORS_PRESENCE: presense_state = hass.states.get(presense).state if DEBUG: - logger.info("%s has state: %s", presense, presense_state) + logger.info("<%s> %s has state: %s", func, presense, presense_state) if presense_state != SENSOR_ON: # if any presense is not ON, then PRESENSE go to False - logger.info("%s is gone to OFF", presense) + logger.info("<%s> %s is gone to OFF", func, presense) return False else: return True @@ -251,41 +330,54 @@ def service_data(entity): # set thermostat off only if not current to reduce commands def call_climate_off(): + func = entity + ": call_climate_off" if current_state != HVAC_MODE_OFF: + if DEBUG: + logger.info("<%s> Set climate to OFF", func) SERVICE_DATA = service_data(ENTITY_ID) hass.services.call(DOMAIN, SERVICE_TURN_OFF, SERVICE_DATA, False) # set climate to comfort and set comfort temperature # this function use the temperature as parameter to avoid global data for that purpose def call_climate_comfort(comfort_temperature): + func = entity + ": call_climate_comfort" # turn climate on only if not current to reduce commands if current_state != HVAC_MODE_HEAT: + if DEBUG: + logger.info("<%s> Set HVAC mode to heat", func) SERVICE_DATA = service_data(ENTITY_ID) hass.services.call(DOMAIN, SERVICE_TURN_ON, SERVICE_DATA, False) # set preset only if not current to reduce commands if current_preset != PRESET_MODE_NONE: + if DEBUG: + logger.info("<%s> Set preset mode to none", func) SERVICE_DATA = service_data(ENTITY_ID) SERVICE_DATA[ATTR_PRESET_MODE] = PRESET_MODE_NONE hass.services.call(DOMAIN, SERVICE_SET_PRESET_MODE, SERVICE_DATA, False) # set temperature SERVICE_DATA = service_data(ENTITY_ID) + if DEBUG: + logger.info("<%s> Set temperature to comfort", func) SERVICE_DATA[ATTR_TEMPERATURE] = float(comfort_temperature) hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, SERVICE_DATA, False) # set climate to eco and st eco temperature # this function use the temperature as parameter to avoid global data for that purpose def call_climate_eco(eco_temperature): + func = entity + ": call_climate_eco" # turn climate on only if not current to reduce commands #if current_state != HVAC_MODE_HEAT: # hass.services.call(DOMAIN, SERVICE_TURN_ON, SERVICE_DATA, False) # set preset only if not current to reduce commands if current_preset != PRESET_MODE_ECO: - logger.info("Set preset_mode to eco") + logger.info("<%s> Set preset_mode to eco", func) SERVICE_DATA = service_data(ENTITY_ID) SERVICE_DATA[ATTR_PRESET_MODE] = PRESET_MODE_ECO + if DEBUG: + logger.info("<%s> Service data: %s", func, SERVICE_DATA) hass.services.call(DOMAIN, SERVICE_SET_PRESET_MODE, SERVICE_DATA, False) # set temperature - logger.info("Set temperature") + logger.info("<%s> Set temperature to eco", func) SERVICE_DATA = service_data(ENTITY_ID) SERVICE_DATA[ATTR_TEMPERATURE] = float(eco_temperature) hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, SERVICE_DATA, False) @@ -305,22 +397,22 @@ def call_climate_eco(eco_temperature): # with False the time scheduling is OFF and there is always ECO if DEBUG: logger.info("### Check time schedules ###") - +func = entity + ": in_time" if is_time_between(SCHEDULE1_START, SCHEDULE1_END): if DEBUG: - logger.info("schedule 1 is now active") + logger.info("<%s> schedule 1 is now active", func) IN_TIME = True else: if DEBUG: - logger.info("schedule 1 is out of now") + logger.info("<%s> schedule 1 is out of now", func) if is_time_between(SCHEDULE2_START, SCHEDULE2_END): if DEBUG: - logger.info("schedule 2 is now active") + logger.info("<%s> schedule 2 is now active", func) IN_TIME = True else: if DEBUG: - logger.info("schedule 2 is out of now") + logger.info("<%s> schedule 2 is out of now", func) # Switches ON or OFF # check if switch states are OFF @@ -329,38 +421,41 @@ def call_climate_eco(eco_temperature): logger.info("### check OFF sensor states ###") # if any SENSOR_OFF is OFF, the switch get False for switch in SWITCHES_ON_OFF: + func = entity + ": test on of" switch_state = hass.states.get(switch).state if DEBUG: - logger.info("%s has state: %s", switch, switch_state) + logger.info("<%> %s has state: %s", func, switch, switch_state) if switch_state == SENSOR_OFF: - logger.info("Climate automation will be OFF") + logger.info("<%s> Climate automation will be OFF", func) SWITCH_ON_OFF = False # windows # check if windows are open if DEBUG: logger.info("### check window states ###") +func = entity + ": windows" for window in SENSORS_WINDOWS: window_state = hass.states.get(window).state if DEBUG: - logger.info("%s has state: %s", window, window_state) + logger.info("<%s> %s has state: %s", func, window, window_state) # We invert this statement to catch 'None' as well if hass.states.is_state(window, SENSOR_ON): # if any Windows is open, then WINDOW_OPEN get True if DEBUG: - logger.info("%s is open", window) + logger.info("<%s> %s is open", func, window) WINDOW_OPEN = True # which eco temperature should be used if DEBUG: logger.info("### check use of global eco ###") +func = entity + ": global eco" if TEMPERATURE_USE_GLOBAL == SENSOR_ON: if DEBUG: - logger.info("Global Eco should be used") + logger.info("<%s> Global Eco should be used", func) ECO_SETPOINT = TEMPERATURE_ECO_GLOBAL else: if DEBUG: - logger.info("local eco will be used") + logger.info("<%s> local eco will be used", func) ECO_SETPOINT = TEMPERATURE_ECO ########################################################################################## @@ -368,34 +463,45 @@ def call_climate_eco(eco_temperature): ########################################################################################## # check first if an entity is given -try: - count = len(ENTITY_IDS) - if count >= 1: - for ENTITY_ID in ENTITY_IDS: - # get the current thermostat values - try: - actual_states = hass.states.get(ENTITY_ID) - current_state = actual_states.state - current_preset = actual_states.attributes.get(ATTR_PRESET_MODE) - current_setpoint = actual_states.attributes.get(ATTR_TEMPERATURE) - current_temperature = actual_states.attributes.get(ATTR_CURRENT_TEMPERATURE) - except: - logger.info("Entity: %s could not be retrieved", ENTITY_ID) +func = "main" +try: + for ENTITY_ID in ENTITY_IDS: + entity = ENTITY_ID.split('.')[1] + func = entity + ": main" + if DEBUG: + logger.info("<%s> work with enitiy: %s", func, ENTITY_ID) + # get the current thermostat values + try: + func = entity + ": retrieve entity data" + actual_states = hass.states.get(ENTITY_ID) + current_state = actual_states.state + current_preset = actual_states.attributes.get(ATTR_PRESET_MODE) + current_setpoint = actual_states.attributes.get(ATTR_TEMPERATURE) + current_temperature = actual_states.attributes.get(ATTR_CURRENT_TEMPERATURE) + if DEBUG: + logger.info("<%s> Actual Data: %s - %s - %s - %s", func, current_state, current_preset, current_setpoint, current_temperature) + except: + logger.info("<%s> Entity: %s could not be retrieved", func, ENTITY_ID) - logger.info("ENTITY: %s - SWITCH_ON_OFF: %s - PRESENSE: %s - WINDOWS OPEN: %s - IN TIME: %s - Party: %s", ENTITY_ID, SWITCH_ON_OFF, PRESENSE, WINDOW_OPEN, IN_TIME, SENSOR_PARTY_MODE) - logger.info("Eco Setpoint: %s - Global Eco: %s - use Global: %s - Eco: %s - Comfort: %s", ECO_SETPOINT, TEMPERATURE_ECO_GLOBAL, TEMPERATURE_USE_GLOBAL, TEMPERATURE_ECO, TEMPERATURE_COMFORT) - - # Logic - if SWITCH_ON_OFF == True and WINDOW_OPEN == False: - logger.info("No OFF State and all windows closed -> Thermostat will be on") - if IN_TIME == False or PRESENSE == False: - logger.info("Now is out of Time Schdule or presense is off -> Eco mode") - call_climate_eco(ECO_SETPOINT) - elif IN_TIME or SENSOR_PARTY_MODE == SENSOR_ON: - logger.info("Now is in Time Schedule or Party -> Comfort Mode") - call_climate_comfort(TEMPERATURE_COMFORT) - else: - logger.info("There is a OFF state or a window open -> Thermostat will be off") - call_climate_off() + func = entity + ": main" + logger.info("<%s> ENTITY: %s - SWITCH_ON_OFF: %s - PRESENSE: %s - WINDOWS OPEN: %s - IN TIME: %s - Party: %s", func, ENTITY_ID, SWITCH_ON_OFF, PRESENSE, WINDOW_OPEN, IN_TIME, SENSOR_PARTY_MODE) + logger.info("<%s> Eco Setpoint: %s - Global Eco: %s - use Global: %s - Eco: %s - Comfort: %s", func, ECO_SETPOINT, TEMPERATURE_ECO_GLOBAL, TEMPERATURE_USE_GLOBAL, TEMPERATURE_ECO, TEMPERATURE_COMFORT) + + # Logic + if SWITCH_ON_OFF == True and WINDOW_OPEN == False: + func = entity + ": switch or window" + logger.info("<%s> No OFF State and all windows closed -> Thermostat will be on", func) + if IN_TIME == False or PRESENSE == False: + func = entity + ": in time false" + logger.info("<%s> Now is out of Time Schdule or presense is off -> Eco mode", func) + call_climate_eco(ECO_SETPOINT) + elif IN_TIME or SENSOR_PARTY_MODE == SENSOR_ON: + func = entity + ": in time" + logger.info("<%s> Now is in Time Schedule or Party -> Comfort Mode", func) + call_climate_comfort(TEMPERATURE_COMFORT) + else: + func = entity + ": offmode" + logger.info("<%s> There is a OFF state or a window open -> Thermostat will be off", func) + call_climate_off() except: - logger.info("There is an error with the enitity") \ No newline at end of file + logger.info("<%s> There is an error with the enitity", func) \ No newline at end of file