updating configuration
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
import globals
|
||||
import datetime
|
||||
|
||||
#
|
||||
# Purifier controller
|
||||
# Turn on and off purifier by light sensor and timers
|
||||
#
|
||||
# Args:
|
||||
#
|
||||
# None
|
||||
#
|
||||
# Release Notes
|
||||
#
|
||||
# Version 1.0:
|
||||
# Initial Version
|
||||
|
||||
class BreaserControl(hass.Hass):
|
||||
away_fan_speed = 8 #1-14
|
||||
listen_event_handle_list = []
|
||||
mode = 'auto'
|
||||
heat_start_at = '23:00:00'
|
||||
heat_stop_at = '07:00:00'
|
||||
|
||||
def initialize(self):
|
||||
if not globals.check_args(self, ["on_time", "off_time"]):
|
||||
return
|
||||
on_time = self.parse_time(self.args["on_time"])
|
||||
off_time = self.parse_time(self.args["off_time"])
|
||||
|
||||
self.on_timer = self.run_daily(self.day_mode_timer_event, on_time)
|
||||
self.off_timer = self.run_daily(self.night_mode_timer_event, off_time)
|
||||
|
||||
if 'heat_switch' in self.args:
|
||||
self.heat_on_timer = self.run_daily(self.heat_mode_on_timer_event, self.heat_start_at)
|
||||
self.heat_off_timer = self.run_daily(self.heat_mode_off_timer_event, self.heat_stop_at)
|
||||
self.heat_mode_by_time()
|
||||
|
||||
self.listen_event_handle_list.append(self.listen_event(self.away_mode_event, "away_mode"))
|
||||
self.listen_event_handle_list.append(self.listen_event(self.return_home_mode_event, "return_home_mode"))
|
||||
if 'co2_sensor' in self.args:
|
||||
self.listen_event_handle_list.append(self.listen_state(self.state_change, self.args['co2_sensor']))
|
||||
|
||||
def state_change(self, entity, attribute, old, new, kwargs):
|
||||
self.heat_mode_by_time()
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
|
||||
presence_state = self.get_state(self.args['ha_panel'])
|
||||
if presence_state != "disarmed":
|
||||
self.log('Nobody home, ignoring co2 changes.')
|
||||
return
|
||||
if new == 'unknown':
|
||||
return
|
||||
self.log("{} changed from {} to {}".format(attribute, old, new))
|
||||
co2_level = int(new)
|
||||
#day
|
||||
if self.time_is_between(self.datetime(), self.args["on_time"], self.args["off_time"]):
|
||||
self.set_mode_by_co2(co2_level, False)
|
||||
#night
|
||||
elif not self.time_is_between(self.datetime(), self.args["on_time"], self.args["off_time"]):
|
||||
self.set_mode_by_co2(co2_level, True)
|
||||
|
||||
def set_mode_by_co2(self, co2_level, is_night):
|
||||
self.heat_mode_by_time()
|
||||
|
||||
# === Настраиваемые коэффициенты ===
|
||||
CFG = {
|
||||
'co2_auto_threshold': 600, # Ниже этого значения — auto_mode
|
||||
'co2_min_control': 700, # Начало плавного роста скорости
|
||||
'co2_max_control': 1200, # Максимальная скорость достигается здесь
|
||||
'speed_min': 30, # Мин. скорость вентиляции
|
||||
'speed_max': 100, # Макс. скорость вентиляции
|
||||
'night_speed_factor': 0.75, # Коэффициент снижения скорости ночью
|
||||
'transition_smoothness': 0.3, # Плавность перехода (0-1)
|
||||
}
|
||||
|
||||
# === Плавная функция перехода от auto к ручной скорости ===
|
||||
def calculate_speed(co2, cfg, is_night):
|
||||
# Нормализация CO2 в диапазон [0, 1]
|
||||
t = (co2 - cfg['co2_min_control']) / (cfg['co2_max_control'] - cfg['co2_min_control'])
|
||||
t = max(0.0, min(1.0, t))
|
||||
|
||||
# Плавная интерполяция (smoothstep для более мягкого старта)
|
||||
if cfg['transition_smoothness'] > 0:
|
||||
t = t * t * (3 - 2 * t) # Cubic smoothstep
|
||||
|
||||
# Базовая скорость
|
||||
base_speed = cfg['speed_min'] + t * (cfg['speed_max'] - cfg['speed_min'])
|
||||
|
||||
# Ночной коэффициент
|
||||
day_factor = 1.0 if not is_night else cfg['night_speed_factor']
|
||||
final_speed = base_speed * day_factor
|
||||
|
||||
return int(round(max(cfg['speed_min'], min(cfg['speed_max'], final_speed))))
|
||||
|
||||
# === Автоматический режим при низком CO2 ===
|
||||
if co2_level <= CFG['co2_auto_threshold']:
|
||||
self.log('CO2 is excellent: {}. Set auto mode.'.format(co2_level))
|
||||
self.auto_mode()
|
||||
return
|
||||
|
||||
# === Плавный расчёт скорости ===
|
||||
speed = calculate_speed(co2_level, CFG, is_night)
|
||||
|
||||
# Определение уровня для лога
|
||||
if co2_level < CFG['co2_min_control']:
|
||||
level_desc = 'good'
|
||||
elif co2_level < CFG['co2_max_control'] * 0.7:
|
||||
level_desc = 'moderate'
|
||||
elif co2_level < CFG['co2_max_control']:
|
||||
level_desc = 'high'
|
||||
else:
|
||||
level_desc = 'extremely high'
|
||||
|
||||
period = 'Night' if is_night else 'Day'
|
||||
self.log('{}, CO2 is {}: {}. Set speed to {}.'.format(
|
||||
period, level_desc, co2_level, speed))
|
||||
|
||||
self.set_speed(speed)
|
||||
|
||||
# def set_mode_by_co2(self, co2_level, is_night):
|
||||
# self.heat_mode_by_time()
|
||||
# if co2_level < 500:
|
||||
# self.log('CO2 is fine: {}. Set auto mode.'.format(co2_level))
|
||||
# self.auto_mode()
|
||||
# if co2_level > 500 and co2_level < 800:
|
||||
# if is_night:
|
||||
# self.log('CO2 is normal: {}. Set night mode'.format(co2_level))
|
||||
# self.night_mode()
|
||||
# else:
|
||||
# self.log('CO2 is normal: {}. Set auto mode.'.format(co2_level))
|
||||
# self.auto_mode()
|
||||
# return
|
||||
# if co2_level > 800 and co2_level < 900:
|
||||
# if is_night:
|
||||
# self.log('Night, CO2 is high: {}. Set speed to 45.'.format(co2_level))
|
||||
# self.set_speed(45)
|
||||
# else:
|
||||
# self.log('Day, CO2 is high: {}. Set speed to 65.'.format(co2_level))
|
||||
# self.set_speed(65)
|
||||
# return
|
||||
# if co2_level > 900 and co2_level < 1000:
|
||||
# if is_night:
|
||||
# self.log('Night, CO2 is high: {}. Set speed to 65.'.format(co2_level))
|
||||
# self.set_speed(65)
|
||||
# else:
|
||||
# self.log('Day, CO2 is high: {}. Set speed to 80.'.format(co2_level))
|
||||
# self.set_speed(80)
|
||||
# return
|
||||
# if co2_level > 1000 and co2_level < 1200:
|
||||
# if is_night:
|
||||
# self.log('Night, CO2 is extremely high: {}. Set speed to 80.'.format(co2_level))
|
||||
# self.set_speed(80)
|
||||
# else:
|
||||
# self.log('Day, CO2 is extremely high: {}. Set speed to 100.'.format(co2_level))
|
||||
# self.set_speed(100)
|
||||
# return
|
||||
# if co2_level > 1200:
|
||||
# if is_night:
|
||||
# self.log('Night, CO2 is extremely high: {}. Set speed to 100.'.format(co2_level))
|
||||
# self.set_speed(100)
|
||||
# else:
|
||||
# self.log('Day, CO2 is extremely high: {}. Set speed to 100.'.format(co2_level))
|
||||
# self.set_speed(100)
|
||||
# return
|
||||
|
||||
def set_speed(self, speed):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
self.log('Set purifier mode to favorite, speed: {}'.format(speed))
|
||||
self.call_service("fan/set_preset_mode", entity_id = self.args['entity'], preset_mode='Favorite')
|
||||
self.call_service("fan/set_percentage", entity_id = self.args['entity'], percentage=speed)
|
||||
|
||||
def return_home_mode_event(self, event_id, event_args, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
self.auto_mode()
|
||||
|
||||
def away_mode_event(self, event_id, event_args, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
self.turn_on(self.args['entity'])
|
||||
self.away_mode()
|
||||
|
||||
def night_mode_timer_event(self, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
presence_state = self.get_state(self.args['ha_panel'])
|
||||
if presence_state != "disarmed":
|
||||
self.log('Nobody home, ignoring automations.')
|
||||
return
|
||||
self.night_mode()
|
||||
|
||||
def day_mode_timer_event(self, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
presence_state = self.get_state(self.args['ha_panel'])
|
||||
if presence_state != "disarmed":
|
||||
self.log('Nobody home, ignoring automations.')
|
||||
return
|
||||
self.auto_mode()
|
||||
|
||||
def away_mode(self):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
self.mode='away'
|
||||
self.log('Set purifier mode to favorite, speed: {}'.format(self.away_fan_speed))
|
||||
if 'entity' in self.args:
|
||||
self.call_service("fan/set_preset_mode", entity_id = self.args['entity'], preset_mode='Favorite')
|
||||
self.call_service("fan/set_percentage", entity_id = self.args['entity'], percentage=100)
|
||||
|
||||
def auto_mode(self):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
self.mode='auto'
|
||||
self.log('Set purifier mode to auto.')
|
||||
if 'entity' in self.args:
|
||||
self.call_service("fan/set_preset_mode", entity_id = self.args['entity'], preset_mode='Auto')
|
||||
|
||||
def night_mode(self):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
self.mode='night'
|
||||
self.log('Set purifier mode to silent.')
|
||||
if 'entity' in self.args:
|
||||
self.call_service("fan/set_preset_mode", entity_id = self.args['entity'], preset_mode='Sleep')
|
||||
|
||||
#heating control
|
||||
def heat_mode_by_time(self):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint_heat']):
|
||||
return
|
||||
if self.time_is_between(self.datetime(), self.heat_stop_at, self.heat_start_at):
|
||||
self.log('Expensive tariff. Set heat off.')
|
||||
self.heat_mode_off_timer_event(None)
|
||||
else:
|
||||
presence_state = self.get_state(self.args['ha_panel'])
|
||||
if presence_state != "disarmed":
|
||||
self.log('Nobody home, ignoring automations.')
|
||||
return
|
||||
self.log('Cheap tariff. Set heat on.')
|
||||
self.heat_mode_on_timer_event(None)
|
||||
|
||||
def heat_mode_on_timer_event(self, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint_heat']):
|
||||
return
|
||||
presence_state = self.get_state(self.args['ha_panel'])
|
||||
if presence_state != "disarmed":
|
||||
self.log('Nobody home, ignoring automations.')
|
||||
return
|
||||
self.turn_on(self.args['heat_switch'])
|
||||
|
||||
def heat_mode_off_timer_event(self, kwargs):
|
||||
self.turn_off(self.args['heat_switch'])
|
||||
|
||||
|
||||
def time_is_between(
|
||||
hass, target_dt: datetime, start_time: str,
|
||||
end_time: str) -> bool:
|
||||
"""Generalization of AppDaemon's now_is_between method."""
|
||||
start_time_dt = hass.parse_time(start_time) # type: datetime
|
||||
end_time_dt = hass.parse_time(end_time) # type: datetime
|
||||
start_dt = target_dt.replace(
|
||||
hour=start_time_dt.hour,
|
||||
minute=start_time_dt.minute,
|
||||
second=start_time_dt.second)
|
||||
end_dt = target_dt.replace(
|
||||
hour=end_time_dt.hour,
|
||||
minute=end_time_dt.minute,
|
||||
second=end_time_dt.second)
|
||||
|
||||
if end_dt < start_dt:
|
||||
# Spans midnight
|
||||
if target_dt < start_dt and target_dt < end_dt:
|
||||
target_dt = target_dt + timedelta(days=1)
|
||||
end_dt = end_dt + timedelta(days=1)
|
||||
return start_dt <= target_dt <= end_dt
|
||||
@@ -0,0 +1,14 @@
|
||||
breaser_control:
|
||||
module: breaser_control
|
||||
class: BreaserControl
|
||||
on_time: "09:00:00"
|
||||
off_time: "22:00:00"
|
||||
entity: fan.ventiliatsionnaia_sistema
|
||||
ha_panel: alarm_control_panel.ha_alarm
|
||||
co2_sensor: sensor.co2_bedroom_co2
|
||||
heat_switch: switch.ventiliatsionnaia_sistema_auxiliary_heat
|
||||
constraint: input_boolean.vent_control
|
||||
constraint_heat: input_boolean.vent_control_heat
|
||||
global_dependencies:
|
||||
- globals
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
from datetime import time, datetime, timedelta
|
||||
|
||||
class ClimateControl(hass.Hass):
|
||||
def initialize(self):
|
||||
# Настройки из YAML
|
||||
self.thermostat = self.args.get("thermostat", "climate.virtual_thermostat")
|
||||
self.ac = self.args.get("ac", "climate.ac_bedroom")
|
||||
self.radiators = self.args.get("radiators", ["climate.radiator_bedroom", "climate.radiator_bedroom_balcony"])
|
||||
self.radiator_enabled = self.args.get("radiator_enabled", "input_boolean.radiator_enabled")
|
||||
self.presence_sensors = self.args.get("presence_sensors", None)
|
||||
self.no_presence_delay = int(self.args.get("no_presence_delay", 1800)) # секунд
|
||||
self.window_sensors = self.args.get("window_sensors", None)
|
||||
self.window_open_delay = int(self.args.get("window_open_delay", 300)) # секунд
|
||||
self.alarm = self.args.get("alarm", "alarm_control_panel.ha_alarm")
|
||||
|
||||
self.listen_state(self.thermostat_changed, self.thermostat)
|
||||
self.listen_state(self.thermostat_temp_changed, self.thermostat, attribute="temperature")
|
||||
self.listen_state(self.thermostat_mode_changed, self.thermostat, attribute="hvac_mode")
|
||||
if self.presence_sensors is not None:
|
||||
self.listen_state(self.presence_changed, self.presence_sensors)
|
||||
if self.window_sensors is not None:
|
||||
self.listen_state(self.window_changed, self.window_sensors)
|
||||
self.listen_state(self.radiator_enabled_changed, self.radiator_enabled)
|
||||
self.listen_state(self.alarm_changed, self.alarm)
|
||||
self.run_every(self.periodic_check, self.datetime() + timedelta(seconds=5), 60)
|
||||
|
||||
self.no_presence_handle = None
|
||||
self.window_open_handle = None
|
||||
self.last_presence = self.datetime()
|
||||
self.last_window_open = None
|
||||
self.updating_thermostat = False
|
||||
self.listen_state(self.ac_changed, self.ac)
|
||||
self.listen_state(self.ac_temp_changed, self.ac, attribute="temperature")
|
||||
self.auto_off_reason = None
|
||||
|
||||
def thermostat_changed(self, entity, attribute, old, new, kwargs):
|
||||
self.log(f"Термостат изменён: {new}")
|
||||
self.control_climate()
|
||||
|
||||
def thermostat_temp_changed(self, entity, attribute, old, new, kwargs):
|
||||
self.log(f"Изменилась температура термостата: {old} → {new}")
|
||||
self.control_climate()
|
||||
|
||||
def thermostat_mode_changed(self, entity, attribute, old, new, kwargs):
|
||||
self.log(f"Изменился режим термостата: {old} → {new}")
|
||||
self.control_climate()
|
||||
|
||||
def presence_changed(self, entity, attribute, old, new, kwargs):
|
||||
if new == "on":
|
||||
self.last_presence = self.datetime()
|
||||
if self.no_presence_handle:
|
||||
self.cancel_timer(self.no_presence_handle)
|
||||
self.no_presence_handle = None
|
||||
self.control_climate()
|
||||
else:
|
||||
if not self.no_presence_handle:
|
||||
self.no_presence_handle = self.run_in(self.no_presence_timeout, self.no_presence_delay)
|
||||
|
||||
def no_presence_timeout(self, kwargs):
|
||||
self.log("В комнате долго нет людей — отключаю климат.")
|
||||
self.turn_off_all(reason="no_presence")
|
||||
|
||||
def window_changed(self, entity, attribute, old, new, kwargs):
|
||||
if new == "on":
|
||||
if not self.window_open_handle:
|
||||
self.window_open_handle = self.run_in(self.window_open_timeout, self.window_open_delay)
|
||||
else:
|
||||
if self.window_open_handle:
|
||||
self.cancel_timer(self.window_open_handle)
|
||||
self.window_open_handle = None
|
||||
self.auto_off_reason = None # сбросить флаг после закрытия окна
|
||||
self.control_climate()
|
||||
|
||||
def window_open_timeout(self, kwargs):
|
||||
self.log("Окно открыто более 5 минут — отключаю климат.")
|
||||
self.turn_off_all(reason="window")
|
||||
|
||||
def radiator_enabled_changed(self, entity, attribute, old, new, kwargs):
|
||||
self.control_climate()
|
||||
|
||||
def alarm_changed(self, entity, attribute, old, new, kwargs):
|
||||
self.control_climate()
|
||||
|
||||
def periodic_check(self, kwargs):
|
||||
self.control_climate()
|
||||
|
||||
def control_climate(self):
|
||||
self.log(f"Проверка климата: alarm={self.get_state(self.alarm)}, окна={self.window_sensors}, присутствие={self.presence_sensors}")
|
||||
mode = self.get_state(self.thermostat, attribute="hvac_mode")
|
||||
if mode is None:
|
||||
mode = self.get_state(self.thermostat)
|
||||
target_temp = self.get_state(self.thermostat, attribute="temperature")
|
||||
self.log(f"Режим термостата (hvac_mode/state): {mode}, температура: {target_temp}")
|
||||
# Проверка сигнализации
|
||||
if self.get_state(self.alarm) != "disarmed":
|
||||
self.log("Сигнализация активна — автоматика отключена.")
|
||||
self.turn_off_all()
|
||||
return
|
||||
|
||||
# Проверка окон
|
||||
if self.window_sensors:
|
||||
if any(self.get_state(s) == "on" for s in self.window_sensors):
|
||||
self.log("Окно открыто — автоматика отключена.")
|
||||
self.turn_off_all()
|
||||
return
|
||||
|
||||
# Проверка присутствия
|
||||
if self.presence_sensors:
|
||||
if all(self.get_state(s) == "off" for s in self.presence_sensors):
|
||||
if (self.datetime() - self.last_presence).total_seconds() > self.no_presence_delay:
|
||||
self.log("Нет людей — автоматика отключена.")
|
||||
self.turn_off_all()
|
||||
return
|
||||
|
||||
# Проверка режима термостата
|
||||
if mode == "off":
|
||||
self.log("Термостат в off — автоматика отключена.")
|
||||
self.turn_off_all()
|
||||
return
|
||||
elif mode == "cool":
|
||||
self.log("Режим охлаждения.")
|
||||
self.set_ac_mode("cool", target_temp)
|
||||
self.set_radiators_off()
|
||||
return
|
||||
elif mode == "heat":
|
||||
# Определяем сезон
|
||||
radiator_on = self.get_state(self.radiator_enabled) == "on"
|
||||
now = datetime.now().time()
|
||||
self.log(f"radiator_enabled: {radiator_on}, время: {now}")
|
||||
if not radiator_on:
|
||||
self.log("Лето — только сплит-система.")
|
||||
self.set_ac_mode("heat", target_temp)
|
||||
self.set_radiators_off()
|
||||
else:
|
||||
if time(23,0) <= now or now < time(7,0):
|
||||
self.log("Зима, ночь — только сплит-система в режиме обогрева.")
|
||||
self.set_ac_mode("heat", target_temp)
|
||||
self.set_radiators_off()
|
||||
else:
|
||||
self.log("Зима, день — только радиаторы.")
|
||||
self.set_ac_off()
|
||||
self.set_radiators_on(target_temp)
|
||||
|
||||
def set_ac_mode(self, mode, temp):
|
||||
current_mode = self.get_state(self.ac)
|
||||
current_temp = self.get_state(self.ac, attribute="temperature")
|
||||
|
||||
current_temp_f = int(current_temp)
|
||||
temp_f = int(temp)
|
||||
|
||||
self.log(f"Текущий режим AC: {current_mode}, температура: {current_temp_f} (требуется: {temp_f})")
|
||||
if current_mode == mode and isinstance(current_temp_f, int) and isinstance(temp_f, int) and abs(current_temp_f - temp_f) < 0.1:
|
||||
self.log(f"AC {self.ac}: режим {mode}, температура {temp} — уже установлены, не отправляю команду.")
|
||||
return
|
||||
self.updating_thermostat = True
|
||||
self.call_service("climate/set_temperature", entity_id=self.ac, temperature=temp, hvac_mode=mode)
|
||||
self.log(f"AC {self.ac}: режим {mode}, температура {temp}")
|
||||
self.updating_thermostat = False
|
||||
|
||||
def set_ac_off(self):
|
||||
current_mode = self.get_state(self.ac)
|
||||
if current_mode == "off":
|
||||
self.log(f"AC {self.ac}: уже выключен, не отправляю команду.")
|
||||
return
|
||||
self.updating_thermostat = True
|
||||
self.call_service("climate/set_hvac_mode", entity_id=self.ac, hvac_mode="off")
|
||||
self.log(f"AC {self.ac}: выключен")
|
||||
self.updating_thermostat = False
|
||||
|
||||
def set_radiators_on(self, temp):
|
||||
for r in self.radiators:
|
||||
self.updating_thermostat = True
|
||||
self.call_service("climate/set_hvac_mode", entity_id=r, hvac_mode="heat")
|
||||
self.call_service("climate/set_temperature", entity_id=r, temperature=temp)
|
||||
self.log(f"Радиатор {r}: режим heat, температура {temp}")
|
||||
self.updating_thermostat = False
|
||||
|
||||
def set_radiators_off(self):
|
||||
for r in self.radiators:
|
||||
self.updating_thermostat = True
|
||||
self.call_service("climate/set_hvac_mode", entity_id=r, hvac_mode="off")
|
||||
self.log(f"Радиатор {r}: выключен")
|
||||
self.updating_thermostat = False
|
||||
|
||||
def turn_off_all(self, reason=None):
|
||||
self.auto_off_reason = reason
|
||||
self.set_ac_off()
|
||||
self.set_radiators_off()
|
||||
|
||||
def ac_changed(self, entity, attribute, old, new, kwargs):
|
||||
if self.updating_thermostat:
|
||||
return
|
||||
if self.auto_off_reason:
|
||||
self.log(f"AC выключен автоматикой ({self.auto_off_reason}), не синхронизирую термостат в off")
|
||||
return
|
||||
mode = self.get_state(self.ac)
|
||||
temp = self.get_state(self.ac, attribute="temperature")
|
||||
self.log(f"AC изменён вручную: режим {mode}, температура {temp}")
|
||||
# Синхронизируем виртуальный термостат
|
||||
self.updating_thermostat = True
|
||||
self.call_service("climate/set_hvac_mode", entity_id=self.thermostat, hvac_mode=mode)
|
||||
self.call_service("climate/set_temperature", entity_id=self.thermostat, temperature=temp)
|
||||
self.updating_thermostat = False
|
||||
|
||||
def ac_temp_changed(self, entity, attribute, old, new, kwargs):
|
||||
if self.updating_thermostat:
|
||||
return
|
||||
self.log(f"AC температура изменена вручную: {old} → {new}")
|
||||
self.updating_thermostat = True
|
||||
self.call_service("climate/set_temperature", entity_id=self.thermostat, temperature=new)
|
||||
self.updating_thermostat = False
|
||||
@@ -0,0 +1,17 @@
|
||||
climate_control:
|
||||
module: climate_control
|
||||
class: ClimateControl
|
||||
thermostat: climate.bedroom_climate
|
||||
ac: climate.ac_bedroom
|
||||
radiators:
|
||||
- climate.radiator_bedroom
|
||||
- climate.radiator_bedroom_balcony
|
||||
radiator_enabled: input_boolean.radiator_enabled
|
||||
#presence_sensors:
|
||||
# - binary_sensor.presence_bedroom
|
||||
no_presence_delay: 1800 # секунд без присутствия для отключения
|
||||
window_sensors:
|
||||
- binary_sensor.door_bedroom_big_window_contact
|
||||
- binary_sensor.door_balcony_contact
|
||||
window_open_delay: 300 # секунд открытого окна для отключения
|
||||
alarm: alarm_control_panel.ha_alarm
|
||||
@@ -0,0 +1,83 @@
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
|
||||
#
|
||||
# Cover control by presence events
|
||||
# Listen for presence events and change light state
|
||||
#
|
||||
# Args:
|
||||
# entities - entities to turn on and off when presence is changed.
|
||||
# Release Notes
|
||||
#
|
||||
# Version 1.0:
|
||||
# Initial Version
|
||||
|
||||
class CoverByPresence(hass.Hass):
|
||||
def initialize(self):
|
||||
if 'covers' not in self.args:
|
||||
self.error("Please provide covers in config!")
|
||||
return
|
||||
self.listen_event(self.away_mode, "away_mode")
|
||||
self.listen_event(self.return_home_mode, "return_home_mode")
|
||||
|
||||
sunrise_offset = int(self.args.get('sunrise_offset', '0'))
|
||||
self.run_at_sunrise(self.run_at_sunrise_func, offset=sunrise_offset)
|
||||
sunset_offset = int(self.args.get('sunset_offset', '0'))
|
||||
self.run_at_sunset(self.run_at_sunset_func, offset=sunset_offset)
|
||||
|
||||
def run_at_sunrise_func(self, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
self.log('execution is disabled.')
|
||||
return
|
||||
|
||||
if 'guest_mode' in self.args and self.constrain_input_boolean(self.args['guest_mode']):
|
||||
self.log('guest mode, no run at sunrise.')
|
||||
return
|
||||
|
||||
if 'open_at_sunrise' not in self.args:
|
||||
self.log('no covers to open at sunrise.')
|
||||
return
|
||||
|
||||
for entity_id in self.args["open_at_sunrise"]:
|
||||
self.log('opening cover {} at sunrise'.format(entity_id))
|
||||
self.turn_on_custom(entity_id)
|
||||
|
||||
def run_at_sunset_func(self, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
self.log('execution is disabled.')
|
||||
return
|
||||
if 'close_at_sunset' not in self.args:
|
||||
self.log('no covers to close at sunset.')
|
||||
return
|
||||
for entity_id in self.args["close_at_sunset"]:
|
||||
self.log('closing cover {} at sunset'.format(entity_id))
|
||||
self.turn_off_custom(entity_id)
|
||||
|
||||
def away_mode(self, event_id, event_args, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
self.log('execution is disabled.')
|
||||
return
|
||||
for entity_id in self.args["covers"]:
|
||||
self.turn_off_custom(entity_id)
|
||||
|
||||
def return_home_mode(self, event_id, event_args, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
self.log('execution is disabled.')
|
||||
return
|
||||
if self.sun_down():
|
||||
self.log('Sun is down, skip opening covers.')
|
||||
return
|
||||
entities = self.args["covers"]
|
||||
for entity_id in entities:
|
||||
self.turn_on_custom(entity_id)
|
||||
|
||||
def turn_on_custom(self, entity_id):
|
||||
if 'cover.' in entity_id:
|
||||
self.call_service("cover/open_cover", entity_id=entity_id)
|
||||
return
|
||||
self.turn_on(entity_id)
|
||||
|
||||
def turn_off_custom(self, entity_id):
|
||||
if 'cover.' in entity_id:
|
||||
self.call_service("cover/close_cover", entity_id=entity_id)
|
||||
return
|
||||
self.turn_off(entity_id)
|
||||
@@ -0,0 +1,29 @@
|
||||
#################################
|
||||
# HOME PRESENSE #
|
||||
#################################
|
||||
cover_action:
|
||||
module: cover
|
||||
class: CoverByPresence
|
||||
sunrise_offset: 0
|
||||
sunset_offset: 0
|
||||
guest_mode: input_boolean.guest_mode
|
||||
covers:
|
||||
- cover.curtain_bedroom
|
||||
- cover.curtain_bedroom_balcony
|
||||
#- cover.curtain_bedroom_bobbinet_balcony
|
||||
- cover.curtain_kitchen
|
||||
- cover.curtain_mainroom
|
||||
#- cover.curtain_mainroom_bobbinet
|
||||
close_at_sunset:
|
||||
- cover.curtain_bedroom
|
||||
- cover.curtain_bedroom_balcony
|
||||
- cover.curtain_bedroom_bobbinet_balcony
|
||||
- cover.curtain_kitchen
|
||||
- cover.curtain_mainroom
|
||||
- cover.curtain_mainroom_bobbinet
|
||||
open_at_sunrise:
|
||||
- cover.curtain_kitchen
|
||||
- cover.curtain_mainroom
|
||||
global_dependencies:
|
||||
- globals
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import hassapi as hass
|
||||
import datetime
|
||||
import adbase as ad
|
||||
|
||||
|
||||
#
|
||||
# entity: sensor.temperature_bath_humidity
|
||||
# control_entity: switch.switch_mirror_bath_heater
|
||||
# constraint: input_boolean.bath_mirror_heat
|
||||
# threshold: 75
|
||||
|
||||
|
||||
class MirrorHeater(hass.Hass):
|
||||
timer = None
|
||||
entity = None
|
||||
threshold = 75
|
||||
blocked = False
|
||||
|
||||
def initialize(self):
|
||||
self.log('starting initialize')
|
||||
|
||||
sensor = self.args['entity']
|
||||
self.log('listen [{0}] state'.format(sensor))
|
||||
self.listen_state(self.humidity_changed, sensor, duration=10)
|
||||
|
||||
if 'threshold' in self.args:
|
||||
self.threshold = self.args['threshold']
|
||||
self.log('setting threshold to: {}'.format(self.threshold))
|
||||
|
||||
def humidity_changed(self, entity, attribute, old_state, new_state, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
|
||||
self.log('[{0}] [humidity changed] : {1}, isnumeric: {2}, type: {3}'.format(entity, new_state, new_state.isnumeric(), type(new_state)))
|
||||
if float(new_state) > self.threshold:
|
||||
self.log('Control Entity On and running timer...')
|
||||
self.control_entity_on()
|
||||
self.run_timer()
|
||||
else:
|
||||
self.log('Control Entity Off and stopping timer...')
|
||||
self.control_entity_off(None)
|
||||
|
||||
############ TIMER ########################
|
||||
def run_timer(self):
|
||||
if self.timer != None:
|
||||
self.stop_timer()
|
||||
self.timer = self.run_in(self.control_entity_off, self.args['timeout'])
|
||||
|
||||
def control_entity_on(self):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
for entity in self.args['control_entities']:
|
||||
state = self.get_state(entity)
|
||||
if state == 'off':
|
||||
self.turn_on(entity)
|
||||
self.log("Turn on ({}).".format(entity))
|
||||
|
||||
def control_entity_off(self, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
return
|
||||
for entity in self.args['control_entities']:
|
||||
state = self.get_state(entity)
|
||||
if state == 'on':
|
||||
self.turn_off(entity)
|
||||
self.log("Turn off ({}).".format(entity))
|
||||
self.stop_timer()
|
||||
|
||||
def stop_timer(self):
|
||||
if (self.timer == None):
|
||||
return
|
||||
self.log('stopping timer.')
|
||||
self.cancel_timer(self.timer)
|
||||
self.timer = None
|
||||
@@ -0,0 +1,39 @@
|
||||
#################################
|
||||
# Контроль подогрева зеркал #
|
||||
#################################
|
||||
mirror_heater_control:
|
||||
# Название класса
|
||||
class: MirrorHeater
|
||||
module: mirror_heater_control
|
||||
|
||||
entity: sensor.temperature_bath_humidity
|
||||
control_entities:
|
||||
- switch.switch_mirror_bath_heater
|
||||
|
||||
constraint: input_boolean.bath_mirror_heat
|
||||
threshold: 75
|
||||
timeout: 1800 #3600=1hr
|
||||
|
||||
global_dependencies:
|
||||
- globals
|
||||
|
||||
|
||||
# #################################
|
||||
# # Контроль подогрева зеркал #
|
||||
# #################################
|
||||
# mirror_heater_control_shower:
|
||||
# # Название класса
|
||||
# class: MirrorHeater
|
||||
# module: mirror_heater_control
|
||||
|
||||
# entity: sensor.temperature_shower_humidity
|
||||
# control_entities:
|
||||
# - switch.shower_mirror
|
||||
# - switch.switch_mirror_shower_heater
|
||||
|
||||
# constraint: input_boolean.shower_mirror_heat
|
||||
# threshold: 75
|
||||
# timeout: 3600 #3600=1hr
|
||||
|
||||
# global_dependencies:
|
||||
# - globals
|
||||
@@ -0,0 +1,46 @@
|
||||
import hassapi as hass
|
||||
import datetime
|
||||
import adbase as ad
|
||||
|
||||
|
||||
class TRVUpdater(hass.Hass):
|
||||
entities = []
|
||||
|
||||
def initialize(self):
|
||||
self.log('starting initialize')
|
||||
self.entities = self.args['entities']
|
||||
|
||||
for sensor in self.entities.keys():
|
||||
self.log('listen [{0}] state'.format(sensor))
|
||||
self.listen_state(self.temperature_changed, sensor, duration=10)
|
||||
current_temperature = self.get_state(sensor)
|
||||
self.temperature_changed(sensor, 'temperature', '20.0', current_temperature, None)
|
||||
|
||||
def temperature_changed(self, entity, attribute, old_state, new_state, kwargs):
|
||||
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
|
||||
self.log('Отправка температуры отключена в связи с отключением отопления.')
|
||||
return
|
||||
|
||||
self.log('[{0}] [temperature changed] : {1}, isnumeric: {2}, type: {3}'.format(entity, new_state, new_state.isnumeric(), type(new_state)))
|
||||
topic = self.entities[entity].get('topic_prefix', None)
|
||||
if topic is not None:
|
||||
sensor_topic = topic.replace('/sensor_temp', '')
|
||||
sensor_payload = '{"sensor":"external"}'
|
||||
self.log('[mqtt publish] topic: "{0}" value: {1}'.format(sensor_topic, sensor_payload))
|
||||
self.call_service("mqtt/publish", topic=sensor_topic, payload=sensor_payload)
|
||||
|
||||
temperature_payload = '{"external_temperature_input":"' + new_state + '"}'
|
||||
self.log('[mqtt publish] topic: "{0}" value: {1}'.format(sensor_topic, temperature_payload))
|
||||
self.call_service("mqtt/publish", topic=sensor_topic, payload=temperature_payload)
|
||||
|
||||
topics = self.entities[entity].get('topic_prefixes', None)
|
||||
if topics is not None:
|
||||
for topic in topics:
|
||||
sensor_topic = topic.replace('/sensor_temp', '')
|
||||
sensor_payload = '{"sensor":"external"}'
|
||||
self.log('[mqtt publish] topic: "{0}" value: {1}'.format(sensor_topic, sensor_payload))
|
||||
self.call_service("mqtt/publish", topic=sensor_topic, payload=sensor_payload)
|
||||
|
||||
temperature_payload = '{"external_temperature_input":' + new_state + '}'
|
||||
self.log('[mqtt publish] topic: "{0}" value: {1}'.format(sensor_topic, temperature_payload))
|
||||
self.call_service("mqtt/publish", topic=sensor_topic, payload=temperature_payload)
|
||||
@@ -0,0 +1,22 @@
|
||||
#################################
|
||||
# Обновление температуры на термоголовках #
|
||||
#################################
|
||||
trv_updater:
|
||||
# Название класса
|
||||
class: TRVUpdater
|
||||
module: trv_updater
|
||||
constraint: input_boolean.radiator_enabled
|
||||
|
||||
entities:
|
||||
'sensor.temperature_mainroom_temperature':
|
||||
topic_prefix: z2m-tcp/radiator_mainroom/set/sensor_temp
|
||||
'sensor.temperature_bedroom_temperature':
|
||||
topic_prefixes:
|
||||
- z2m-tcp/radiator_bedroom/set/sensor_temp
|
||||
- z2m-tcp/radiator_bedroom_balcony/set/sensor_temp
|
||||
'sensor.temperature_kitchen_temperature':
|
||||
topic_prefix: z2m-tcp/radiator_kitchen/set/sensor_temp
|
||||
'sensor.temperature_childroom_temperature':
|
||||
topic_prefix: z2m-tcp/radiator_childroom/set/sensor_temp
|
||||
global_dependencies:
|
||||
- globals
|
||||
Reference in New Issue
Block a user