updating configuration

This commit is contained in:
Your Name
2026-07-14 14:12:07 +00:00
parent 456f43d435
commit 6199c62362
83 changed files with 552 additions and 364 deletions
@@ -1,5 +1,6 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import os import os
import globals
# #
# App to send notification when doorbell is ringing # App to send notification when doorbell is ringing
@@ -26,10 +27,9 @@ class Doorbell(hass.Hass):
def ha_event(self, event_name, data, kwargs): def ha_event(self, event_name, data, kwargs):
self.log('{}:{}'.format(event_name, data)) self.log('{}:{}'.format(event_name, data))
self.notify("Звонок в дверь!!!", name = self.args['notify']) globals.send_telegram(self, "Звонок в дверь!!!", target = self.args['notify'])
if 'doorbell_pic' in data: if 'doorbell_pic' in data:
extra_data = {'photo': {'url':data['doorbell_pic']}} globals.send_telegram_photo(self, data['doorbell_pic'], target = self.args['notify'], caption = "Фото звонившего")
self.notify("Фото звонившего", name = self.args['notify'], data=extra_data)
self.say('Звонок в дверь') self.say('Звонок в дверь')
@@ -38,6 +38,6 @@ class Doorbell(hass.Hass):
def say(self, command): def say(self, command):
if 'alices' in self.args: if 'alices' in self.args:
for alice in self.args['alices']: for alice in self.args['alices']:
self.log('command = {}'.format(command)) self.log('command = {}'.format(command))
self.call_service('media_player/play_media', entity_id=alice, media_content_type='text', media_content_id=command, extra={'volume_level': 0.8}) self.call_service('media_player/play_media', entity_id=alice, media_content_type='text', media_content_id=command, extra={'volume_level': 0.8})
+52 -21
View File
@@ -2,22 +2,59 @@ import importlib
import subprocess import subprocess
from typing import Any, Tuple, Union from typing import Any, Tuple, Union
#
# Global module
# contain global functions
#
# Args:
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
"""Define various constants.""" """Define various constants."""
CONF_PEOPLE = 'people' CONF_PEOPLE = 'people'
# --- Telegram notifications -------------------------------------------------
#
# HA 2026.2 deprecated the per-platform notify.telegram* services in favour
# of entity-based targeting (see repairs.issue_registry: telegram/migrate_notify).
# The generic notify.send_message service only supports message/title, so it
# can't carry parse_mode/photo/keyboard payloads our apps rely on - instead we
# target the telegram_bot integration's own (non-deprecated) domain services
# (telegram_bot.send_message / telegram_bot.send_photo) with entity_id, which
# is the current 1:1 replacement for the old chat_id-based notify.telegram*.
#
# Entity ids come from .storage/core.entity_registry (platform: telegram_bot),
# mapped from the friendly names ('telegram', 'telegram_monster',
# 'telegram_sveta') used throughout apps.yaml configs.
TELEGRAM_TARGETS = {
'telegram': 'notify.home_assistant_fonvizina_fonvizina_18_1001567589226',
'telegram_monster': 'notify.home_assistant_fonvizina_monster_123622180',
'telegram_sveta': 'notify.home_assistant_fonvizina_svetlana_205225134',
}
def send_telegram(self, message, target='telegram', parse_mode='html', **kwargs):
"""Send a Telegram text message via telegram_bot.send_message.
Replacement for the deprecated `self.notify(message, name=target)`.
`target` is a friendly name from TELEGRAM_TARGETS (falls back to the
default group chat if unknown). Extra kwargs (title, keyboard, etc.) are
passed straight through to the service call.
"""
entity_id = TELEGRAM_TARGETS.get(target, TELEGRAM_TARGETS['telegram'])
service_data = {'entity_id': entity_id, 'message': message}
if parse_mode:
service_data['parse_mode'] = parse_mode
service_data.update(kwargs)
self.call_service('telegram_bot/send_message', **service_data)
def send_telegram_photo(self, url, target='telegram', caption=None, parse_mode='html', **kwargs):
"""Send a photo via telegram_bot.send_photo.
Replacement for the deprecated `self.notify(msg, name=target,
data={'photo': {'url': url}})`.
"""
entity_id = TELEGRAM_TARGETS.get(target, TELEGRAM_TARGETS['telegram'])
service_data = {'entity_id': entity_id, 'url': url}
if caption:
service_data['caption'] = caption
if parse_mode:
service_data['parse_mode'] = parse_mode
service_data.update(kwargs)
self.call_service('telegram_bot/send_photo', **service_data)
# -----------------------------------------------------------------------------
def most_common(the_list: list) -> Any: def most_common(the_list: list) -> Any:
"""Return the most common element in a list.""" """Return the most common element in a list."""
return max(set(the_list), key=the_list.count) return max(set(the_list), key=the_list.count)
@@ -57,12 +94,8 @@ def get_group_entities(self, group):
return [] return []
if 'attributes' in attributes: if 'attributes' in attributes:
attributes = attributes['attributes'] attributes = attributes['attributes']
#group
if "entity_id" in attributes: if "entity_id" in attributes:
return attributes["entity_id"] return attributes["entity_id"]
#simple item
return [group] return [group]
def turn_on(self, entity_id, state): def turn_on(self, entity_id, state):
@@ -78,14 +111,12 @@ def turn_off(self, entity_id, state):
self.turn_off(entity_id, state) self.turn_off(entity_id, state)
def notification(self, to, message): def notification(self, to, message):
self.notify(message, name = "telegram") send_telegram(self, message, target = "telegram")
def get_arg(args, key): def get_arg(args, key):
key = args[key] key = args[key]
return key return key
def get_arg_list(args, key): def get_arg_list(args, key):
arg_list = [] arg_list = []
if isinstance(args[key], list): if isinstance(args[key], list):
@@ -94,4 +125,4 @@ def get_arg_list(args, key):
arg = (args[key]).split(",") arg = (args[key]).split(",")
for key in arg: for key in arg:
arg_list.append(key) arg_list.append(key)
return arg_list return arg_list
+7 -7
View File
@@ -1,4 +1,5 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import globals
# #
# Aqara lock notifications # Aqara lock notifications
@@ -7,7 +8,7 @@ import appdaemon.plugins.hass.hassapi as hass
# Args: # Args:
# lock_state_entity - entity of aqara lock state # lock_state_entity - entity of aqara lock state
# notify - notify entity to send message # notify - notify entity to send message
# #
# Release Notes # Release Notes
# #
# Version 1.0: # Version 1.0:
@@ -18,17 +19,17 @@ class AqaraLock(hass.Hass):
if 'lock_state_entity' not in self.args or 'notify' not in self.args: if 'lock_state_entity' not in self.args or 'notify' not in self.args:
self.error("Please provide lock_state_entity and notify in config!") self.error("Please provide lock_state_entity and notify in config!")
return return
lock_state_entity = self.args['lock_state_entity'] lock_state_entity = self.args['lock_state_entity']
self.log(' [+] listening for lock_state_entity sensor {}'.format(lock_state_entity)) self.log(' [+] listening for lock_state_entity sensor {}'.format(lock_state_entity))
self.listen_state(self.sensor_trigger, lock_state_entity) self.listen_state(self.sensor_trigger, lock_state_entity)
def sensor_trigger(self, entity, attribute, old, new, kwargs): def sensor_trigger(self, entity, attribute, old, new, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return return
#(old != "" and old != "unknown") or #(old != "" and old != "unknown") or
if new == "unknown" or new == "" or new == "unavailable": if new == "unknown" or new == "" or new == "unavailable":
self.log('skiping event. {} changed from {} to {}'.format(entity, old, new)) self.log('skiping event. {} changed from {} to {}'.format(entity, old, new))
return return
@@ -36,7 +37,7 @@ class AqaraLock(hass.Hass):
action = new action = new
self.process_event(action) self.process_event(action)
def process_event(self, eventname): def process_event(self, eventname):
if eventname == "ring_bell": if eventname == "ring_bell":
self.fire_event('doorbell_call') self.fire_event('doorbell_call')
@@ -51,5 +52,4 @@ class AqaraLock(hass.Hass):
self.notify_tg("Неизвестное событие замка: {}".format(eventname)) self.notify_tg("Неизвестное событие замка: {}".format(eventname))
def notify_tg(self, msg): def notify_tg(self, msg):
extra_data = {'parse_mode': 'html'} globals.send_telegram(self, msg, target = self.args['notify'])
self.notify(msg, name = self.args['notify'], data=extra_data)
+3 -3
View File
@@ -1,4 +1,5 @@
import datetime import datetime
import globals
from automation import Automation, Base # type: ignore from automation import Automation, Base # type: ignore
# #
@@ -21,7 +22,7 @@ class Balance(Base):
super().initialize() super().initialize()
self.check_balance({"force": 1}) self.check_balance({"force": 1})
self.run_daily(self.check_balance, datetime.time(10, 0, 0)) self.run_daily(self.check_balance, datetime.time(10, 0, 0))
def check_balance(self, kwargs): def check_balance(self, kwargs):
# if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): # if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
# return # return
@@ -61,5 +62,4 @@ class Balance(Base):
def notify_tg(self, msg): def notify_tg(self, msg):
if not 'notify' in self.args: if not 'notify' in self.args:
return return
extra_data = {'parse_mode': 'html'} globals.send_telegram(self, msg, target = self.args['notify'])
self.notify(msg, name = self.args['notify'], data=extra_data)
+66 -65
View File
@@ -1,65 +1,66 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import datetime import datetime
import globals
#
# App to send email report for devices running low on battery #
# # App to send email report for devices running low on battery
# Args: #
# # Args:
# threshold = value below which battery levels are reported and email is sent #
# always_send = set to 1 to override threshold and force send # threshold = value below which battery levels are reported and email is sent
# # always_send = set to 1 to override threshold and force send
# None #
# # None
# Release Notes #
# # Release Notes
# Version 1.0: #
# Initial Version # Version 1.0:
# Initial Version
class Battery(hass.Hass):
class Battery(hass.Hass):
def initialize(self):
#self.check_batteries({"force": 1}) def initialize(self):
time = datetime.time(10, 0, 0) #self.check_batteries({"force": 1})
self.run_daily(self.check_batteries, time) time = datetime.time(10, 0, 0)
self.check_batteries(None) self.run_daily(self.check_batteries, time)
self.check_batteries(None)
def check_batteries(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): def check_batteries(self, kwargs):
return if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
devices = self.get_state()
values = {} devices = self.get_state()
low = [] values = {}
for deviceName in devices: low = []
device = devices[deviceName] for deviceName in devices:
if device == None: device = devices[deviceName]
continue if device == None:
battery = None continue
if "attributes" in device: battery = None
if "battery" in device["attributes"]: if "attributes" in device:
battery = device["attributes"]["battery"] if "battery" in device["attributes"]:
elif "battery_level" in device["attributes"]: battery = device["attributes"]["battery"]
battery = device["attributes"]["battery_level"] elif "battery_level" in device["attributes"]:
elif '_battery' in device['entity_id']: battery = device["attributes"]["battery_level"]
battery = self.get_state(device['entity_id']) elif '_battery' in device['entity_id']:
if not battery.isdigit(): battery = self.get_state(device['entity_id'])
continue if not battery.isdigit():
battery = int(battery) continue
print('{} = {}'.format(device['entity_id'], battery)) battery = int(battery)
if battery != None: print('{} = {}'.format(device['entity_id'], battery))
print('{} = {}'.format(device['entity_id'], battery)) if battery != None:
if battery < self.args["threshold"] and battery != 0: print('{} = {}'.format(device['entity_id'], battery))
low.append(deviceName) if battery < self.args["threshold"] and battery != 0:
values[deviceName] = battery low.append(deviceName)
values[deviceName] = battery
message=""
if low: message=""
message += "У следующих датчиков садится батарея: (< {}) \n".format(self.args["threshold"]) if low:
for device in low: message += "У следующих датчиков садится батарея: (< {}) \n".format(self.args["threshold"])
message += "{}: {}%\n".format(self.friendly_name(device), values[device]) for device in low:
message += "\n\n" message += "{}: {}%\n".format(self.friendly_name(device), values[device])
message += "\n\n"
if low: # or ("always_send" in self.args and self.args["always_send"] == 1) or ("force" in kwargs and kwargs["force"] == 1):
if message != "": if low: # or ("always_send" in self.args and self.args["always_send"] == 1) or ("force" in kwargs and kwargs["force"] == 1):
self.notify(message, name = "telegram") if message != "":
globals.send_telegram(self, message)
@@ -1,5 +1,6 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import datetime import datetime
import globals
class BMSMonitor(hass.Hass): class BMSMonitor(hass.Hass):
def initialize(self): def initialize(self):
@@ -17,7 +18,7 @@ class BMSMonitor(hass.Hass):
self.current_state = "IDLE" # IDLE, CHARGING, DISCHARGING self.current_state = "IDLE" # IDLE, CHARGING, DISCHARGING
self.timer_handle = None self.timer_handle = None
self.was_charging = False self.was_charging = False
# Переменная для хранения времени начала отключения # Переменная для хранения времени начала отключения
self.outage_start_time = None self.outage_start_time = None
@@ -45,15 +46,15 @@ class BMSMonitor(hass.Hass):
# Логика переходов состояний # Логика переходов состояний
if self.current_state != new_state: if self.current_state != new_state:
self.log(f"Смена состояния BMS: {self.current_state} -> {new_state}") self.log(f"Смена состояния BMS: {self.current_state} -> {new_state}")
# 1. Переход в разрядку (Отключили питание) # 1. Переход в разрядку (Отключили питание)
if new_state == "DISCHARGING" and self.current_state != "DISCHARGING": if new_state == "DISCHARGING" and self.current_state != "DISCHARGING":
# Фиксируем время начала отключения # Фиксируем время начала отключения
self.outage_start_time = datetime.datetime.now() self.outage_start_time = datetime.datetime.now()
self.send_telegram("⚠️ <b>ВНИМАНИЕ: Отключено питание!</b>\nБатарея перешла в режим разрядки.", "html") self.send_telegram("⚠️ <b>ВНИМАНИЕ: Отключено питание!</b>\nБатарея перешла в режим разрядки.", "html")
self.start_discharge_timer() self.start_discharge_timer()
# 2. Выход из разрядки (Питание восстановлено) # 2. Выход из разрядки (Питание восстановлено)
# Срабатывает при переходе DISCHARGING -> IDLE или DISCHARGING -> CHARGING # Срабатывает при переходе DISCHARGING -> IDLE или DISCHARGING -> CHARGING
elif self.current_state == "DISCHARGING" and new_state in ("IDLE", "CHARGING"): elif self.current_state == "DISCHARGING" and new_state in ("IDLE", "CHARGING"):
@@ -62,14 +63,14 @@ class BMSMonitor(hass.Hass):
duration = datetime.datetime.now() - self.outage_start_time duration = datetime.datetime.now() - self.outage_start_time
duration_msg = f"\n⏱ <b>Время без электричества: {self._format_timedelta(duration)}</b>" duration_msg = f"\n⏱ <b>Время без электричества: {self._format_timedelta(duration)}</b>"
self.outage_start_time = None # Сбрасываем таймер self.outage_start_time = None # Сбрасываем таймер
self.send_telegram(f"✅ <b>Питание восстановлено!</b>\nБатарея вышла из режима разрядки.{duration_msg}", "html") self.send_telegram(f"✅ <b>Питание восстановлено!</b>\nБатарея вышла из режима разрядки.{duration_msg}", "html")
self.stop_discharge_timer() self.stop_discharge_timer()
# 3. Переход в простой после зарядки (Зарядка окончена) # 3. Переход в простой после зарядки (Зарядка окончена)
elif new_state == "IDLE" and self.current_state == "CHARGING": elif new_state == "IDLE" and self.current_state == "CHARGING":
self.send_telegram("🔋 <b>Зарядка завершена!</b>\nБатарея полностью заряжена или нагрузка отключена.", "html") self.send_telegram("🔋 <b>Зарядка завершена!</b>\nБатарея полностью заряжена или нагрузка отключена.", "html")
# 4. Переход IDLE -> CHARGING (если не было разрядки перед этим) # 4. Переход IDLE -> CHARGING (если не было разрядки перед этим)
elif new_state == "CHARGING" and self.current_state == "IDLE": elif new_state == "CHARGING" and self.current_state == "IDLE":
# Можно добавить отдельное уведомление, если нужно # Можно добавить отдельное уведомление, если нужно
@@ -84,14 +85,14 @@ class BMSMonitor(hass.Hass):
soc = float(new) soc = float(new)
if soc >= 100 and self.current_state == "CHARGING" and not self.was_charging: if soc >= 100 and self.current_state == "CHARGING" and not self.was_charging:
# Небольшая защита от повторных уведомлений # Небольшая защита от повторных уведомлений
self.was_charging = True self.was_charging = True
except (ValueError, TypeError): except (ValueError, TypeError):
pass pass
def start_discharge_timer(self): def start_discharge_timer(self):
if self.timer_handle is None: if self.timer_handle is None:
# Запускаем таймер: сразу первое сообщение, потом каждые N секунд # Запускаем таймер: сразу первое сообщение, потом каждые N секунд
self.send_status_report() self.send_status_report()
self.timer_handle = self.run_every(self.periodic_status, datetime.datetime.now(), self.interval) self.timer_handle = self.run_every(self.periodic_status, datetime.datetime.now(), self.interval)
self.log("Таймер статуса разрядки запущен") self.log("Таймер статуса разрядки запущен")
@@ -113,12 +114,12 @@ class BMSMonitor(hass.Hass):
total_seconds = int(td.total_seconds()) total_seconds = int(td.total_seconds())
hours, remainder = divmod(total_seconds, 3600) hours, remainder = divmod(total_seconds, 3600)
minutes, _ = divmod(remainder, 60) minutes, _ = divmod(remainder, 60)
parts = [] parts = []
if hours > 0: if hours > 0:
parts.append(f"{hours} ч") parts.append(f"{hours} ч")
parts.append(f"{minutes} мин") parts.append(f"{minutes} мин")
return " ".join(parts) return " ".join(parts)
def send_status_report(self): def send_status_report(self):
@@ -134,16 +135,16 @@ class BMSMonitor(hass.Hass):
soc = round(float(soc_raw), 1) if soc_raw not in (None, 'unknown', 'unavailable') else 0 soc = round(float(soc_raw), 1) if soc_raw not in (None, 'unknown', 'unavailable') else 0
current = round(float(current_raw), 2) if current_raw not in (None, 'unknown', 'unavailable') else 0 current = round(float(current_raw), 2) if current_raw not in (None, 'unknown', 'unavailable') else 0
voltage = round(float(voltage_raw), 2) if voltage_raw not in (None, 'unknown', 'unavailable') else 0 voltage = round(float(voltage_raw), 2) if voltage_raw not in (None, 'unknown', 'unavailable') else 0
# Определяем цвет/иконку для направления тока # Определяем цвет/иконку для направления тока
direction = "🔻 Разряд" if power < 0 else "🔺 Заряд" direction = "🔻 Разряд" if power < 0 else "🔺 Заряд"
# Добавляем время без электричества, если известно # Добавляем время без электричества, если известно
outage_info = "" outage_info = ""
if self.outage_start_time: if self.outage_start_time:
duration = datetime.datetime.now() - self.outage_start_time duration = datetime.datetime.now() - self.outage_start_time
outage_info = f"\n⏳ Без света: <b>{self._format_timedelta(duration)}</b>" outage_info = f"\n⏳ Без света: <b>{self._format_timedelta(duration)}</b>"
msg = ( msg = (
f"📊 <b>Статус батареи (JK BMS)</b>\n\n" f"📊 <b>Статус батареи (JK BMS)</b>\n\n"
f"🔋 SOC: <b>{soc}%</b>\n" f"🔋 SOC: <b>{soc}%</b>\n"
@@ -159,7 +160,6 @@ class BMSMonitor(hass.Hass):
def send_telegram(self, message, parse_mode="html"): def send_telegram(self, message, parse_mode="html"):
try: try:
extra_data = {'parse_mode': parse_mode} globals.send_telegram(self, message, target = self.notification_target, parse_mode = parse_mode)
self.notify(message, name = self.notification_target, data=extra_data)
except Exception as e: except Exception as e:
self.log(f"Ошибка отправки Telegram: {e}") self.log(f"Ошибка отправки Telegram: {e}")
+45 -30
View File
@@ -1,6 +1,4 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
from datetime import timedelta
import datetime
# #
# App to show dishwasher state # App to show dishwasher state
@@ -18,58 +16,75 @@ import datetime
class Dishwasher(hass.Hass): class Dishwasher(hass.Hass):
listen_event_handle_list = [] listen_event_handle_list = []
run_state = ""
def initialize(self): def initialize(self):
if 'run_state' not in self.args or 'door_sensor' not in self.args or 'counter' not in self.args: if 'run_state' not in self.args or 'door_sensor' not in self.args or 'counter' not in self.args or 'state_extended' not in self.args:
self.log("Please define all args") self.log("Please define all args")
return return
self.listen_event_handle_list.append(self.listen_state(self.door_state_change, self.args['door_sensor'])) #, attribute='action' self.listen_event_handle_list.append(self.listen_state(self.door_state_change, self.args['door_sensor']))
self.listen_event_handle_list.append(self.listen_state(self.run_state_change, self.args['run_state'])) #, attribute='action' self.listen_event_handle_list.append(self.listen_state(self.run_state_change, self.args['run_state']))
def get_extended_state(self):
return self.get_state(self.args['state_extended'])
def set_extended_state(self, state):
self.call_service("input_select/select_option", entity_id = self.args['state_extended'], option = state)
def door_state_change(self, entity, attribute, old, new, kwargs): def door_state_change(self, entity, attribute, old, new, kwargs):
if (new == "on"): if new != "on":
self.call_service("counter/increment", entity_id = self.args['counter']) return
if (self.run_state == "finished"):
self.run_state = "collecting" self.call_service("counter/increment", entity_id = self.args['counter'])
self.set_light_state_by_counter() extended_state = self.get_extended_state()
open_count = int(self.get_state(self.args['counter']))
if extended_state == "Washed":
self.set_extended_state("Empty")
elif extended_state == "Empty":
if open_count < 10:
self.set_extended_state("Collecting Dishes")
elif open_count > 10:
self.set_extended_state("Ready to wash")
elif extended_state == "Collecting Dishes" and open_count > 10:
self.set_extended_state("Ready to wash")
self.set_light_state_by_counter()
def run_state_change(self, entity, attribute, old, new, kwargs): def run_state_change(self, entity, attribute, old, new, kwargs):
self.log("event: {}: {}->{}".format(attribute,old,new)) self.log("event: {}: {}->{}".format(attribute, old, new))
if (new == "Run"): if new == "Run":
self.log('resetting counter') self.log('resetting counter')
self.call_service("counter/reset", entity_id = self.args['counter']) self.call_service("counter/reset", entity_id = self.args['counter'])
self.run_state = "washing" self.set_extended_state("Washing")
if (new == "Finished"): elif new == "Finished":
self.log('resetting counter') self.log('resetting counter')
self.call_service("counter/reset", entity_id = self.args['counter']) self.call_service("counter/reset", entity_id = self.args['counter'])
self.run_state = "finished" self.set_extended_state("Washed")
self.log('Current state is: {}'.format(self.run_state)) self.log('Current state is: {}'.format(self.get_extended_state()))
self.set_light_state_by_counter() self.set_light_state_by_counter()
def set_light_state_by_counter(self): def set_light_state_by_counter(self):
state = self.get_state(self.args['counter']) open_count = self.get_state(self.args['counter'])
open_count = int(state) extended_state = self.get_extended_state()
self.log('counter state={}, machine state = {}'.format(state, self.run_state)) self.log('counter state={}, machine state = {}'.format(open_count, extended_state))
if (self.run_state == "finished"): if extended_state == "Washed":
# Есть посуда, которую нужно разобрать
# red color
self.log('Time to take dishes out.') self.log('Time to take dishes out.')
if 'lamp' in self.args: if 'lamp' in self.args:
self.call_service("light/turn_on", entity_id = self.args['lamp'], rgb_color=[255, 0, 0]) #red self.call_service("light/turn_on", entity_id = self.args['lamp'], rgb_color=[255, 0, 0])
elif (self.run_state == "washing"): elif extended_state == "Washing":
self.log('washing in progress') self.log('washing in progress')
self.call_service("light/turn_off", entity_id = self.args['lamp']) if 'lamp' in self.args:
self.call_service("light/turn_off", entity_id = self.args['lamp'])
elif (open_count > 10): elif extended_state == "Ready to wash":
self.log('Time to run dishwasher.') self.log('Time to run dishwasher.')
if 'lamp' in self.args: if 'lamp' in self.args:
self.call_service("light/turn_on", entity_id = self.args['lamp'], rgb_color=[245, 236, 0]) #yellow self.call_service("light/turn_on", entity_id = self.args['lamp'], rgb_color=[245, 236, 0])
else: elif 'lamp' in self.args:
self.call_service("light/turn_off", entity_id = self.args['lamp']) self.call_service("light/turn_off", entity_id = self.args['lamp'])
@@ -5,9 +5,10 @@ dishwasher:
class: Dishwasher class: Dishwasher
module: dishwasher module: dishwasher
constraint: input_boolean.dishwasher_state_light constraint: input_boolean.dishwasher_state_light
counter: counter.dishwasher_door_open_count counter: counter.bosch_dishwasher_door_open_count
door_sensor: binary_sensor.hc_dishwasher_door door_sensor: binary_sensor.bosch_dishwasher_door
run_state: sensor.hc_dishwasher_state run_state: sensor.bosch_dishwasher_operation_state
state_extended: input_select.bosch_dishwasher_state_extended
lamp: light.espresense_kitchen_led_1 lamp: light.espresense_kitchen_led_1
global_dependencies: global_dependencies:
- globals - globals
@@ -0,0 +1,90 @@
import appdaemon.plugins.hass.hassapi as hass
import random
#
# App to remind about dishwasher when someone enters the kitchen
#
# Args:
#
# state_extended = input_select with extended dishwasher state
# motion_sensors = list of motion sensors to watch
# alice = Yandex Station media_player entity
# cooldown (optional) = seconds between reminders, default 1800
# constraint (optional) = input_boolean to enable/disable the app
#
# Release Notes
#
# Version 1.0:
# Initial Version
# Version 1.1:
# Added "Ready to wash" reminders
class DishwasherReminder(hass.Hass):
PHRASES_WASHED = [
"Посудомойка готова! Пора разобрать посуду.",
"Чистая посуда сидит в посудомойке как в Open Space — пора выпустить на волю.",
"Посудомойка отработала, теперь твоя очередь — разгрузить.",
"Тарелки уже высохли и скучают, спаси их из посудомойки.",
"Посудомойка не склад для чистого, разгрузи уже.",
"Грязной посуды нет, а чистая застряла внутри — спасательная операция!",
"Посудомойка помыла всё, осталось только разобрать. Ну же!",
"Чистые тарелки устроили вечеринку в посудомойке — пора их разогнать.",
"Не забудь разобрать посудомойку, иначе завтра некуда будет класть грязное.",
"Посудомойка ждёт разгрузки — всё помыто, высушено и нетерпеливо дребезжит.",
]
PHRASES_READY_TO_WASH = [
"Посудомойка скучает без работы — пора её запустить.",
"Гора посуды накопилась, посудомойка просит смелости нажать пуск.",
"Тарелки в посудомойке собрались на митинг — жми старт и отпусти их мыться.",
"Посудомойка готова к бою, осталось только нажать кнопку.",
"Хватит кормить посудомойку тарелками — пора включить и отдохнуть.",
"Посудомойка смотрит на тебя с надеждой — запускай программу.",
"Грязная посуда накопилась как снежный ком — время пустить посудомойку в ход.",
"Твои тарелки ждут водных процедур — включай посудомойку!",
"Посудомойка обидится, если её ещё раз оставят без дела — жми пуск.",
"Посуда застряла в посудомойке без мытья — самое время нажать старт и уйти пить чай.",
]
STATE_PHRASES = {
"Washed": PHRASES_WASHED,
"Ready to wash": PHRASES_READY_TO_WASH,
}
def initialize(self):
if 'state_extended' not in self.args or 'motion_sensors' not in self.args or 'alice' not in self.args:
self.log("Please define state_extended, motion_sensors and alice")
return
self.cooldown = self.args.get('cooldown', 1800)
self.last_announced = {}
for sensor in self.args['motion_sensors']:
self.listen_state(self.motion_detected, sensor)
def motion_detected(self, entity, attribute, old, new, kwargs):
if new != "on" or old == "on":
return
# if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
# self.log('disabled by constraint')
# return
state = self.get_state(self.args['state_extended'])
phrases = self.STATE_PHRASES.get(state)
if phrases is None:
return
last = self.last_announced.get(state)
if last is not None:
elapsed = (self.datetime() - last).total_seconds()
if elapsed < self.cooldown:
self.log('cooldown active for {}, skipping'.format(state))
return
self.say(random.choice(phrases))
self.last_announced[state] = self.datetime()
def say(self, command):
self.log('command = {}'.format(command))
self.call_service('media_player/play_media', entity_id=self.args['alice'], media_content_type='text', media_content_id=command)
@@ -0,0 +1,15 @@
#################################
# DISHWASHER REMINDER #
#################################
dishwasher_reminder:
class: DishwasherReminder
module: dishwasher_reminder
# constraint: input_boolean.dishwasher_reminder
state_extended: input_select.bosch_dishwasher_state_extended
motion_sensors:
- binary_sensor.motion_kitchenset_left_occupancy
- binary_sensor.motion_kitchenset_right_occupancy
alice: media_player.yandex_station_m00r46000m53db
cooldown: 3600
global_dependencies:
- globals
+29 -28
View File
@@ -1,28 +1,29 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import datetime import datetime
import globals
#
# App to send reports about unsuccessfull auths #
# # App to send reports about unsuccessfull auths
# Args: #
# # Args:
# threshold = value below which battery levels are reported and email is sent #
# always_send = set to 1 to override threshold and force send # threshold = value below which battery levels are reported and email is sent
# # always_send = set to 1 to override threshold and force send
# None #
# # None
# Release Notes #
# # Release Notes
# Version 1.0: #
# Initial Version # Version 1.0:
# Initial Version
class Security(hass.Hass):
def initialize(self): class Security(hass.Hass):
self.log('---------------------') def initialize(self):
self.listen_event_handle_list = [] self.log('---------------------')
self.listen_event_handle_list.append(self.listen_state(self.state_change, 'persistent_notification.http_login', attribute='message')) self.listen_event_handle_list = []
self.listen_event_handle_list.append(self.listen_state(self.state_change, 'persistent_notification.http_login', attribute='message'))
def state_change(self, entity, attribute, old, new, kwargs):
notifier = self.args.get('notify', 'telegram') def state_change(self, entity, attribute, old, new, kwargs):
self.notify(new, name = notifier) notifier = self.args.get('notify', 'telegram')
self.log(new) globals.send_telegram(self, new, target = notifier)
self.log(new)
@@ -1,6 +1,7 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
from datetime import timedelta from datetime import timedelta
import datetime import datetime
import globals
# #
# App to send email report for devices running low on battery # App to send email report for devices running low on battery
@@ -24,10 +25,10 @@ class Teeth(hass.Hass):
# Устанавливаем сброс в 10 утра # Устанавливаем сброс в 10 утра
reset_time = datetime.time(10, 0, 0) reset_time = datetime.time(10, 0, 0)
self.run_daily(self.reset_action, reset_time) self.run_daily(self.reset_action, reset_time)
# Запускаем проверку напоминаний # Запускаем проверку напоминаний
self.timer = self.run_every(self.remind_if_needed, self.datetime()+timedelta(seconds=1), self.args['remind_interval']) self.timer = self.run_every(self.remind_if_needed, self.datetime()+timedelta(seconds=1), self.args['remind_interval'])
# Слушаем изменения состояния кнопки # Слушаем изменения состояния кнопки
self.listen_event_handle_list.append(self.listen_state(self.button_state_change, self.args['mute_button'])) self.listen_event_handle_list.append(self.listen_state(self.button_state_change, self.args['mute_button']))
@@ -78,5 +79,4 @@ class Teeth(hass.Hass):
notify = self.args["notify"] notify = self.args["notify"]
now = datetime.datetime.now()+timedelta(hours=3) now = datetime.datetime.now()+timedelta(hours=3)
message="Уже {}, а ты еще не почистил зубы. Самое время =)".format(now.strftime("%H:%M")) message="Уже {}, а ты еще не почистил зубы. Самое время =)".format(now.strftime("%H:%M"))
self.notify(message, name = notify) globals.send_telegram(self, message, target = notify)
@@ -24,7 +24,7 @@ class WaterCountersAlarm(hass.Hass):
return return
self.checkup_threasold = self.args['checkup_threasold'] self.checkup_threasold = self.args['checkup_threasold']
self.send_threasold = self.args['send_threasold'] self.send_threasold = self.args['send_threasold']
self.check_send_dates({"force": 1}) self.check_send_dates({"force": 1})
self.run_daily(self.check_checkup_dates, datetime.time(10, 0, 0)) self.run_daily(self.check_checkup_dates, datetime.time(10, 0, 0))
self.run_daily(self.check_send_dates, datetime.time(10, 1, 0)) self.run_daily(self.check_send_dates, datetime.time(10, 1, 0))
@@ -69,8 +69,8 @@ class WaterCountersAlarm(hass.Hass):
if send_devices or ("always_send" in self.args and self.args["always_send"] == 1) or ("force" in kwargs and kwargs["force"] == 1): if send_devices or ("always_send" in self.args and self.args["always_send"] == 1) or ("force" in kwargs and kwargs["force"] == 1):
if message != "": if message != "":
self.notify(message, name = "telegram") globals.send_telegram(self, message)
def check_checkup_dates(self, kwargs): def check_checkup_dates(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return return
@@ -78,7 +78,7 @@ class WaterCountersAlarm(hass.Hass):
entities = self.get_state() entities = self.get_state()
if entities == None: if entities == None:
return return
values = {} values = {}
checkup_devices = [] checkup_devices = []
for entity_id in entities: for entity_id in entities:
@@ -106,4 +106,4 @@ class WaterCountersAlarm(hass.Hass):
if checkup_devices or ("always_send" in self.args and self.args["always_send"] == 1) or ("force" in kwargs and kwargs["force"] == 1): if checkup_devices or ("always_send" in self.args and self.args["always_send"] == 1) or ("force" in kwargs and kwargs["force"] == 1):
if message != "": if message != "":
self.notify(message, name = "telegram") globals.send_telegram(self, message)
+4 -3
View File
@@ -1,10 +1,11 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import globals
# #
# Notification system # Notification system
# #
# Args: # Args:
# #
# Release Notes # Release Notes
# #
# Version 1.0: # Version 1.0:
@@ -42,7 +43,7 @@ class TelegramNotifyTarget(BaseNotifyTarget):
target = self.target_sveta target = self.target_sveta
hass.log('notifying over telegram person {} with message: {}'.format(target, message)) hass.log('notifying over telegram person {} with message: {}'.format(target, message))
hass.notify(message, name=target) globals.send_telegram(hass, message, target=target)
class Notification(hass.Hass): class Notification(hass.Hass):
home_targets = [AliceNotifyTarget()] home_targets = [AliceNotifyTarget()]
@@ -60,4 +61,4 @@ class Notification(hass.Hass):
notifyTarget = self.home_targets[0] notifyTarget = self.home_targets[0]
self.log('message: {}'.format(message)) self.log('message: {}'.format(message))
notifyTarget.notify(self, message, person=target) notifyTarget.notify(self, message, person=target)
@@ -4,11 +4,11 @@ import globals
# #
# Home and away report # Home and away report
# Send report about home state after presence_actions was done. # Send report about home state after presence_actions was done.
# #
# Args: # Args:
# entity - group of entities to send state report # entity - group of entities to send state report
# notify - notify entity to send message # notify - notify entity to send message
# #
# Release Notes # Release Notes
# #
# Version 1.0: # Version 1.0:
@@ -32,7 +32,7 @@ class ReportByPresence(hass.Hass):
def return_home_mode(self, event_id, event_args, kwargs): def return_home_mode(self, event_id, event_args, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return return
self.notify("Добро пожаловать домой!", name = self.args['notify']) globals.send_telegram(self, "Добро пожаловать домой!", target = self.args['notify'])
############# AWAY REPORT ########################## ############# AWAY REPORT ##########################
def send_report(self, kwargs): def send_report(self, kwargs):
@@ -46,6 +46,6 @@ class ReportByPresence(hass.Hass):
message += '{} ({}) = {}\n'.format(self.friendly_name(entity), entity, self.get_state(entity)) message += '{} ({}) = {}\n'.format(self.friendly_name(entity), entity, self.get_state(entity))
if any: if any:
message = "В доме остались работать следующие устройства:\n" + message message = "В доме остались работать следующие устройства:\n" + message
self.notify(message, name = self.args['notify']) globals.send_telegram(self, message, target = self.args['notify'])
else: else:
self.notify("Все устройства в доме выключены.", name = self.args['notify']) globals.send_telegram(self, "Все устройства в доме выключены.", target = self.args['notify'])
@@ -1,15 +1,16 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import datetime import datetime
import globals
# #
# Water control application. # Water control application.
# Controls water valve when all leave and no water needed devices is working. # Controls water valve when all leave and no water needed devices is working.
# #
# Args: # Args:
# water_valve = water valve entity # water_valve = water valve entity
# notify - notify entity to send message # notify - notify entity to send message
# constraint (optional) = input_boolean to enable\disable this automation # constraint (optional) = input_boolean to enable\disable this automation
# #
# Release Notes # Release Notes
# #
# Version 1.0: # Version 1.0:
@@ -56,11 +57,11 @@ class WaterValveControl(hass.Hass):
if name is None: if name is None:
name = device name = device
device_names += name device_names += name
self.notify('В доме остались требующие воды устройства ({}), подача воды не будет отключена.'.format(device_names), name = self.args['notify']) globals.send_telegram(self, 'В доме остались требующие воды устройства ({}), подача воды не будет отключена.'.format(device_names), target = self.args['notify'])
self.cancel_current_timer() self.cancel_current_timer()
self.timers.append(self.run_every(self.wait_when_device_is_done, self.datetime()+datetime.timedelta(seconds=5), 5*60)) self.timers.append(self.run_every(self.wait_when_device_is_done, self.datetime()+datetime.timedelta(seconds=5), 5*60))
else: else:
self.notify('Отключаю подачу воды.', name = self.args['notify']) globals.send_telegram(self, 'Отключаю подачу воды.', target = self.args['notify'])
self.turn_off(self.args['water_valve']) self.turn_off(self.args['water_valve'])
# for device in self.args['water_devices']: # for device in self.args['water_devices']:
# self.turn_off(device) # self.turn_off(device)
@@ -77,7 +78,7 @@ class WaterValveControl(hass.Hass):
self.current_wait_cycle = self.current_wait_cycle+1 self.current_wait_cycle = self.current_wait_cycle+1
elif len(powered_on) == 0 and self.current_wait_cycle>=self.wait_cycles: elif len(powered_on) == 0 and self.current_wait_cycle>=self.wait_cycles:
self.current_wait_cycle = 0 self.current_wait_cycle = 0
self.notify('Устройства требующие воды закончили свою работу, отключаю подачу воды.', name = self.args['notify']) globals.send_telegram(self, 'Устройства требующие воды закончили свою работу, отключаю подачу воды.', target = self.args['notify'])
self.turn_off_water_devices() self.turn_off_water_devices()
self.cancel_current_timer() self.cancel_current_timer()
@@ -9,6 +9,6 @@ water_action:
constraint: input_boolean.water_control constraint: input_boolean.water_control
water_devices: water_devices:
- binary_sensor.washer_running # стиралка - binary_sensor.washer_running # стиралка
- binary_sensor.hc_dishwasher_running # посудомойка - binary_sensor.bosch_dishwasher_running # посудомойка
global_dependencies: global_dependencies:
- globals - globals
+47 -49
View File
@@ -1,5 +1,6 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import requests import requests
import globals
from datetime import datetime from datetime import datetime
class TelegramInternetControl(hass.Hass): class TelegramInternetControl(hass.Hass):
@@ -7,50 +8,50 @@ class TelegramInternetControl(hass.Hass):
Управление интернетом через Telegram команды. Управление интернетом через Telegram команды.
Поддерживает множественные MAC-адреса. Поддерживает множественные MAC-адреса.
""" """
def initialize(self): def initialize(self):
""" """
Инициализация приложения. Инициализация приложения.
""" """
self.log("Telegram Internet Control initialized") self.log("Telegram Internet Control initialized")
# Конфигурация из apps.yaml # Конфигурация из apps.yaml
self.api_url = self.args.get("api_url", "http://localhost:8000/run") self.api_url = self.args.get("api_url", "http://localhost:8000/run")
self.api_user = self.args.get("api_user", "api_user") self.api_user = self.args.get("api_user", "api_user")
self.api_password = self.args.get("api_password", "strong_password") self.api_password = self.args.get("api_password", "strong_password")
# Команды Telegram # Команды Telegram
self.block_command = self.args.get("block_command", "Коля наказан").lower() self.block_command = self.args.get("block_command", "Коля наказан").lower()
self.unblock_command = self.args.get("unblock_command", "Включи Коле интернет").lower() self.unblock_command = self.args.get("unblock_command", "Включи Коле интернет").lower()
# MAC адреса для управления (массив) # MAC адреса для управления (массив)
self.target_macs = self.args.get("target_macs", []) self.target_macs = self.args.get("target_macs", [])
if isinstance(self.target_macs, str): if isinstance(self.target_macs, str):
self.target_macs = [self.target_macs] self.target_macs = [self.target_macs]
# Разрешенные user_id (безопасность) # Разрешенные user_id (безопасность)
self.allowed_users = self.args.get("allowed_users", []) self.allowed_users = self.args.get("allowed_users", [])
if isinstance(self.allowed_users, int): if isinstance(self.allowed_users, int):
self.allowed_users = [self.allowed_users] self.allowed_users = [self.allowed_users]
# Entity для прослушивания событий Telegram # Entity для прослушивания событий Telegram
self.telegram_event_entity = self.args.get( self.telegram_event_entity = self.args.get(
"telegram_event_entity", "telegram_event_entity",
"event.home_assistant_fonvizina_update_event" "event.home_assistant_fonvizina_update_event"
) )
# Сервис для отправки ответов в Telegram # Цель (friendly name из globals.TELEGRAM_TARGETS) для отправки ответов в Telegram
self.notify_service = self.args.get("notify_service", "notify/telegram") self.notify_target = self.args.get("notify_target", "telegram")
# Сохраняем последнее обработанное время (для пропуска дублей) # Сохраняем последнее обработанное время (для пропуска дублей)
self.last_processed_time = None self.last_processed_time = None
# Слушаем изменения состояния события Telegram # Слушаем изменения состояния события Telegram
self.listen_state( self.listen_state(
self.on_telegram_event, self.on_telegram_event,
self.telegram_event_entity self.telegram_event_entity
) )
self.log(f"Listening to: {self.telegram_event_entity}") self.log(f"Listening to: {self.telegram_event_entity}")
self.log(f"Block command: '{self.block_command}'") self.log(f"Block command: '{self.block_command}'")
self.log(f"Unblock command: '{self.unblock_command}'") self.log(f"Unblock command: '{self.unblock_command}'")
@@ -78,80 +79,80 @@ class TelegramInternetControl(hass.Hass):
try: try:
# Получаем полный объект состояния с атрибутами # Получаем полный объект состояния с атрибутами
state_data = self.get_state(entity, attribute="all") state_data = self.get_state(entity, attribute="all")
if state_data is None: if state_data is None:
self.log("State data is None") self.log("State data is None")
return return
# Получаем атрибуты # Получаем атрибуты
attributes = state_data.get("attributes", {}) attributes = state_data.get("attributes", {})
if not attributes: if not attributes:
self.log("No attributes in state data") self.log("No attributes in state data")
return return
# Проверяем тип события (должно быть telegram_text) # Проверяем тип события (должно быть telegram_text)
event_type = attributes.get("event_type", "") event_type = attributes.get("event_type", "")
if event_type != "telegram_text": if event_type != "telegram_text":
return return
# Получаем текст сообщения # Получаем текст сообщения
text = attributes.get("text", "").strip() text = attributes.get("text", "").strip()
if not text: if not text:
return return
# Получаем user_id отправителя # Получаем user_id отправителя
user_id = attributes.get("user_id") user_id = attributes.get("user_id")
from_first = attributes.get("from_first", "Unknown") from_first = attributes.get("from_first", "Unknown")
# Получаем время события для защиты от дублей # Получаем время события для защиты от дублей
event_time = attributes.get("date", "") event_time = attributes.get("date", "")
# Пропускаем если уже обработали это событие # Пропускаем если уже обработали это событие
if event_time == self.last_processed_time: if event_time == self.last_processed_time:
self.log(f"Skipping duplicate event: {event_time}") self.log(f"Skipping duplicate event: {event_time}")
return return
self.last_processed_time = event_time self.last_processed_time = event_time
self.log(f"Telegram message from {from_first} (user_id: {user_id}): '{text}'") self.log(f"Telegram message from {from_first} (user_id: {user_id}): '{text}'")
# Проверяем, разрешен ли пользователь # Проверяем, разрешен ли пользователь
if user_id not in self.allowed_users: if user_id not in self.allowed_users:
self.log(f"User {user_id} not in allowed list. Ignoring.") self.log(f"User {user_id} not in allowed list. Ignoring.")
return return
# Обрабатываем команды (регистронезависимо) # Обрабатываем команды (регистронезависимо)
text_lower = text.lower() text_lower = text.lower()
if text_lower == self.block_command: if text_lower == self.block_command:
self.handle_block_command(from_first) self.handle_block_command(from_first)
elif text_lower == self.unblock_command: elif text_lower == self.unblock_command:
self.handle_unblock_command(from_first) self.handle_unblock_command(from_first)
except Exception as e: except Exception as e:
self.log(f"Error processing Telegram event: {str(e)}") self.log(f"Error processing Telegram event: {str(e)}")
self.log(f"State data: {state_data if 'state_data' in locals() else 'N/A'}") self.log(f"State data: {state_data if 'state_data' in locals() else 'N/A'}")
def handle_block_command(self, user_name): def handle_block_command(self, user_name):
""" """
Блокировка интернета для всех MAC-адресов. Блокировка интернета для всех MAC-адресов.
""" """
self.log(f"Blocking internet for {user_name}") self.log(f"Blocking internet for {user_name}")
if not self.target_macs: if not self.target_macs:
self.send_telegram_message(f"❌ {user_name}, не настроены MAC-адреса для блокировки!") self.send_telegram_message(f"❌ {user_name}, не настроены MAC-адреса для блокировки!")
self.log("No MAC addresses configured") self.log("No MAC addresses configured")
return return
success_count = 0 success_count = 0
failed_macs = [] failed_macs = []
for mac in self.target_macs: for mac in self.target_macs:
# Формируем команду для роутера # Формируем команду для роутера
command = f"ip hotspot host {mac} schedule schedule1" command = f"ip hotspot host {mac} schedule schedule1"
self.log(f"Sending command: {command}") self.log(f"Sending command: {command}")
if self.send_to_router(command): if self.send_to_router(command):
success_count += 1 success_count += 1
else: else:
@@ -159,7 +160,7 @@ class TelegramInternetControl(hass.Hass):
command = "system configuration save" command = "system configuration save"
self.log(f"Sending command: {command}") self.log(f"Sending command: {command}")
cmd = self.send_to_router(command) cmd = self.send_to_router(command)
# Формируем отчет # Формируем отчет
if success_count == len(self.target_macs): if success_count == len(self.target_macs):
self.send_telegram_message( self.send_telegram_message(
@@ -175,21 +176,21 @@ class TelegramInternetControl(hass.Hass):
else: else:
self.send_telegram_message(f"❌ {user_name}, ошибка при блокировке интернета!") self.send_telegram_message(f"❌ {user_name}, ошибка при блокировке интернета!")
self.log("Failed to block internet") self.log("Failed to block internet")
def handle_unblock_command(self, user_name): def handle_unblock_command(self, user_name):
""" """
Разблокировка интернета для всех MAC-адресов. Разблокировка интернета для всех MAC-адресов.
""" """
self.log(f"Unblocking internet for {user_name}") self.log(f"Unblocking internet for {user_name}")
if not self.target_macs: if not self.target_macs:
self.send_telegram_message(f"❌ {user_name}, не настроены MAC-адреса для разблокировки!") self.send_telegram_message(f"❌ {user_name}, не настроены MAC-адреса для разблокировки!")
self.log("No MAC addresses configured") self.log("No MAC addresses configured")
return return
success_count = 0 success_count = 0
failed_macs = [] failed_macs = []
for mac in self.target_macs: for mac in self.target_macs:
# Формируем команду для роутера # Формируем команду для роутера
command = f"ip hotspot host {mac} schedule schedule0" command = f"ip hotspot host {mac} schedule schedule0"
@@ -203,7 +204,7 @@ class TelegramInternetControl(hass.Hass):
command = "system configuration save" command = "system configuration save"
self.log(f"Sending command: {command}") self.log(f"Sending command: {command}")
cmd = self.send_to_router(command) cmd = self.send_to_router(command)
# Формируем отчет # Формируем отчет
if success_count == len(self.target_macs): if success_count == len(self.target_macs):
self.send_telegram_message( self.send_telegram_message(
@@ -219,7 +220,7 @@ class TelegramInternetControl(hass.Hass):
else: else:
self.send_telegram_message(f"❌ {user_name}, ошибка при включении интернета!") self.send_telegram_message(f"❌ {user_name}, ошибка при включении интернета!")
self.log("Failed to unblock internet") self.log("Failed to unblock internet")
def send_to_router(self, command): def send_to_router(self, command):
""" """
Отправка команды на роутер через API. Отправка команды на роутер через API.
@@ -231,11 +232,11 @@ class TelegramInternetControl(hass.Hass):
auth=(self.api_user, self.api_password), auth=(self.api_user, self.api_password),
timeout=10 timeout=10
) )
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
exit_code = result.get("exit_code", -1) exit_code = result.get("exit_code", -1)
if exit_code == 0: if exit_code == 0:
return True return True
else: else:
@@ -247,20 +248,17 @@ class TelegramInternetControl(hass.Hass):
self.log(f"API request failed with status: {response.status_code}") self.log(f"API request failed with status: {response.status_code}")
self.log(f"Response: {response.text}") self.log(f"Response: {response.text}")
return False return False
except Exception as e: except Exception as e:
self.log(f"Error sending command to router: {str(e)}") self.log(f"Error sending command to router: {str(e)}")
return False return False
def send_telegram_message(self, message): def send_telegram_message(self, message):
""" """
Отправка ответа в Telegram. Отправка ответа в Telegram.
""" """
try: try:
self.call_service( globals.send_telegram(self, message, target = self.notify_target)
self.notify_service,
message=message
)
self.log(f"Telegram message sent: {message}") self.log(f"Telegram message sent: {message}")
except Exception as e: except Exception as e:
self.log(f"Error sending Telegram message: {str(e)}") self.log(f"Error sending Telegram message: {str(e)}")
+10 -8
View File
@@ -1,27 +1,29 @@
keenetic: keenetic:
module: keenetic module: keenetic
class: TelegramInternetControl class: TelegramInternetControl
# URL вашего API (адрес контейнера с API) # URL вашего API (адрес контейнера с API)
api_url: "http://192.168.1.6:8001/run" api_url: "http://192.168.1.6:8001/run"
api_user: !secret router_username api_user: !secret router_username
api_password: !secret router_password api_password: !secret router_password
# Команды Telegram (регистронезависимые) # Команды Telegram (регистронезависимые)
block_command: "Коля наказан" block_command: "Коля наказан"
unblock_command: "Включи Коле интернет" unblock_command: "Включи Коле интернет"
# MAC адреса для управления (массив) # MAC адреса для управления (массив)
target_macs: target_macs:
- "cc:4b:73:04:ed:34" - "cc:4b:73:04:ed:34"
- "b8:41:d9:d8:3f:58" - "b8:41:d9:d8:3f:58"
# Разрешенные user_id из Telegram (безопасность!) # Разрешенные user_id из Telegram (безопасность!)
allowed_users: allowed_users:
- 123622180 - 123622180
# Entity для прослушивания событий # Entity для прослушивания событий
telegram_event_entity: "event.home_assistant_fonvizina_update_event" telegram_event_entity: "event.home_assistant_fonvizina_update_event"
# Сервис для отправки уведомлений в Telegram # Цель отправки уведомлений (friendly name из globals.TELEGRAM_TARGETS,
notify_service: "notify/telegram" # см. appdaemon/settings/apps/globals.py). Раньше здесь был deprecated
# notify_service: "notify/telegram".
notify_target: "telegram"
@@ -1,5 +1,6 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import fnmatch import fnmatch
import globals
# #
# Fire sensors alarm notification # Fire sensors alarm notification
@@ -10,7 +11,7 @@ import fnmatch
# sensor - gorup of sensors to listen # sensor - gorup of sensors to listen
# ha_panel - alarm panel entity # ha_panel - alarm panel entity
# notify - notify entity to send message # notify - notify entity to send message
# #
# Release Notes # Release Notes
# #
# Version 1.0: # Version 1.0:
@@ -21,7 +22,7 @@ class FireSensorsReport(hass.Hass):
if 'sensor_parts' not in self.args or 'notify' not in self.args: if 'sensor_parts' not in self.args or 'notify' not in self.args:
self.error("Please provide sensor_parts, ha_panel and notify in config!") self.error("Please provide sensor_parts, ha_panel and notify in config!")
return return
sensor_parts = self.args['sensor_parts'] sensor_parts = self.args['sensor_parts']
all_sensors = self.get_state() all_sensors = self.get_state()
@@ -29,7 +30,7 @@ class FireSensorsReport(hass.Hass):
for part in sensor_parts: for part in sensor_parts:
if fnmatch.fnmatch(entity_id, part): if fnmatch.fnmatch(entity_id, part):
self.log(' [+] listening for security sensor {}'.format(entity_id)) self.log(' [+] listening for security sensor {}'.format(entity_id))
self.listen_state(self.sensor_trigger, entity_id) self.listen_state(self.sensor_trigger, entity_id)
def sensor_trigger(self, entity, attribute, old, new, kwargs): def sensor_trigger(self, entity, attribute, old, new, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
@@ -45,4 +46,4 @@ class FireSensorsReport(hass.Hass):
friendly_name = self.friendly_name(entity) friendly_name = self.friendly_name(entity)
entity = entity.replace('_', '-') entity = entity.replace('_', '-')
message = "Сработал датчик пожарной сигнализации {} ({}) = {}-{}!".format(friendly_name, entity, old, new) message = "Сработал датчик пожарной сигнализации {} ({}) = {}-{}!".format(friendly_name, entity, old, new)
self.notify(message, name = self.args['notify']) globals.send_telegram(self, message, target = self.args['notify'])
@@ -1,5 +1,6 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import fnmatch import fnmatch
import globals
# #
# Security sensors alarm notification # Security sensors alarm notification
@@ -10,7 +11,7 @@ import fnmatch
# sensor - gorup of sensors to listen # sensor - gorup of sensors to listen
# ha_panel - alarm panel entity # ha_panel - alarm panel entity
# notify - notify entity to send message # notify - notify entity to send message
# #
# Release Notes # Release Notes
# #
# Version 1.0: # Version 1.0:
@@ -21,7 +22,7 @@ class SecuritySensorsReport(hass.Hass):
if 'sensor_parts' not in self.args or 'ha_panel' not in self.args or 'notify' not in self.args: if 'sensor_parts' not in self.args or 'ha_panel' not in self.args or 'notify' not in self.args:
self.error("Please provide sensor_parts, ha_panel and notify in config!") self.error("Please provide sensor_parts, ha_panel and notify in config!")
return return
sensor_parts = self.args['sensor_parts'] sensor_parts = self.args['sensor_parts']
all_sensors = self.get_state() all_sensors = self.get_state()
@@ -29,7 +30,7 @@ class SecuritySensorsReport(hass.Hass):
for part in sensor_parts: for part in sensor_parts:
if fnmatch.fnmatch(entity_id, part): if fnmatch.fnmatch(entity_id, part):
self.log(' [+] listening for security sensor {}'.format(entity_id)) self.log(' [+] listening for security sensor {}'.format(entity_id))
self.listen_state(self.sensor_trigger, entity_id) self.listen_state(self.sensor_trigger, entity_id)
def sensor_trigger(self, entity, attribute, old, new, kwargs): def sensor_trigger(self, entity, attribute, old, new, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
@@ -56,4 +57,4 @@ class SecuritySensorsReport(hass.Hass):
friendly_name = self.friendly_name(entity) friendly_name = self.friendly_name(entity)
entity = entity.replace('_', '-') entity = entity.replace('_', '-')
message = "В ваше отсутствие сработал датчик безопастности {} ({}) = {}->{}!".format(friendly_name, entity, old, new) message = "В ваше отсутствие сработал датчик безопастности {} ({}) = {}->{}!".format(friendly_name, entity, old, new)
self.notify(message, name = self.args['notify']) globals.send_telegram(self, message, target = self.args['notify'])
@@ -1,4 +1,5 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import globals
# #
# Water leak detector # Water leak detector
@@ -54,9 +55,9 @@ class WaterLeak(hass.Hass):
self.call_service("mqtt/publish", topic = 'home/watercontrol/bath/valve/set', payload = 'close') self.call_service("mqtt/publish", topic = 'home/watercontrol/bath/valve/set', payload = 'close')
message = "ВНИМАНИЕ! В ваше отстутствие сработал датчик протечки воды ({})!!! Выключаю подачу воды.".format(self.friendly_name(entity_id)) message = "ВНИМАНИЕ! В ваше отстутствие сработал датчик протечки воды ({})!!! Выключаю подачу воды.".format(self.friendly_name(entity_id))
self.notify(message, name = self.args['notify']) globals.send_telegram(self, message, target = self.args['notify'])
message = "Состояние датчиков протечки:\n" message = "Состояние датчиков протечки:\n"
for entity_id in self.sensors: for entity_id in self.sensors:
message += '{} ({}) = {}\n'.format(self.friendly_name(entity_id), entity_id, self.get_state(entity_id)) message += '{} ({}) = {}\n'.format(self.friendly_name(entity_id), entity_id, self.get_state(entity_id))
self.notify(message, name = self.args['notify']) globals.send_telegram(self, message, target = self.args['notify'])
@@ -97,7 +97,7 @@ class EpdBalanceSensor(hass.Hass):
doc = BeautifulSoup(r.content, 'html.parser') doc = BeautifulSoup(r.content, 'html.parser')
address = doc.find("div", string="Вы собственник по адресу:").parent.find_all('div')[1].text address = doc.find("div", string="Вы собственник по адресу:").parent.find_all('div')[1].text
result['address'] = address result['address'] = address
service_to_pay = doc.find('div', string="ЖКУ, к оплате:").parent.find_all('div')[1].text.replace(' Р.', '') service_to_pay = doc.find('div', string="ЖКУ, к оплате:").parent.find_all('div')[1].text.replace(' Р.', '')
result['service_to_pay'] = service_to_pay result['service_to_pay'] = service_to_pay
@@ -167,5 +167,4 @@ class EpdBalanceSensor(hass.Hass):
def notify_tg(self, msg): def notify_tg(self, msg):
if not 'notify' in self.args: if not 'notify' in self.args:
return return
extra_data = {'parse_mode': 'html'} globals.send_telegram(self, msg, target = self.args['notify'])
self.notify(msg, name = self.args['notify'], data=extra_data)
+11 -8
View File
@@ -7,14 +7,17 @@ Arguments:
- events: List of events to monitor - events: List of events to monitor
""" """
class Test(hass.Hass): class Test(hass.Hass):
notify = None # notify = None
def initialize(self) -> None: def initialize(self) -> None:
self.listen_state(self.sensor_trigger, 'sensor.0x00158d0003a361d8_action') self.log("hello")
self.listen_state(self.sensor_trigger, 'binary_sensor.0x00158d0003f26d61_occupancy') # notifier = self.args.get("notify", "telegram")
# self.listen_state(self.sensor_trigger, 'sensor.remote20_action') # self.notify("hello world", name = "telegram")
# self.listen_state(self.sensor_trigger, "sensor.0x00158d0003a361d8_action")
# self.listen_state(self.sensor_trigger, "binary_sensor.0x00158d0003f26d61_occupancy")
# self.listen_state(self.sensor_trigger, "sensor.remote20_action")
def sensor_trigger(self, entity, attribute, old, new, kwargs): # def sensor_trigger(self, entity, attribute, old, new, kwargs):
if new != "" and new != "unknown": # if new != "" and new != "unknown":
self.log("doorbell call") # self.log("doorbell call")
self.toggle("switch.0x00158d00020f3a29_switch") # self.toggle("switch.0x00158d00020f3a29_switch")
+20 -18
View File
@@ -32,7 +32,7 @@ services:
max-size: 50m max-size: 50m
hass: hass:
image: "homeassistant/home-assistant:stable" #stable image: "homeassistant/home-assistant:stable" #2026.4.2
#image: ghcr.io/home-assistant/home-assistant:stable #image: ghcr.io/home-assistant/home-assistant:stable
container_name: hass container_name: hass
volumes: volumes:
@@ -121,10 +121,12 @@ services:
volumes: volumes:
- ./zigbee2mqtt/settings:/app/data - ./zigbee2mqtt/settings:/app/data
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080"] # в образе нет curl (Alpine), используем wget --spider
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 6 retries: 6
start_period: 60s
restart: always restart: always
logging: logging:
driver: json-file driver: json-file
@@ -184,22 +186,22 @@ services:
max-size: "10m" max-size: "10m"
max-file: "5" max-file: "5"
homeconnect2mqtt: #homeconnect2mqtt:
build: # build:
context: ./homeconnect2mqtt/docker # context: ./homeconnect2mqtt/docker
dockerfile: Dockerfile # dockerfile: Dockerfile
restart: always # restart: always
container_name: homeconnect2mqtt # container_name: homeconnect2mqtt
ports: # ports:
- "5001:5000" # - "5001:5000"
env_file: ./homeconnect2mqtt/settings/homeconnect2mqtt.env # env_file: ./homeconnect2mqtt/settings/homeconnect2mqtt.env
volumes: # volumes:
- ./homeconnect2mqtt/settings/config.json:/app/config.json:ro # - ./homeconnect2mqtt/settings/config.json:/app/config.json:ro
logging: # logging:
driver: json-file # driver: json-file
options: # options:
max-size: "10m" # max-size: "10m"
max-file: "5" # max-file: "5"
matter-bridge: matter-bridge:
image: ghcr.io/t0bst4r/home-assistant-matter-hub:latest image: ghcr.io/t0bst4r/home-assistant-matter-hub:latest
+1 -1
View File
@@ -1 +1 @@
2026.4.2 2026.7.2
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+1 -1
View File
@@ -1 +1 @@
{"pid": 238780, "version": 1, "ha_version": "2026.4.2", "start_ts": 1781684834.86627} {"pid": 71, "version": 1, "ha_version": "2026.7.2", "start_ts": 1784034083.5601983}
+48 -38
View File
@@ -21,55 +21,65 @@
# {{ states('sensor.dishwasher_program_progress') + "%" }} # {{ states('sensor.dishwasher_program_progress') + "%" }}
# {% endif %} # {% endif %}
mqtt: # mqtt:
sensor: # sensor:
- name: hc_dishwasher_state # - name: hc_dishwasher_state
unique_id: hc_dishwasher_state # unique_id: hc_dishwasher_state
state_topic: "homeconnect/dishwasher/state" # state_topic: "homeconnect/dishwasher/state"
value_template: "{{ value_json.state }}" # value_template: "{{ value_json.state }}"
json_attributes_topic: "homeconnect/dishwasher/state" # json_attributes_topic: "homeconnect/dishwasher/state"
- name: hc_dishwasher_remaining # - name: hc_dishwasher_remaining
unique_id: hc_dishwasher_remaining # unique_id: hc_dishwasher_remaining
state_topic: "homeconnect/dishwasher/state" # state_topic: "homeconnect/dishwasher/state"
value_template: "{{ value_json.remaining }}" # value_template: "{{ value_json.remaining }}"
json_attributes_topic: "homeconnect/dishwasher/state" # json_attributes_topic: "homeconnect/dishwasher/state"
binary_sensor: # binary_sensor:
- state_topic: "homeconnect/dishwasher/state" # - state_topic: "homeconnect/dishwasher/state"
name: hc_dishwasher_door # name: hc_dishwasher_door
unique_id: hc_dishwasher_door # unique_id: hc_dishwasher_door
device_class: opening # device_class: opening
payload_on: "Open" # payload_on: "Open"
payload_off: "Closed" # payload_off: "Closed"
value_template: "{{ value_json.door }}" # value_template: "{{ value_json.door }}"
json_attributes_topic: "homeconnect/dishwasher/state" # json_attributes_topic: "homeconnect/dishwasher/state"
- state_topic: "homeconnect/dishwasher/state" # - state_topic: "homeconnect/dishwasher/state"
name: hc_dishwasher_power # name: hc_dishwasher_power
unique_id: hc_dishwasher_power # unique_id: hc_dishwasher_power
payload_on: true # payload_on: true
payload_off: false # payload_off: false
value_template: "{{ value_json.power }}" # value_template: "{{ value_json.power }}"
json_attributes_topic: "homeconnect/dishwasher/state" # json_attributes_topic: "homeconnect/dishwasher/state"
template: template:
binary_sensor: binary_sensor:
- name: hc_dishwasher_running - name: bosch_dishwasher_running
unique_id: hc_dishwasher_running unique_id: bosch_dishwasher_running
state: "{{ is_state('sensor.hc_dishwasher_state', 'Run') }}" state: "{{ is_state('sensor.bosch_dishwasher_operation_state', 'Run') }}"
counter: counter:
dishwasher_door_open_count: bosch_dishwasher_door_open_count:
name: dishwasher_door_open_count name: bosch_dishwasher_door_open_count
input_select:
bosch_dishwasher_state_extended:
name: bosch_dishwasher_state_extended
options:
- 'Empty'
- 'Washing'
- 'Washed'
- 'Collecting Dishes'
- 'Ready to wash'
homeassistant: homeassistant:
customize: customize:
sensor.hc_dishwasher_state: sensor.bosch_dishwasher_state:
friendly_name: Посудомойка Статус friendly_name: Посудомойка Статус
binary_sensor.hc_dishwasher_door: binary_sensor.bosch_dishwasher_door:
friendly_name: Посудомойка Дверца friendly_name: Посудомойка Дверца
binary_sensor.hc_dishwasher_remaining: binary_sensor.bosch_dishwasher_remaining:
friendly_name: Посудомойка До конца программы friendly_name: Посудомойка До конца программы
sensor.hc_dishwasher_running: sensor.bosch_dishwasher_running:
friendly_name: Посудомоечная машина friendly_name: Посудомоечная машина
binary_sensor.hc_dishwasher_power: binary_sensor.bosch_dishwasher_power:
friendly_name: Посудомойка Питание friendly_name: Посудомойка Питание
@@ -1,21 +1,14 @@
telegram_bot: # telegram_bot: секция удалена — интеграция уже настроена через config entry
- platform: polling # (UI, импортирован 2025-12-28), YAML-платформа с ней конфликтовала:
api_key: !secret telegram_key # ERROR integration telegram_bot cannot be merged, expected a dict
allowed_chat_ids: #
- !secret telegram_group_chat # notify: платформы (legacy, deprecated) тоже убраны — все appdaemon-скрипты
- !secret telegram_monster_chat # мигрированы на entity-based telegram_bot.send_message/send_photo (см.
- !secret telegram_sveta_chat # appdaemon/settings/apps/globals.py: TELEGRAM_TARGETS, send_telegram(),
# send_telegram_photo()). Проверено: тестовые сообщения через новый механизм
notify: # успешно доставлены во все 3 чата (группа, Monster, Svetlana) 2026-07-14.
- platform: telegram #
name: telegram # Telegram bot notify-сущности теперь берутся из config entry:
chat_id: !secret telegram_group_chat # notify.home_assistant_fonvizina_fonvizina_18_1001567589226
parse_mode: html # notify.home_assistant_fonvizina_monster_123622180
- platform: telegram # notify.home_assistant_fonvizina_svetlana_205225134
name: telegram_monster
chat_id: !secret telegram_monster_chat
parse_mode: html
- platform: telegram
name: telegram_sveta
chat_id: !secret telegram_sveta_chat
parse_mode: html
@@ -0,0 +1,21 @@
telegram_bot:
- platform: polling
api_key: !secret telegram_key
allowed_chat_ids:
- !secret telegram_group_chat
- !secret telegram_monster_chat
- !secret telegram_sveta_chat
notify:
- platform: telegram
name: telegram
chat_id: !secret telegram_group_chat
parse_mode: html
- platform: telegram
name: telegram_monster
chat_id: !secret telegram_monster_chat
parse_mode: html
- platform: telegram
name: telegram_sveta
chat_id: !secret telegram_sveta_chat
parse_mode: html
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long