Created
September 7, 2023 11:00
-
-
Save ALERTua/0e89f5279dd0e1de7ee270c9e718d8d2 to your computer and use it in GitHub Desktop.
auto_ac
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# https://github.com/custom-components/pyscript | |
# https://github.com/ALERTua/ha_pyscript_modules | |
from imports import * | |
DEFAULT_BOOST_TEMP_DIFFERENCE = 1.2 | |
DEFAULT_TEMP_TOLERANCE_UP = 0.5 | |
DEFAULT_TEMP_TOLERANCE_DOWN = 0.5 | |
DEFAULT_HOLD = constants.HOLD_1M | |
PRECISION = 0.5 | |
MIN_TEMP = 18 | |
MAX_TEMP = 32 | |
PRESET_MODE_BOOST = 'boost' | |
PRESET_MODE_OFF = 'none' | |
HVAC_MODE_COOL = 'cool' | |
HVAC_MODE_HEAT = 'heat' | |
HVAC_MODE_FAN = 'fan_only' | |
HVAC_MODE_OFF = 'off' | |
FAN_MODE_AUTO = 'Auto' | |
FAN_MODES = ['Silence', '1', '2', '3', '4', '5'] | |
OFFICE_AC = constants.OFFICE_AC | |
OFFICE_IB = 'input_boolean.office_auto_ac' | |
OFFICE_TEMP = constants.OFFICE_TEMPERATURE | |
OFFICE_WANTED_TEMP = 'input_number.office_wanted_temperature' | |
OFFICE_ALLOWED_MODES = 'input_select.office_auto_ac_allowed_modes' | |
OFFICE_TOLERANCE_UP = 0.2 | |
OFFICE_TOLERANCE_DOWN = 0.2 | |
OFFICE_KWARGS = dict( | |
ac_entity=OFFICE_AC, | |
cur_temp_entity=OFFICE_TEMP, | |
allowed_modes_selector=OFFICE_ALLOWED_MODES, | |
wanted_temperature_entity=OFFICE_WANTED_TEMP, | |
boost_trigger_difference=DEFAULT_BOOST_TEMP_DIFFERENCE, | |
tolerance_up=OFFICE_TOLERANCE_UP, | |
tolerance_down=OFFICE_TOLERANCE_DOWN, | |
change_temperature=True, | |
change_fan_speed=True, | |
fan_speed_limit=None, | |
allow_turning_off=False, | |
) | |
ROOM_AC = constants.ROOM_AC | |
ROOM_IB = 'input_boolean.room_auto_ac' | |
ROOM_TEMP = constants.ROOM_TEMPERATURE | |
ROOM_WANTED_TEMP = 'input_number.room_wanted_temperature' | |
ROOM_ALLOWED_MODES = 'input_select.room_auto_ac_allowed_modes' | |
ROOM_TOLERANCE_UP = 0.5 | |
ROOM_TOLERANCE_DOWN = 0.5 | |
ROOM_KWARGS = dict( | |
ac_entity=constants.ROOM_AC, | |
cur_temp_entity=ROOM_TEMP, | |
allowed_modes_selector=ROOM_ALLOWED_MODES, | |
wanted_temperature_entity=ROOM_WANTED_TEMP, | |
boost_trigger_difference=DEFAULT_BOOST_TEMP_DIFFERENCE, | |
tolerance_up=ROOM_TOLERANCE_UP, | |
tolerance_down=ROOM_TOLERANCE_DOWN, | |
change_temperature=True, | |
change_fan_speed=True, | |
fan_speed_limit=None, | |
allow_turning_off=True, | |
) | |
KITCHEN_AC = constants.KITCHEN_AC | |
KITCHEN_IB = 'input_boolean.kitchen_auto_ac' | |
KITCHEN_TEMP = constants.KITCHEN_TEMPERATURE | |
KITCHEN_WANTED_TEMP = 'input_number.kitchen_wanted_temperature' | |
KITCHEN_ALLOWED_MODES = 'input_select.kitchen_auto_ac_allowed_modes' | |
KITCHEN_TOLERANCE_UP = 0.5 | |
KITCHEN_TOLERANCE_DOWN = 0.5 | |
KITCHEN_KWARGS = dict( | |
ac_entity=KITCHEN_AC, | |
cur_temp_entity=KITCHEN_TEMP, | |
allowed_modes_selector=KITCHEN_ALLOWED_MODES, | |
wanted_temperature_entity=KITCHEN_WANTED_TEMP, | |
boost_trigger_difference=DEFAULT_BOOST_TEMP_DIFFERENCE, | |
tolerance_up=KITCHEN_TOLERANCE_UP, | |
tolerance_down=KITCHEN_TOLERANCE_DOWN, | |
change_temperature=True, | |
change_fan_speed=True, | |
fan_speed_limit=None, | |
allow_turning_off=True, | |
) | |
BEDROOM_AC = constants.BEDROOM_AC | |
BEDROOM_IB = constants.BEDROOM_AUTO_AC | |
BEDROOM_TEMP = constants.BEDROOM_TEMPERATURE | |
BEDROOM_WANTED_TEMP = 'input_number.bedroom_wanted_temperature' | |
BEDROOM_ALLOWED_MODES = 'input_select.bedroom_auto_ac_allowed_modes' | |
BEDROOM_TOLERANCE_UP = 0.5 | |
BEDROOM_TOLERANCE_DOWN = 0.5 | |
BEDROOM_HOLD = constants.HOLD_5M | |
BEDROOM_KWARGS = dict( | |
ac_entity=BEDROOM_AC, | |
cur_temp_entity=BEDROOM_TEMP, | |
allowed_modes_selector=BEDROOM_ALLOWED_MODES, | |
wanted_temperature_entity=BEDROOM_WANTED_TEMP, | |
boost_trigger_difference=DEFAULT_BOOST_TEMP_DIFFERENCE, | |
tolerance_up=BEDROOM_TOLERANCE_UP, | |
tolerance_down=BEDROOM_TOLERANCE_DOWN, | |
change_temperature=True, | |
change_fan_speed=True, | |
fan_speed_limit=None, | |
allow_turning_off=False, | |
) | |
@state_trigger( | |
BEDROOM_TEMP, | |
state_hold=BEDROOM_HOLD, | |
kwargs=BEDROOM_KWARGS, | |
) | |
@state_trigger( | |
BEDROOM_IB, | |
BEDROOM_WANTED_TEMP, | |
BEDROOM_ALLOWED_MODES, | |
state_hold=3, | |
kwargs=BEDROOM_KWARGS, | |
) | |
@state_active(f"{BEDROOM_IB} == 'on'" | |
f" and {BEDROOM_AC} not in {constants.UNK_S} " | |
f" and {BEDROOM_TEMP} not in {constants.UNK_S} " | |
f" and {BEDROOM_WANTED_TEMP} not in {constants.UNK_S} " | |
f" and {BEDROOM_ALLOWED_MODES} not in {constants.UNK_S}") | |
def auto_ac_bedroom(trigger_type=None, var_name=None, value=None, old_value=None, context=None, **kwargs): | |
ib = entity(BEDROOM_IB) | |
if ib.state() != 'on': | |
return | |
return _auto_ac_main(trigger_type=trigger_type, var_name=var_name, value=value, old_value=old_value, context=context, **kwargs) | |
@state_trigger( | |
OFFICE_TEMP, | |
state_hold=DEFAULT_HOLD, | |
kwargs=OFFICE_KWARGS, | |
) | |
@state_trigger( | |
OFFICE_IB, | |
OFFICE_WANTED_TEMP, | |
OFFICE_ALLOWED_MODES, | |
state_hold=2, | |
kwargs=OFFICE_KWARGS, | |
) | |
@state_active(f"{OFFICE_IB} == 'on'" | |
f" and {OFFICE_AC} not in {constants.UNK_S} " | |
f" and {OFFICE_TEMP} not in {constants.UNK_S} " | |
f" and {OFFICE_WANTED_TEMP} not in {constants.UNK_S} " | |
f" and {OFFICE_ALLOWED_MODES} not in {constants.UNK_S}") | |
def auto_ac_office(trigger_type=None, var_name=None, value=None, old_value=None, context=None, **kwargs): | |
ib = entity(OFFICE_IB) | |
if ib.state() != 'on': | |
return | |
return _auto_ac_main(trigger_type=trigger_type, var_name=var_name, value=value, old_value=old_value, | |
context=context, **kwargs) | |
@state_trigger( | |
ROOM_TEMP, | |
state_hold=DEFAULT_HOLD, | |
kwargs=ROOM_KWARGS, | |
) | |
@state_trigger( | |
ROOM_ALLOWED_MODES, | |
ROOM_IB, | |
ROOM_WANTED_TEMP, | |
state_hold=2, | |
kwargs=ROOM_KWARGS, | |
) | |
@state_active(f"{ROOM_IB} == 'on'" | |
f" and {ROOM_AC} not in {constants.UNK_S}" | |
f" and {ROOM_TEMP} not in {constants.UNK_S}" | |
f" and {ROOM_WANTED_TEMP} not in {constants.UNK_S}" | |
f" and {ROOM_ALLOWED_MODES} not in {constants.UNK_S}") | |
def auto_ac_room(trigger_type=None, var_name=None, value=None, old_value=None, context=None, **kwargs): | |
ib = entity(ROOM_IB) | |
if ib.state() != 'on': | |
return | |
return _auto_ac_main(trigger_type=trigger_type, var_name=var_name, value=value, old_value=old_value, context=context, **kwargs) | |
@state_trigger( | |
KITCHEN_TEMP, | |
state_hold=DEFAULT_HOLD, | |
kwargs=KITCHEN_KWARGS, | |
) | |
@state_trigger( | |
KITCHEN_IB, | |
KITCHEN_WANTED_TEMP, | |
KITCHEN_ALLOWED_MODES, | |
state_hold=3, | |
kwargs=KITCHEN_KWARGS, | |
) | |
@state_active(f"{KITCHEN_IB} == 'on'" | |
f" and {KITCHEN_AC} not in {constants.UNK_S}" | |
f" and {KITCHEN_TEMP} not in {constants.UNK_S}" | |
f" and {KITCHEN_WANTED_TEMP} not in {constants.UNK_S}" | |
f" and {KITCHEN_ALLOWED_MODES} not in {constants.UNK_S}") | |
def auto_ac_kitchen(trigger_type=None, var_name=None, value=None, old_value=None, context=None, **kwargs): | |
ib = entity(KITCHEN_IB) | |
if ib.state() != 'on': | |
return | |
return _auto_ac_main(trigger_type=trigger_type, var_name=var_name, value=value, old_value=old_value, context=context, **kwargs) | |
def turn_off(ac_entity, allow_turning_off=True, ac_action_wait=3): | |
if allow_turning_off: | |
ac_entity.turn_off() | |
task.sleep(ac_action_wait) | |
ac_entity.turn_off() | |
task.sleep(ac_action_wait) | |
else: | |
ac_entity.set_hvac_mode(HVAC_MODE_FAN) | |
task.sleep(ac_action_wait) | |
ac_entity.set_preset_mode(PRESET_MODE_OFF) | |
task.sleep(ac_action_wait) | |
ac_entity.set_fan_mode(FAN_MODES[0]) | |
def _auto_ac_main(trigger_type=None, var_name=None, value=None, old_value=None, context=None, **kwargs): | |
# 'entity_id': 'climate.ac_office', | |
# 'state': 'cool', | |
# 'attributes': { | |
# 'hvac_modes': [ | |
# <HVACMode.FAN_ONLY: 'fan_only'>, | |
# <HVACMode.DRY: 'dry'>, | |
# <HVACMode.COOL: 'cool'>, | |
# <HVACMode.HEAT: 'heat'>, | |
# <HVACMode.HEAT_COOL: 'heat_cool'>, | |
# <HVACMode.OFF: 'off'> | |
# ], | |
# 'min_temp': 7, | |
# 'max_temp': 35, | |
# 'target_temp_step': 1, | |
# 'fan_modes': ['Auto', 'Silence', '1', '2', '3', '4', '5'], | |
# 'preset_modes': ['none', 'away', 'eco', 'boost'], | |
# 'swing_modes': ['Off', 'Vertical', 'Horizontal', '3D'], | |
# 'current_temperature': 24.0, | |
# 'temperature': 24.0, | |
# 'fan_mode': 'Silence', | |
# 'hvac_action': <HVACAction.COOLING: 'cooling'>, | |
# 'preset_mode': 'none', | |
# 'swing_mode': 'Off', | |
# 'friendly_name': 'OfficeAC', | |
# 'supported_features': <ClimateEntityFeature.SWING_MODE|PRESET_MODE|FAN_MODE|TARGET_TEMPERATURE: 57>}, | |
# 'last_changed': '2023-05-23T16:32:31.787600+00:00', | |
# 'last_updated': '2023-05-23T16:32:31.787600+00:00', | |
wanted_temp_entity_id = kwargs.get('wanted_temperature_entity') | |
ac_entity_id = kwargs.get('ac_entity') | |
assert ac_entity_id, f"ac_entity_id: {ac_entity_id}, kwargs: {kwargs}" | |
tolerance_up = round(float(kwargs.get('tolerance_up', DEFAULT_TEMP_TOLERANCE_UP)), 1) | |
tolerance_up = max(tolerance_up, PRECISION) | |
tolerance_down = round(float(kwargs.get('tolerance_down', DEFAULT_TEMP_TOLERANCE_DOWN)), 1) | |
tolerance_down = max(tolerance_down, PRECISION) | |
cur_temp_entity_id = kwargs.get('cur_temp_entity') | |
change_temperature = kwargs.get('change_temperature', True) | |
change_fan_speed = kwargs.get('change_fan_speed', True) | |
boost_temp_difference = round(float(kwargs.get('boost_trigger_difference', DEFAULT_BOOST_TEMP_DIFFERENCE)), 1) | |
allowed_modes_selector = kwargs.get('allowed_modes_selector', None) | |
fan_speed_limit = kwargs.get('fan_speed_limit', None) | |
allow_turning_off = kwargs.get('allow_turning_off', True) | |
cur_temp_entity = entity(cur_temp_entity_id) | |
cur_temp = round(float(cur_temp_entity.state()), 1) | |
cur_temp_friendly_name = cur_temp_entity.friendly_name() | |
ac_entity = entity(ac_entity_id) | |
ac_state = ac_entity.state() | |
ac_preset_mode = ac_entity.attrs().get('preset_mode') | |
ac_fan_speed = ac_entity.attrs().get('fan_mode') | |
ac_temperature = round(float(ac_entity.attrs().get('temperature', cur_temp) or cur_temp), 1) | |
ac_friendly_name = ac_entity.friendly_name() | |
ac_action_wait = 3 | |
ac_inside_temp = float(ac_entity.attrs().get('current_temperature', cur_temp) or cur_temp) | |
msgs = DiscordMsgBucket(name=f"{__name__} for {ac_friendly_name}", target='1111696430206287892') | |
wanted_temp_entity = entity(wanted_temp_entity_id) | |
wanted_temp_entity_friendly_name = wanted_temp_entity.friendly_name() | |
wanted_temp = round(float(wanted_temp_entity.state()), 1) | |
temp_low_bar = wanted_temp - tolerance_down | |
msgs.add(f'↓:{wanted_temp}-{tolerance_down}:{temp_low_bar}') | |
temp_high_bar = wanted_temp + tolerance_up | |
msgs.add(f'↑:{wanted_temp}+{tolerance_down}:{temp_high_bar}') | |
temp_difference = round(float(cur_temp - wanted_temp), 1) | |
# log.debug(f"temp_difference = round(float(cur_temp {cur_temp} - wanted_temp {wanted_temp}), 1) = {temp_difference}") | |
temp_difference_abs = abs(temp_difference) | |
# log.debug(f"temp_difference_abs={temp_difference_abs}") | |
allowed_modes = [HVAC_MODE_COOL, HVAC_MODE_HEAT] | |
if allowed_modes_selector: | |
allowed_modes_selector_entity = entity(allowed_modes_selector) | |
allowed_mode = allowed_modes_selector_entity.state() | |
allowed_modes = [_ for _ in allowed_modes if _ in allowed_mode.lower()] | |
allowed_modes.append(HVAC_MODE_FAN) | |
allowed_modes.append(HVAC_MODE_OFF) | |
wanted_state = HVAC_MODE_OFF | |
preset_target = PRESET_MODE_OFF | |
if cur_temp >= temp_high_bar: | |
msgs.add(f'{cur_temp} >= ↑{temp_high_bar}') | |
wanted_state = HVAC_MODE_COOL | |
elif cur_temp <= temp_low_bar: # got it | |
msgs.add(f'{cur_temp} <= ↓{temp_low_bar}') | |
wanted_state = HVAC_MODE_HEAT | |
else: | |
wanted_state = ac_state | |
msgs.add(f'{temp_high_bar} ↑ {cur_temp} ↓ {temp_low_bar}') | |
if wanted_state not in allowed_modes: | |
msgs.add(f'{ac_friendly_name} wanted_state unallowed: {wanted_state}. Turning off.') | |
msgs.send() | |
turn_off(ac_entity, allow_turning_off, ac_action_wait) | |
return | |
elif ac_state != wanted_state: | |
msgs.add(f"wanted_state: {wanted_state}") | |
if ac_state != HVAC_MODE_OFF and wanted_state == HVAC_MODE_OFF: | |
log.debug(f"{ac_friendly_name} wanted_state off. Turning off.") | |
msgs.send() | |
turn_off(ac_entity, allow_turning_off, ac_action_wait) | |
return | |
elif ac_state == HVAC_MODE_OFF and wanted_state == HVAC_MODE_OFF: | |
log.debug(f"{ac_friendly_name} ac_state == wanted_state == {HVAC_MODE_OFF}") | |
return | |
if ac_state != HVAC_MODE_OFF and ac_state not in allowed_modes: | |
msgs.add(f'{ac_friendly_name} current state unallowed: {ac_state}. Turning off.') | |
msgs.send() | |
turn_off(ac_entity, allow_turning_off, ac_action_wait) | |
return | |
# target_temperature = wanted_temp - temp_difference | |
target_temperature = ac_inside_temp - temp_difference # base target temperature on AC inside temperature | |
target_temperature_max = target_temperature + tolerance_up | |
target_temperature_min = target_temperature - tolerance_down | |
target_temperature = max(target_temperature, MIN_TEMP) | |
target_temperature_max = max(target_temperature_max, MIN_TEMP) | |
target_temperature_min = max(target_temperature_min, MIN_TEMP) | |
target_temperature = min(target_temperature, MAX_TEMP) | |
target_temperature_max = min(target_temperature_max, MAX_TEMP) | |
target_temperature_min = min(target_temperature_min, MAX_TEMP) | |
try: | |
index_try = FAN_MODES.index(ac_fan_speed) | |
except: | |
index_try = 'Unknown' | |
msgs_init = [ | |
f":leaves: {__name__} for {ac_friendly_name}:", | |
f"🌡️ {cur_temp_friendly_name}: {cur_temp}", | |
f"🎯 {wanted_temp_entity_friendly_name}: {wanted_temp}", | |
f'temp_difference: {temp_difference}', | |
f"state: {ac_state}", | |
f"allowed_modes: {allowed_modes}", | |
f"preset_mode: {ac_preset_mode}", | |
f"🌬️fan_speed: {ac_fan_speed}: {index_try}/{len(FAN_MODES)}", | |
] | |
if ac_state != wanted_state and wanted_state in allowed_modes: | |
msgs.add(f'Setting HVAC state {ac_state} to {wanted_state}') | |
ac_entity.set_hvac_mode(wanted_state) | |
task.sleep(ac_action_wait) | |
if change_fan_speed: # and temp_difference_ok | |
wanted_fan_speed = float(len(FAN_MODES)) * (temp_difference or 0.1) / float(boost_temp_difference) | |
wanted_fan_speed -= 1 # indexes from 0 | |
# log.debug(f"before mod: {wanted_fan_speed}") | |
# if wanted_fan_speed % 1.0 >= 0.41: | |
if (wanted_fan_speed_div := wanted_fan_speed % 1.0) >= 0.5: | |
wanted_fan_speed -= wanted_fan_speed_div | |
wanted_fan_speed += 1 | |
# log.debug(f"after mod: {wanted_fan_speed}") | |
wanted_fan_speed = int(round(wanted_fan_speed, 0)) | |
# log.debug(f"after int: {wanted_fan_speed}") | |
if fan_speed_limit is not None: | |
wanted_fan_speed = min(wanted_fan_speed, int(fan_speed_limit)) | |
# log.debug(f"after min: {wanted_fan_speed}") | |
if wanted_fan_speed > len(FAN_MODES) - 1: | |
preset_target = PRESET_MODE_BOOST | |
else: | |
wanted_fan_speed = max(min(wanted_fan_speed, len(FAN_MODES) - 1), 0) | |
# log.debug(f"after max: {wanted_fan_speed}") | |
# log.debug(f"""{ac_friendly_name} | |
# float(len(FAN_MODES)) * (temp_difference or 0.1) / float(boost_temp_difference): | |
# {float(len(FAN_MODES))} * {(temp_difference or 0.1)} / {float(boost_temp_difference)} | |
# wanted_fan_speed: {wanted_fan_speed}/{len(FAN_MODES)} | |
# """) | |
try: | |
wanted_fan_speed = FAN_MODES[wanted_fan_speed] | |
except Exception as e: | |
tools.telegram_message(f"error wanted_fan_speed: {wanted_fan_speed} of {FAN_MODES} {type(e)} {e}") | |
wanted_fan_speed = FAN_MODES[0] | |
if ac_fan_speed != wanted_fan_speed: | |
msgs.add(f'Setting fan speed {ac_fan_speed} to {wanted_fan_speed}/{len(FAN_MODES)}') | |
ac_entity.set_fan_mode(wanted_fan_speed) | |
task.sleep(ac_action_wait) | |
if ac_preset_mode != preset_target: | |
msgs.add(f'Setting preset mode {ac_preset_mode} to {preset_target}') | |
ac_entity.set_preset_mode(preset_target) | |
task.sleep(ac_action_wait) | |
if change_temperature and ac_temperature != target_temperature and temp_difference_abs > 0: | |
msgs.add(f'Setting {ac_friendly_name} temperature {ac_temperature} to ' | |
f'{target_temperature_min}-{target_temperature}-{target_temperature_max}') | |
ac_entity.set_temperature(hvac_mode=wanted_state, temperature=target_temperature, | |
target_temp_high=target_temperature_max, target_temp_low=target_temperature_min) | |
task.sleep(ac_action_wait) | |
if msgs.msgs: | |
msgs.msgs = msgs_init + msgs.msgs | |
msgs.send() | |
else: | |
log.debug(f"{__name__}: nothing to do for {ac_friendly_name}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment