first commit

This commit is contained in:
Your Name
2024-01-11 15:10:51 +03:00
commit eddf07c652
602 changed files with 67154 additions and 0 deletions
@@ -0,0 +1,98 @@
import appdaemon.plugins.hass.hassapi as hass
import globals
import time
import datetime
import random
from datetime import timedelta
#
# Co2 report.
#
# Args:
# entity - group of entities to send state report
# notify - notify entity to send message
#
# Release Notes
#
# Version 1.0:
# Initial Version
class AirQuality(hass.Hass):
volume = 0.7
co2_max_level = 1000
def initialize(self):
if 'alice' not in self.args or 'co2_sensor' not in self.args or 'ha_panel' not in self.args:
self.error("Please provide alice, co2_sensor, ha_panel in config!")
return
self.timer = self.run_every(self.timer_tick, self.datetime()+timedelta(seconds=10), 1*60*60)
def timer_tick(self, args) -> None:
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
home_state = self.get_state(self.args['ha_panel'])
co2_value = int(float(self.get_state(self.args['co2_sensor'])))
if home_state == "disarmed" and self.time_is_between(self.datetime(), '08:00:00', '23:59:59'):
self.log('co2 level: {}'.format(co2_value))
if co2_value > self.co2_max_level:
self.warn_air_quality(co2_value)
def warn_air_quality(self, co2_value):
commands=[
"Уровень ЦО2 достиг отметки {}... Пожалуйста, откройте окно.".format(co2_value),
"Мне кажется у нас душно... Давайте откроем окно?",
"ЦО2 растёт не по дням, а по часам... Уже {}... Давайте проветрим?".format(co2_value),
"Кажется, я давно не была на улице... Давайте откроем окно и сделаем вид что гуляем?",
"Пора открыть окошко, ЦО2 уже {}.".format(co2_value)
]
random.shuffle(commands)
self.say(commands[0])
def command(self, command):
self.call_service('yandex_station/send_command', command='sendText', text=command)
def say(self, command):
volume = self.get_state(self.args['alice'], 'volume_level')
state = self.get_state(self.args['alice'])
self.log('state: {}'.format(state))
if state == "playing":
self.call_service('media_player/media_pause', entity_id=self.args['alice'])
self.call_service('media_player/volume_set', entity_id=self.args['alice'], volume_level=self.volume)
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)
self.run_in(self.run_in_volume, 10, volume=volume, state=state)
def run_in_volume(self, kwargs):
if 'volume' not in kwargs or 'state' not in kwargs:
return
volume = kwargs['volume']
state = kwargs['state']
self.log('setting volume back to {}'.format(volume))
self.call_service('media_player/volume_set', entity_id=self.args['alice'], volume_level=volume)
if state == "playing":
self.log('resuming')
# self.call_service('media_player/media_play', entity_id=self.args['alice'])
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,11 @@
#################################
# HOME PRESENSE #
#################################
airquality:
module: airquality
class: AirQuality
alice: media_player.yandex_station
ha_panel: alarm_control_panel.ha_alarm
co2_sensor: sensor.co2_mainroom_bed
global_dependencies:
- globals
+31
View File
@@ -0,0 +1,31 @@
import appdaemon.plugins.hass.hassapi as hass
import appdaemon.plugins.mqtt.mqttapi as mqtt
import globals
import json
import random
import time
import datetime
class MqttSay(mqtt.Mqtt, hass.Hass):
base_topic = "home/AliceBig/say"
def initialize(self) -> None:
self.log('Initialize Alice say plugin')
self.base_topic = self.args.get('topic', self.base_topic)
self.log('Listening for {}'.format(self.base_topic))
self.mqtt_subscribe(self.base_topic, namespace='mqtt')
self.listen_event(self.event_listener, 'MQTT_MESSAGE', namespace='mqtt')
def event_listener(self, event_name, data, kwargs):
if self.base_topic != data['topic']:
return
self.say(data['payload'])
def command(self, command):
self.call_service('yandex_station/send_command', command='sendText', text=command)
def say(self, command):
# self.call_service('media_player/volume_set', entity_id=self.args['alice'], volume_level=0.6)
self.call_service('media_player/play_media', entity_id=self.args['alice'], media_content_type='text', media_content_id=command)
@@ -0,0 +1,7 @@
mqttSay:
module: mqttSay
class: MqttSay
global_dependencies:
- globals
alice: media_player.yandex_station
topic: "home/yandexStation/say"
@@ -0,0 +1,49 @@
import appdaemon.plugins.hass.hassapi as hass
import globals
import time
#
# Home and away report
# Send report about home state after presence_actions was done.
#
# Args:
# entity - group of entities to send state report
# notify - notify entity to send message
#
# Release Notes
#
# Version 1.0:
# Initial Version
class PresenceAction(hass.Hass):
report_delay = 15
def initialize(self):
if 'alice' not in self.args:
self.error("Please provide alice in config!")
return
self.listen_event(self.away_mode, "away_mode")
self.listen_event(self.return_home_mode, "return_home_mode")
def away_mode(self, event_id, event_args, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
self.command('стоп')
def return_home_mode(self, event_id, event_args, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
command = 'Добро пожаловать домой.'
self.run_in(self.run_in_say, 59, command=command)
def run_in_say(self, kwargs):
if 'command' not in kwargs:
return
text = kwargs['command']
self.say(text)
def command(self, command):
self.call_service('yandex_station/send_command', command='sendText', text=command)
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,9 @@
#################################
# HOME PRESENSE #
#################################
presence_action:
module: presence_action
class: PresenceAction
alice: media_player.yandex_station
global_dependencies:
- globals
+29
View File
@@ -0,0 +1,29 @@
#################################################################
## Global
#################################################################
global_modules:
- globals
- automation
- notify
automation:
class: Automation
module: automation
#################################
# CUBE #
#################################
# cube:
# module: cube
# class: Cube
#################################
# NARODMON #
#################################
# narodmon_sensor:
# module: narodmon_sensor
# class: NarodmonSensor
# entity: sensor.lux_outside
# friendly_name: 'Освещенность (Улица)'
# user: 1089
# sensorid: 7574
# interval: 120
+55
View File
@@ -0,0 +1,55 @@
import appdaemon.plugins.hass.hassapi as hass
class Base(hass.Hass):
"""Define a base automation object."""
def initialize(self) -> None:
"""Initialize."""
# self.log('Initialize base')
# Define a holding place for HASS entity IDs:
self.entity_ids = self.args.get('entity_ids', {})
# Define a holding place for any scheduler handles that the automation
# wants to keep track of:
self.handles = {} # type: Dict[str, str]
# Define a holding place for key/value properties for this automation:
self.properties = self.args.get('properties', {})
# Take every dependecy and create a reference to it:
for app in self.args.get('dependencies', []):
if not getattr(self, app, None):
setattr(self, app, self.get_app(app))
class Automation(Base):
"""Define a base automation object."""
def initialize(self) -> None:
"""Initialize."""
super().initialize()
# self.log('Initialize automation')
# Define a reference to the "manager app" – for example, a trash-
# related automation might carry a reference to TrashManager:
if self.args.get('app'):
self.app = getattr(self, self.args['app'])
# Set the entity ID of the input boolean that will control whether
# this automation is enabled or not:
self.enabled_entity_id = None # type: ignore
enabled_config = self.args.get('enabled_config', {})
if enabled_config:
if enabled_config.get('toggle_name'):
self.enabled_entity_id = 'input_boolean.{0}'.format(
enabled_config['toggle_name'])
else:
self.enabled_entity_id = 'input_boolean.{0}'.format(self.name)
# Register any "mode alterations" for this automation – for example,
# perhaps it should be disabled when Vacation Mode is enabled:
mode_alterations = self.args.get('mode_alterations', {})
if mode_alterations:
for mode, value in mode_alterations.items():
mode_app = getattr(self, mode)
mode_app.register_enabled_entity(self.enabled_entity_id, value)
@@ -0,0 +1,41 @@
import appdaemon.plugins.hass.hassapi as hass
import os
#
# App to send notification when doorbell is ringing
#
# Args:
#
# notify = notification platform to send notifications to
# ringtone (optional) = ringtone to play
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class Doorbell(hass.Hass):
ringtone = 10
gw_mac = ''
def initialize(self):
if "notify" not in self.args or "sensor" not in self.args:
self.error("Please provide notify, sensor in config!")
return
if "gw_mac" in self.args:
self.gw_mac = self.args['gw_mac']
if 'ringtone' in self.args:
self.ringtone = self.args['ringtone']
self.listen_state(self.sensor_trigger, self.args['sensor'])
def sensor_trigger(self, entity, attribute, old, new, kwargs):
if new == None or new == "None" or new == "" or new == "unknown":
return
self.log("doorbell call")
self.log("old: {}, new: {}".format(old, new))
self.call_service("xiaomi_aqara/play_ringtone", gw_mac=self.gw_mac, ringtone_id=self.ringtone, ringtone_vol=100)
self.notify("Звонок в дверь!!!", name = self.args['notify'])
# result = self.send_video()
@@ -0,0 +1,11 @@
#################################
# INTERCOM #
#################################
doorbell:
module: doorbell
class: Doorbell
sensor: sensor.doorbell_button
notify: telegram
gw_mac: !secret aqara1_mac
global_dependencies:
- globals
@@ -0,0 +1,60 @@
import appdaemon.plugins.hass.hassapi as hass
import os
#
# App to send notification when doorbell is ringing
#
# Args:
#
# notify = notification platform to send notifications to
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class Intercom(hass.Hass):
is_blocked = False
timer = None
def initialize(self):
if "notify" not in self.args or "sensor" not in self.args:
self.error("Please provide notify, sensor in config!")
return
self.listen_state(self.sensor_trigger, self.args['sensor'])
def sensor_trigger(self, entity, attribute, old, new, kwargs):
if old == "" or old == "unknown":
return
if new == "ring" and self.is_blocked == False:
self.log("intercom call")
self.log("old: {}, new: {}".format(old, new))
self.say("Звонок в домофон!!!")
self.notify("Звонок в домофон!!! ({}, {})".format(old, new), name = self.args['notify'])
self.is_blocked = True
self.run_timer()
def run_timer(self):
if self.timer != None:
self.stop_timer()
self.timer = self.run_in(self.unblock_msg, self.args['timeout'])
def stop_timer(self):
if (self.timer == None):
return
self.log('stopping timer.')
self.cancel_timer(self.timer)
self.timer = None
def unblock_msg(self, kwargs):
self.log('stopping timer')
self.is_blocked = False
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,12 @@
#################################
# INTERCOM #
#################################
intercom:
module: intercom
class: Intercom
sensor: sensor.zintercom_state
notify: telegram
timeout: 30
alice: media_player.yandex_station
global_dependencies:
- globals
@@ -0,0 +1,22 @@
import appdaemon.plugins.hass.hassapi as hass
#
# This app fix event turn off after hass restart
#
# Args:
#
# Release Notes
#
# Version 1.0:
# Initial Version
class AutomationFix(hass.Hass):
def initialize(self):
self.listen_event(self.ha_event, "plugin_started")
def ha_event(self, event_name, data, kwargs):
self.run_in(self.turn_on_automations_timer, 10)
def turn_on_automations_timer(self, kwargs):
self.log('turning on automations')
self.call_service('automation/turn_on', entity_id='group.all_automations')
@@ -0,0 +1,6 @@
#################################
# FIXES #
#################################
automation_fix:
module: automation_fix
class: AutomationFix
+97
View File
@@ -0,0 +1,97 @@
import importlib
import subprocess
from typing import Any, Tuple, Union
#
# Global module
# contain global functions
#
# Args:
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
"""Define various constants."""
CONF_PEOPLE = 'people'
def most_common(the_list: list) -> Any:
"""Return the most common element in a list."""
return max(set(the_list), key=the_list.count)
def check_properties(self, args):
missing = []
for arg in args:
if arg not in self.properties:
missing.append(arg)
if len(missing) > 0:
self.error("Please provide {} in properties!".format(missing))
return False
return True
def check_args(self, args):
missing = []
for arg in args:
if arg not in self.args:
missing.append(arg)
if len(missing) > 0:
self.error("Please provide {} in config!".format(missing))
return False
return True
def check_constaint(self, name='constraint'):
self.log('checking constaint: {}'.format(name))
if name not in self.args:
self.log('script is not constrained')
return True;
constaint_input = self.args[name]
constaint_state = self.get_state(constaint_input)
def get_group_entities(self, group):
attributes = self.get_state(group, attribute = "all")
if (attributes == None):
self.log('Warning: Possible invalid group: {}'.format(group))
return []
if 'attributes' in attributes:
attributes = attributes['attributes']
#group
if "entity_id" in attributes:
return attributes["entity_id"]
#simple item
return [group]
def turn_on(self, entity_id, state):
if entity_id.startswith("light"):
self.call_service("light/turn_off", entity_id = entity_id, transition = 1, brightness = 255)
else:
self.turn_off(entity_id, state)
def turn_off(self, entity_id, state):
if entity_id.startswith("light"):
self.call_service("light/turn_off", entity_id = entity_id, transition = 1)
else:
self.turn_off(entity_id, state)
def notification(self, to, message):
self.notify(message, name = "telegram")
def get_arg(args, key):
key = args[key]
return key
def get_arg_list(args, key):
arg_list = []
if isinstance(args[key], list):
arg = args[key]
else:
arg = (args[key]).split(",")
for key in arg:
arg_list.append(key)
return arg_list
+79
View File
@@ -0,0 +1,79 @@
import appdaemon.plugins.hass.hassapi as hass
import re
#
# This app will create groups in runtime by user defined params
# Based on Rene Tode groups plugin ( hass@reot.org )
#
# Args:
# device_type: sensor # or any devicetype
# entity_part: "any_part"
# entities: # list of entities
# - sensor.any_entity
# hidden: False # or True
# view: True # or False
# assumed_state: False # or True
# friendly_name: Your Friendly Name
# nested_view: True # or False
#
# Release Notes
#
# Version 1.0:
# Initial Version
class Groups(hass.Hass):
def initialize(self):
self.run_in(self.create_groups_timer, 10)
def create_groups_timer(self, kwargs):
entitylist = []
expand_group = self.args.get("expand_group", False)
device_type = self.args.get("device_type", None)
all_entities = self.get_state(device_type)
if "entities" in self.args:
for entity in self.args["entities"]:
found = False
for real_entity in all_entities:
if real_entity.lower() == entity.lower():
self.append_entity(entitylist, entity, expand_group)
found = True
if not found:
for real_entity in all_entities:
if re.match(entity.lower(), real_entity.lower()):
self.append_entity(entitylist, real_entity, expand_group)
if 'entity_part' in self.args:
for entity in all_entities:
if self.args["entity_part"] in entity and entity.lower() not in entitylist:
self.append_entity(entitylist, entity, expand_group)
hidden = self.args.get("hidden", False)
view = self.args.get("view", False)
assumed_state = self.args.get("assumed_state",False)
friendly_name = self.args.get("friendly_name","")
name = "group." + self.name
if self.args.get('orderby', None) == 'friendly_name':
entitylist.sort(key=self.sortByFriendlyName)
self.log("Creating group {} with entities: {}".format(name, entitylist))
self.set_state(name,state="on",attributes={"view": view,"hidden": hidden,"assumed_state": assumed_state,"friendly_name": friendly_name,"entity_id": entitylist, 'description': 'Created and updated from appdaemon ({})'.format(__name__)})
def sortByFriendlyName(self, entity):
return self.get_state(entity, attribute='friendly_name')
def append_entity(self, entitylist, entity, expand_group):
entity = entity.lower()
if (('group.' not in entity and expand_group) or not expand_group) and entity not in entitylist:
entitylist.append(entity)
return
if expand_group and 'group.' in entity:
group_entities = self.get_group_entities(entity)
for group_entity in group_entities:
if group_entity not in entitylist:
entitylist.append(group_entity)
def get_group_entities(self, group):
group = self.get_state(group, attribute = "all")
return group["attributes"]["entity_id"]
@@ -0,0 +1,17 @@
report_entities:
module: groups
class: Groups
friendly_name: Контроль при приходе-уходе
entities: # list of entities
- light.yeelight_color1_286c071065c7
- light.yeelight_color1_34ce008fe328
- light.yeelight_mono1_34ce0087ff33
- light.yeelight_mono1_f0b4290e5444
- light.yeelight_mono1_f0b429a8f6df
- light.yeelight_mono1_286c07f12bed
- light.yeelight_mono1_f0b429a8d936
- switch.light_bath
- switch.light_kitchen
- switch.plug_light_hall
# device_type: light
# entity_part: "_"
@@ -0,0 +1,133 @@
import appdaemon.plugins.hass.hassapi as hass
import globals
import datetime
class LightButton(hass.Hass):
isLocked = False
def initialize(self):
if not globals.check_args(self,['entities', 'sensor']):
return
self.listen_event_handle_list = []
self.listen_event_handle_list.append(self.listen_event(self.event_detected, "xiaomi_aqara.click"))
self.listen_event_handle_list.append(self.listen_state(self.state_change, self.args['sensor'])) #, attribute='action'
def state_change(self, entity, attribute, old, new, kwargs):
if new == "single":
self.log('button_click')
self.make_action_by_mode("off")
if new == "double":
self.log('button_dblclick')
self.make_action_by_mode("on")
def event_detected(self, event_name, data, kwargs):
if data["entity_id"] != self.args["sensor"] or self.isLocked:
return
self.isLocked = True
if data["click_type"] == "single":
self.log('button_click')
self.make_action_by_mode("off")
if data["click_type"] == "double":
self.log('button_dblclick')
self.make_action_by_mode("on")
self.run_in(self.clear_lock, 1)
def clear_lock(self, kwargs):
self.isLocked = False
def make_action_by_mode(self, state):
states = self.fill_states()
control_entity = self.args.get('control_input', None)
if (control_entity != None):
entity_id = next(iter(states))
state = self.get_state(entity_id)
self.set_state(entity_id, self.invert_state(state))
self.set_state(control_entity, state)
return
if len(self.args["entities"]) > 1:
self.toggle_next_with_state(state, states)
def toggle_next_with_state(self, state, states):
for entity_id in states:
entity_state = states[entity_id]
if (entity_state == state):
self.set_state(entity_id, self.invert_state(state))
self.log('toggling {}'.format(entity_id))
return
for entity_id in states:
self.log('toggling {}'.format(entity_id))
self.toggle(entity_id)
def set_state(self, entity_id, state):
if (state == "off"):
self.log('turn_off {}'.format(entity_id))
self.set_off(entity_id)
if (state == "on"):
self.log('turn_on {}'.format(entity_id))
self.set_on(entity_id)
#light
def set_on(self, entity_id):
brightness = 1 #night mode
if self.time_is_between(self.datetime(), '07:00:00', '23:00:00'):
brightness=255 #day mode
if entity_id.startswith("light") and 'color' in entity_id:
self.call_service("homeassistant/turn_on", entity_id = entity_id, transition = 1, brightness = brightness, color_temp=204)
elif entity_id.startswith("light"):
self.call_service("homeassistant/turn_on", entity_id = entity_id, transition = 1, brightness = brightness)
else:
self.turn_on(entity_id)
def set_off(self, entity_id):
if entity_id.startswith("light"):
self.call_service("homeassistant/turn_off", entity_id = entity_id, transition = 1)
else:
self.turn_off(entity_id)
def invert_state(self, state):
if (state == "off"):
return "on"
return "off"
def fill_states(self):
states = {}
for entity_id in self.split_device_list(self.args["entities"]):
state = self.get_state(entity_id)
if (state == None):
self.log("Warning! Device {} not found (state: {}).".format(entity_id, state))
continue
states[entity_id] = state
return states
def terminate(self):
if self.listen_event_handle_list != None:
for listen_event_handle in self.listen_event_handle_list:
self.cancel_listen_event(listen_event_handle)
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,29 @@
#################################
# LIGHT BUTTONS #
#################################
# light_button_bath:
# module: lightbutton
# class: LightButton
# sensor: binary_sensor.wall_switch_bath
# entities: light.bath
# # control_input: input_boolean.bath_light_control
# global_dependencies:
# - globals
light_button_main:
module: lightbutton
class: LightButton
sensor: sensor.bed_button
entities: light.yeelight_color1_34ce008fe328,light.yeelight_color1_286c071065c7,light.yeelight_mono1_34ce0087ff33,light.yeelight_mono1_f0b4290e5444,light.yeelight_mono1_f0b429a8f6df,light.yeelight_mono1_286c07f12bed,light.yeelight_mono1_f0b429a8d936,light.lamp1
global_dependencies:
- globals
light_button_hall:
module: lightbutton
class: LightButton
sensor: sensor.0x00158d000183a5fe_action
entities: switch.plug_light_hall
global_dependencies:
- globals
@@ -0,0 +1,123 @@
import appdaemon.plugins.hass.hassapi as hass
import globals
import datetime
#
# This app will use motion sensors to turn on and off control_entity for 'timeout' time.
# It work with xiaomi motion sensors.
# main algorithm: when sensor turns on - turn on the light; when sensor is off - set timer for 'timeout';
# if sensor goes on - turn off the timer; when timer was hit - turn off the light.
#
# Args:
#
# sensor = motion sensor to use (will work with other sensor types too)
# timeout = timeout after sensor will turned off (keep in mind that xiaomi motion sensor has own timeout!)
# control_entity = entity, that will be controlled (example: group of light bulbs)
# constraint = you can define constraint (input_boolean), that will turn on and off this app.
# after_sundown (optionally) - whether to only trigger after sundown. example: True
# Release Notes
#
# Version 1.0:
# Initial Version
class LightControl(hass.Hass):
timer = None
def initialize(self):
if not globals.check_args(self, ["sensor", "timeout", "control_entity"]):
return
self.listen_event_handle_list = []
self.listen_event_handle_list.append(self.listen_state(self.sensor_trigger, self.args['sensor']))
def sensor_trigger(self, entity, attribute, old, new, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
if 'after_sundown' in self.args and self.args['after_sundown'] and not self.sun_down():
# self.log('Sun is up, and we will turn it only after sundown.')
return
vacuum_state = "docked"
ha_panel_state = ""
if "vacuum" in self.args:
vacuum_state = self.get_state(self.args["vacuum"])
if "ha_panel" in self.args:
ha_panel_state = self.get_state(self.args["ha_panel"])
#Если работает пылесос и сработал датчик движения
if ha_panel_state == "armed_away" and vacuum_state == 'cleaning':
self.log('Motion sensor is triggered, but vacuum is cleaning.')
return
#sensor is off
if new == 'off' and old == 'on':
self.log('Sensor is off - running timer for {}s.'.format(self.args['timeout']))
self.run_timer()
#sensor is on
if new == 'on' and old == 'off':
#turnin on control entity and wait while sensor will be off, than run timer.
self.control_entity_on()
self.stop_timer()
def terminate(self):
if self.listen_event_handle_list != None:
for listen_event_handle in self.listen_event_handle_list:
self.cancel_listen_event(listen_event_handle)
############ 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' is off - we dont need to do anything
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
entity = self.args['control_entity']
if not self.time_is_between(self.datetime(), '07:00:00', '23:00:00') and 'control_night_entity' in self.args:
entity=self.args['control_night_entity'] #day mode
if self.get_state(entity) == 'off':
self.turn_on(entity)
self.log("Light on ({}).".format(entity))
def control_entity_off(self, kwargs):
#if 'constraint' is off - we dont need to do anything
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
entity = self.args['control_entity']
if not self.time_is_between(self.datetime(), '07:00:00', '23:00:00') and 'control_night_entity' in self.args:
entity=self.args['control_night_entity'] #day mode
if self.get_state(entity) == 'on':
self.turn_off(entity)
self.log("Light off.")
self.stop_timer()
def stop_timer(self):
if (self.timer == None):
return
self.log('stopping timer.')
self.cancel_timer(self.timer)
self.timer = None
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,25 @@
light_control_hall:
module: lightcontrol
class: LightControl
sensor: binary_sensor.motion_sensor_hall
control_entity: switch.plug_light_hall
control_night_entity: light.gateway
timeout: 240
constraint: input_boolean.hall_light_control
ha_panel: alarm_control_panel.ha_alarm
vacuum: vacuum.xiaomi_vacuum_cleaner
global_dependencies:
- globals
light_control_kitchen:
module: lightcontrol
class: LightControl
sensor: group.kitchen_motion_sensors
control_entity: switch.light_kitchen
timeout: 600
constraint: input_boolean.kitchen_light_control
ha_panel: alarm_control_panel.ha_alarm
vacuum: vacuum.xiaomi_vacuum_cleaner
global_dependencies:
- globals
@@ -0,0 +1,94 @@
import appdaemon.plugins.hass.hassapi as hass
#
# This app will use motion sensors to turn on and off control_entity for 'timeout' time.
# It work with xiaomi motion sensors.
# main algorithm: when sensor turns on - turn on the light; when sensor is off - set timer for 'timeout';
# if sensor goes on - turn off the timer; when timer was hit - turn off the light.
#
# Args:
#
# sensor = motion sensor to use (will work with other sensor types too)
# timeout = timeout after sensor will turned off (keep in mind that xiaomi motion sensor has own timeout!)
# control_entity = entity, that will be controlled (example: group of light bulbs)
# constraint = you can define constraint (input_boolean), that will turn on and off this app.
#
# Release Notes
#
# Version 1.0:
# Initial Version
class LightControlBath(hass.Hass):
timer = None
door_state_changed = None
def initialize(self):
if "sensor" not in self.args or 'door_sensor' not in self.args or "timeout" not in self.args or "control_entity" not in self.args:
self.error("Please provide sensor, door_sensor, control_entity and timeout in config!")
return
self.listen_state(self.sensor_trigger, self.args['sensor'])
self.listen_state(self.door_sensor_trigger, self.args['door_sensor'])
def sensor_trigger(self, entity, attribute, old, new, kwargs):
#sensor is off
if new == 'off' and old == 'on':
self.log('door_state_changed:{}'.format(self.door_state_changed))
if self.door_state_changed == False:
return
self.log('Sensor is off - running timer for {}s.'.format(self.args['timeout']))
self.run_timer()
#sensor is on
if new == 'on' and old == 'off':
#turnin on control entity and wait while sensor will be off, than run timer.
self.control_entity_on()
self.stop_timer()
door_state = self.get_state(self.args['door_sensor'])
if door_state == "off":
self.door_state_changed = False
else :
self.door_state_changed = None
def door_sensor_trigger(self, entity, attribute, old, new, kwargs):
#door is open
sensor_state = self.get_state(self.args['sensor'])
if new == 'on' and old == 'off' and sensor_state == 'off':
self.log('door is opened now, runnig the timer')
if self.timer == None:
self.run_timer()
if self.door_state_changed == False:
self.door_state_changed = True
self.log('door_state_changed:{}'.format(self.door_state_changed))
############ TIMER ########################
def control_entity_on(self):
#if 'constraint' is off - we dont need to do anything
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
if self.get_state(self.args['control_entity']) == 'off':
self.turn_on(self.args['control_entity'])
self.log("Light on.")
def control_entity_off(self, kwargs):
#if 'constraint' is off - we dont need to do anything
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
if self.door_state_changed == False:
self.log('Motion is detected inside and door is still closed - dont turn off the light')
return
if self.get_state(self.args['control_entity']) == 'on':
self.turn_off(self.args['control_entity'])
self.log("Light off.")
self.stop_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 stop_timer(self):
if (self.timer == None):
return
self.log('stopping timer.')
self.cancel_timer(self.timer)
self.timer = None
@@ -0,0 +1,13 @@
#################################
# LIGHT CONTROL #
#################################
light_control_bath:
module: lightcontrol_bath
class: LightControlBath
sensor: binary_sensor.motion_sensor_bath
door_sensor: binary_sensor.door_window_sensor_bath
control_entity: switch.light_bath
timeout: 240
constraint: input_boolean.bath_light_control
global_dependencies:
- globals
@@ -0,0 +1,58 @@
import datetime
from automation import Automation, Base # type: ignore
#
# App to send email report for low balance
#
# Args:
#
# threshold = value below which battery levels are reported and email is sent
# always_send = set to 1 to override threshold and force send
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class Balance(Base):
def initialize(self):
super().initialize()
self.check_balance({"force": 1})
self.run_daily(self.check_balance, datetime.time(10, 0, 0))
def check_balance(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
values = {}
low = []
for key in self.entity_ids:
device = self.entity_ids[key]
balance = self.get_state(device)
if balance != None and self.is_number(balance):
if float(balance) < self.args["threshold"]:
low.append(device)
values[device] = balance
message=""
name = "Алтуфьево"
if 'name' in self.args:
name = self.args['name']
if low:
message += "Пожалуйста пополните баланс ({}): (< {}) \n".format(name, self.args["threshold"])
for device in low:
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 != "":
self.notify(message, name = "telegram") #name = "telegram_monster"
def is_number(self, s):
try:
float(s)
return True
except ValueError:
return False
@@ -0,0 +1,30 @@
#################################
# SENSOR BATTERY LOW REMINDER #
#################################
balance_low_report:
class: Balance
module: balance
constraint: input_boolean.balance_report
name: Алтуфьево
entity_ids:
mosenergosbyt_balance: sensor.power_mosenergosbyt_balance
epd_balance: sensor.epd_balance
always_send: 0
threshold: -100
notify: telegram
global_dependencies:
- globals
balance_low_report_belorechenskaya:
class: Balance
module: balance
constraint: input_boolean.balance_report
name: Белореченская
entity_ids:
mosenergosbyt_balance: sensor.power_mosenergosbyt_belorechenskaya_balance
epd_balance: sensor.epd_balance_belorechenskaya
always_send: 0
threshold: -100
notify: telegram
global_dependencies:
- globals
@@ -0,0 +1,58 @@
import appdaemon.plugins.hass.hassapi as hass
import datetime
#
# App to send email report for devices running low on battery
#
# Args:
#
# threshold = value below which battery levels are reported and email is sent
# always_send = set to 1 to override threshold and force send
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class Battery(hass.Hass):
def initialize(self):
#self.check_batteries({"force": 1})
time = datetime.time(10, 0, 0)
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']):
return
devices = self.get_state()
values = {}
low = []
for deviceName in devices:
device = devices[deviceName]
if device == None:
continue
battery = None
if "attributes" in device:
if "battery" in device["attributes"]:
battery = device["attributes"]["battery"]
if "battery_level" in device["attributes"]:
battery = device["attributes"]["battery_level"]
if battery != None:
if battery < self.args["threshold"] and battery != 0:
low.append(deviceName)
values[deviceName] = battery
message=""
if low:
message += "У следующих датчиков садится батарея: (< {}) \n".format(self.args["threshold"])
for device in low:
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 != "":
self.notify(message, name = "telegram")
@@ -0,0 +1,12 @@
#################################
# SENSOR BATTERY LOW REMINDER #
#################################
sensor_battery_low_report:
class: Battery
module: battery
constraint: input_boolean.battery_report
always_send: 0
threshold: 7
notify: telegram
global_dependencies:
- globals
@@ -0,0 +1,59 @@
import appdaemon.plugins.hass.hassapi as hass
import datetime
#
# App to send email report for devices running low on battery
#
# Args:
#
# threshold = value below which battery levels are reported and email is sent
# always_send = set to 1 to override threshold and force send
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class Flower(hass.Hass):
def initialize(self):
#self.check_batteries({"force": 1})
time = datetime.time(21, 0, 0)
self.run_daily(self.check_flowers, time)
# self.log('---------------------')
# self.check_flowers([])
def check_flowers(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
devices = self.get_state()
values = {}
low = []
for deviceName in devices:
device = devices[deviceName]
if device == None:
continue
if '_soil_moisture' not in deviceName or 'avocado' not in deviceName:
continue
# self.log('found: {}'.format(device))
moisture = int(device['state'])
if moisture != None:
if moisture < self.args["threshold"] and moisture != 0:
low.append(deviceName)
values[deviceName] = moisture
message=""
if low:
message += "Пора полить цветочки: (< {}) \n".format(self.args["threshold"])
for device in low:
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 != "":
self.notify(message, name = "telegram")
self.call_service('media_player/play_media', entity_id=self.args['alice'], media_content_type='text', media_content_id="Пора полить цветочки.")
@@ -0,0 +1,13 @@
#################################
# SENSOR BATTERY LOW REMINDER #
#################################
sensor_flower_moisture_low_report:
class: Flower
module: flower
constraint: input_boolean.flower_report
alice: media_player.yandex_station
always_send: 0
threshold: 60
notify: telegram
global_dependencies:
- globals
@@ -0,0 +1,28 @@
import appdaemon.plugins.hass.hassapi as hass
import datetime
#
# App to send reports about unsuccessfull auths
#
# Args:
#
# threshold = value below which battery levels are reported and email is sent
# always_send = set to 1 to override threshold and force send
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class Security(hass.Hass):
def initialize(self):
self.log('---------------------')
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')
self.notify(new, name = notifier)
self.log(new)
@@ -0,0 +1,10 @@
#################################
# INVALID AUTH REPORT #
#################################
invalid_auth_report:
class: Security
module: security
alice: media_player.yandex_station
notify: telegram
global_dependencies:
- globals
@@ -0,0 +1,57 @@
import appdaemon.plugins.hass.hassapi as hass
from datetime import timedelta
import datetime
import globals
import requests
#
# App to monitor entity update time.
#
# Args:
#
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class Watchdog(hass.Hass):
listen_event_handle_list = []
timezone = 0
def initialize(self):
if not globals.check_args(self, ['check_interval', 'timeout']):
return
self.timer = self.run_every(self.watchdog_check, self.datetime()+timedelta(seconds=1), self.args['check_interval'])
def watchdog_check(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
self.log('disabled by constraint')
return
entities = []
if 'entity' in self.args:
entities.append(self.args['entity'])
if 'entities' in self.args:
for entity in self.args['entities']:
entities.append(entity)
# self.log(entities)
for entity in entities:
# entity = self.args['entity']
attributes = self.get_state(entity, attribute='all')
if not 'last_changed' in attributes:
continue
now = datetime.datetime.now()
last_changed = attributes['last_changed']
last_changed_date = datetime.datetime.strptime(last_changed, '%Y-%m-%dT%H:%M:%S.%f+00:00') + timedelta(hours=self.timezone)
time_diff = now - last_changed_date
# self.log('{}: {}, {}, {} (now: {})'.format(entity, last_changed, last_changed_date, time_diff, now))
if time_diff.seconds > self.args['timeout']:
self.log('Entity {} was changed {} seconds ago.'.format(entity, time_diff.seconds))
friendly_name = self.friendly_name(entity)
message = "Датчик {} ({}) не обновлялся в течении {} секунд.".format(friendly_name, entity, self.args['timeout'])
self.notify(message, name = self.args.get('notify', "telegram"))
@@ -0,0 +1,13 @@
# uptime_watchdog:
# class: Watchdog
# module: watchdog
# constraint: input_boolean.watchdog_report
# entities:
# - sensor.uptime_drink_filter
# - sensor.uptime_sls
# - sensor.uptime_watercontrol
# check_interval: 60
# timeout: 900
# notify: telegram_monster
# global_dependencies:
# - globals
@@ -0,0 +1,109 @@
import appdaemon.plugins.hass.hassapi as hass
import datetime
import globals
#
# App to send notifications when counters state needs attention
#
# Args:
#
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class WaterCountersAlarm(hass.Hass):
checkup_threasold = 31
send_threasold = 31
def initialize(self):
if not globals.check_args(self, ['checkup_threasold', 'send_threasold']):
return
self.checkup_threasold = self.args['checkup_threasold']
self.send_threasold = self.args['send_threasold']
self.check_send_dates({"force": 1})
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.check_checkup_dates([])
def check_send_dates(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
devices = self.get_state()
if devices == None:
return
values = {}
send_devices = []
for deviceName in devices:
device = devices[deviceName]
if (device == None):
continue
send_date = None
if "attributes" in device:
if "date" in device["attributes"]:
send_date = device["attributes"]["date"]
if send_date != None:
self.log('found device {} with send date {}'.format(deviceName, send_date))
if send_date == "":
send_devices.append(deviceName)
else:
send_date_date = None
if '-' in send_date:
send_date_date = datetime.datetime.strptime(send_date, '%Y-%m-%d')
else:
send_date_date = datetime.datetime.strptime(send_date, '%d.%m.%Y')
if send_date_date < (datetime.datetime.now() - datetime.timedelta(days=self.send_threasold)):
send_devices.append(deviceName)
values[deviceName] = send_date
message=""
if send_devices:
message += "ВНИМАНИЕ! У вас есть счетчики, показания по которым давно не передавались:\n"
for device in send_devices:
message += "- {}: {}\n".format(self.friendly_name(device), values[device])
message += "\n\n"
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 != "":
self.notify(message, name = "telegram")
def check_checkup_dates(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
entities = self.get_state()
if entities == None:
return
values = {}
checkup_devices = []
for entity_id in entities:
entity = entities[entity_id]
if entity == None:
# self.log('not found {} in entities list.'.format(entity_id))
continue
checkup = None
if "attributes" in entity:
if "checkup" in entity["attributes"]:
checkup = entity["attributes"]["checkup"]
if checkup != None:
self.log('found device {} with checkup date {}'.format(entity_id, checkup))
checkup_date = datetime.datetime.strptime(checkup, '%Y-%m-%d')
if (checkup_date - datetime.timedelta(days=self.checkup_threasold)) < datetime.datetime.now():
checkup_devices.append(entity_id)
values[entity_id] = checkup
message=""
if checkup_devices:
message += "ВНИМАНИЕ! У вас есть счетчик, нуждающийся в поверке:\n"
for device in checkup_devices:
message += "- {}: {}\n".format(self.friendly_name(device), values[device])
message += "\n\n"
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 != "":
self.notify(message, name = "telegram")
@@ -0,0 +1,11 @@
#################################
# WATER COUNTERS ALARM #
#################################
water_counters:
class: WaterCountersAlarm
module: water_counters
constraint: input_boolean.water_counters_report
checkup_threasold: 31
send_threasold: 31
global_dependencies:
- globals
+63
View File
@@ -0,0 +1,63 @@
import appdaemon.plugins.hass.hassapi as hass
#
# Notification system
#
# Args:
#
# Release Notes
#
# Version 1.0:
# Initial Version
class BaseNotifyTarget():
def notify(self, hass, message, person='*'):
hass.log('notifying person {} with message: {}'.format(person, message))
class AliceNotifyTarget(BaseNotifyTarget):
alice = 'media_player.yandex_station'
def notify(self, hass, message, person='*'):
msg = message
if person == 'danil':
msg = "Данил, " + msg
elif person == 'sveta':
msg = "Света, " + msg
hass.log('notifying over alice person {} with message: {}'.format(target, message))
self.say(hass, msg)
def say(self, hass, message):
hass.log('message = {}'.format(message))
hass.call_service('media_player/play_media', entity_id=self.alice, media_content_type='text', media_content_id=message)
class TelegramNotifyTarget(BaseNotifyTarget):
target_default = 'telegram'
target_sveta = 'telegram_sveta'
target_danil = 'telegram_monster'
def notify(self, hass, message, person='*'):
target = self.target_default
if person == 'danil':
target = self.target_danil
elif person == 'sveta':
target = self.target_sveta
hass.log('notifying over telegram person {} with message: {}'.format(target, message))
hass.notify(message, name=target)
class Notification(hass.Hass):
home_targets = [AliceNotifyTarget()]
away_targets = [TelegramNotifyTarget()]
def initialize(self):
self.log('Init')
def notify_person(self, message, target='*', repeat = 1, delay = 60, onEvent = '', at=None):
notifyTarget = None
away_mode = True
if (away_mode):
notifyTarget = self.away_targets[0]
else:
notifyTarget = self.home_targets[0]
self.log('message: {}'.format(message))
notifyTarget.notify(self, message, person=target)
+3
View File
@@ -0,0 +1,3 @@
notify:
module: notify
class: Notification
@@ -0,0 +1,46 @@
import appdaemon.plugins.hass.hassapi as hass
#
# Listen for presence sensor change state and change alarm control panel state.
#
# Args:
# sensor - home presence 'sensor'
# ha_panel - alarm control panel entity (to arm and disarm).
# constraint - (optional, input_boolen), if turned off - alarm panel will be not armed\disarmed.
#
# Release Notes
#
# Version 1.0:
# Initial Version
class AlarmPanelBySensor(hass.Hass):
def initialize(self):
if "sensor" not in self.args or "ha_panel" not in self.args:
self.error("Please provide sensor and ha_panel in config!")
return
self.listen_state(self.sensor_trigger, self.args['sensor'])
self.listen_event(self.ha_event, "ha_started")
def ha_event(self, event_name, data, kwargs):
self.log('Starting up!')
state = self.get_state(self.args['sensor'])
self.log('Updating alarm_control_panel state: {}'.format(state))
if state == "off":
self.away_mode()
def sensor_trigger(self, entity, attribute, old, new, kwargs):
self.log("{} turned {}".format(entity, new))
if new == "off" and old == "on":
self.away_mode()
if new == "on" and old == "off":
self.return_home_mode()
def away_mode(self):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
self.call_service("alarm_control_panel/alarm_arm_away", entity_id = self.args['ha_panel'])
def return_home_mode(self):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
self.call_service("alarm_control_panel/alarm_disarm", entity_id = self.args['ha_panel'])
@@ -0,0 +1,11 @@
#################################
# HOME PRESENSE #
#################################
alarm_panel:
module: alarm_panel
class: AlarmPanelBySensor
sensor: sensor.presence_home
ha_panel: alarm_control_panel.ha_alarm
constraint: input_boolean.presence_control
global_dependencies:
- globals
@@ -0,0 +1,77 @@
import appdaemon.plugins.hass.hassapi as hass
#
# Listen for Alarm panel state change and fire an events ('away_mode' and 'return_home_mode')
#
# Args:
# ha_panel - alarm control panel entity.
# Release Notes
#
# Version 1.0:
# Initial Version
class AlarmPanelAction(hass.Hass):
extended_timeout = 5*60
timers = []
def initialize(self):
if "ha_panel" not in self.args:
self.error("Please provide ha_panel in config!")
return
self.listen_state(self.ha_panel_trigger, self.args['ha_panel'])
extended_timeout = self.args.get('extended_timeout', self.extended_timeout)
self.log('extended_timeout: {}'.format(self.extended_timeout))
def ha_panel_trigger(self, entity, attribute, old, new, kwargs):
# не срабатываем на triggered.
if new == "armed_away" and old != "triggered":
self.away_mode()
if new == "disarmed" and old == "armed_away":
self.return_home_mode()
def away_mode(self):
self.log('away_mode')
self.fire_event('away_mode')
if 'presence_extended' in self.args:
self.fire_event('presence_extended_just_left')
self.set_mode('Just Left')
self.cancel_current_timer()
self.timers.append(self.run_in(self.run_in_set_mode, self.extended_timeout, mode='Away'))
def return_home_mode(self):
self.log('return_home_mode')
self.fire_event('return_home_mode')
if 'presence_extended' in self.args:
self.set_mode('Just Arrived')
self.fire_event('presence_extended_just_arrived')
self.cancel_current_timer()
self.timers.append(self.run_in(self.run_in_set_mode, self.extended_timeout, mode='Home'))
def set_mode(self, mode):
if 'presence_extended' not in self.args:
return
entity_id = self.args['presence_extended']
self.log('setting mode direct: {}={}'.format(entity_id, mode))
self.call_service("input_select/select_option", entity_id = entity_id, option = mode)
def run_in_set_mode(self, kwargs):
if not 'mode' in kwargs:
return
mode = kwargs['mode']
entity_id = self.args['presence_extended']
event = 'presence_extended_{}'.format(mode.lower())
self.log('firing event: {}'.format(event))
self.fire_event(event)
self.log('setting mode by timer: {}={}'.format(entity_id, mode))
self.call_service("input_select/select_option", entity_id = entity_id, option = mode)
def cancel_current_timer(self):
for timer in self.timers:
self.log('canceling timer')
self.cancel_timer(timer)
self.timers = []
@@ -0,0 +1,11 @@
#################################
# HOME PRESENSE #
#################################
alarm_panel_action:
module: alarm_panel_action
class: AlarmPanelAction
ha_panel: alarm_control_panel.ha_alarm
presence_extended: input_select.presence_extended
extended_timeout: 300
global_dependencies:
- globals
@@ -0,0 +1,32 @@
import appdaemon.plugins.hass.hassapi as hass
#
# Light 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 EntitiesByPresence(hass.Hass):
def initialize(self):
if 'away_entities' not in self.args or 'return_entities' not in self.args:
self.error("Please provide away_entities and return_entities in config!")
return
self.listen_event(self.away_mode, "away_mode")
self.listen_event(self.return_home_mode, "return_home_mode")
def away_mode(self, event_id, event_args, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
for entity_id in self.args["away_entities"]:
self.turn_off(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']):
return
for entity_id in self.args["return_entities"]:
self.turn_on(entity_id)
@@ -0,0 +1,23 @@
#################################
# HOME PRESENSE #
#################################
entities_action:
module: entities_action
class: EntitiesByPresence
away_entities:
- light.yeelight_color1_286c071065c7
- light.yeelight_color1_34ce008fe328
- light.yeelight_mono1_34ce0087ff33
- light.yeelight_mono1_f0b4290e5444
- light.yeelight_mono1_f0b429a8f6df
- light.yeelight_mono1_286c07f12bed
- light.yeelight_mono1_f0b429a8d936
- light.lamp1
- switch.light_bath
- switch.light_kitchen
- switch.plug_light_hall
return_entities:
- switch.plug_light_hall
- light.yeelight_color1_34ce008fe328
global_dependencies:
- globals
@@ -0,0 +1,107 @@
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 Purifier(hass.Hass):
away_fan_speed = 8 #1-14
listen_event_handle_list = []
mode = 'auto'
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)
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"))
self.listen_event_handle_list.append(self.listen_state(self.state_change, self.args['entity'], attribute='illuminance'))
def state_change(self, entity, attribute, old, new, kwargs):
presence_state = self.get_state(self.args['ha_panel'])
if presence_state != "disarmed":
# self.log('Nobody home, ignoring illuminance changes.')
return
self.log("{} changed from {} to {}".format(attribute, old, new))
if old > 0 and new == 0 and self.time_is_between(self.datetime(), self.args["on_time"], self.args["off_time"]):
self.night_mode()
elif old == 0 and new > 0 and self.time_is_between(self.datetime(), self.args["on_time"], self.args["off_time"]):
self.auto_mode()
#self.auto_mode()
def return_home_mode_event(self, event_id, event_args, kwargs):
self.auto_mode()
def away_mode_event(self, event_id, event_args, kwargs):
self.away_mode()
def night_mode_timer_event(self, kwargs):
self.night_mode()
def day_mode_timer_event(self, kwargs):
self.auto_mode()
def away_mode(self):
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_speed", entity_id = self.args['entity'], speed='favorite')
self.call_service("fan/xiaomi_miio_set_favorite_level", entity_id = self.args['entity'], level=self.away_fan_speed)
self.call_service("fan/xiaomi_miio_set_led_on", entity_id = self.args['entity'])
# self.call_service("fan/xiaomi_miio_set_buzzer_on", entity_id = self.args['entity'])
def auto_mode(self):
self.mode='auto'
self.log('Set purifier mode to auto.')
if 'entity' in self.args:
self.call_service("fan/set_speed", entity_id = self.args['entity'], speed='auto')
self.call_service("fan/xiaomi_miio_set_led_on", entity_id = self.args['entity'])
# self.call_service("fan/xiaomi_miio_set_buzzer_on", entity_id = self.args['entity'])
def night_mode(self):
self.mode='night'
self.log('Set purifier mode to silent.')
if 'entity' in self.args:
self.call_service("fan/xiaomi_miio_set_buzzer_off", entity_id = self.args['entity'])
self.call_service("fan/set_speed", entity_id = self.args['entity'], speed='silent')
self.call_service("fan/xiaomi_miio_set_led_off", entity_id = self.args['entity'])
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,9 @@
purifier_timer:
module: purifier
class: Purifier
on_time: "06:45:00"
off_time: "23:00:00"
entity: fan.xiaomi_miio_device
ha_panel: alarm_control_panel.ha_alarm
global_dependencies:
- globals
@@ -0,0 +1,51 @@
import appdaemon.plugins.hass.hassapi as hass
import globals
#
# Home and away report
# Send report about home state after presence_actions was done.
#
# Args:
# entity - group of entities to send state report
# notify - notify entity to send message
#
# Release Notes
#
# Version 1.0:
# Initial Version
class ReportByPresence(hass.Hass):
report_delay = 15
def initialize(self):
if 'entity' not in self.args or 'notify' not in self.args:
self.error("Please provide entity and notify in config!")
return
self.listen_event(self.away_mode, "away_mode")
self.listen_event(self.return_home_mode, "return_home_mode")
def away_mode(self, event_id, event_args, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
#wait 15 seconds before all devices will change thair states.
self.run_in(self.send_report, self.report_delay)
def return_home_mode(self, event_id, event_args, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
self.notify("Добро пожаловать домой!", name = self.args['notify'])
############# AWAY REPORT ##########################
def send_report(self, kwargs):
any = False
message = ""
entities = globals.get_group_entities(self, self.args['entity'])
for entity in entities:
state = self.get_state(entity)
if state == "on":
any = True
message += '{} ({}) = {}\n'.format(self.friendly_name(entity), entity, self.get_state(entity))
if any:
message = "В доме остались работать следующие устройства:\n" + message
self.notify(message, name = self.args['notify'])
else:
self.notify("Все устройства в доме выключены.", name = self.args['notify'])
@@ -0,0 +1,10 @@
#################################
# HOME PRESENSE #
#################################
report_action:
module: report_action
class: ReportByPresence
entity: group.report_entities
notify: telegram
global_dependencies:
- globals
@@ -0,0 +1,134 @@
import appdaemon.plugins.hass.hassapi as hass
#
# Tags counter
# This app count 'at home' BLE tags and set home presence state.
# tag names must start from 'tag_'. Room presence sensors will be generated.
#
# Args:
# tag_prefix = tag prefix to search (in my case 'sensor.tag_')
# sensor_prefix = sensor prefix for room presence sensor (ex: if prefix 'sensor.presence_', than sensor name will be 'sensor.presence_hall')
# rooms = rooms list, where tags will be discovered (sensor will be created for each room)
# constraint_prefix (input_boolean) = constraint for tag (ex: input_boolean.tag_monster)
#
# Release Notes
#
# Version 1.0:
# Initial Version
class TagsPresence(hass.Hass):
tags = []
constraint_list = []
tag_by_room_cache = {}
def initialize(self):
if 'tag_prefix' not in self.args or 'sensor_prefix' not in self.args or 'rooms' not in self.args or 'constraint_prefix' not in self.args:
self.error("Please provide tag_prefix, sensor_prefix, constraint_prefix and rooms in config!")
return
self.listen_tags()
# self.listen_event(self.ha_event, "plugin_started")
self.refresh_presence_state()
# def ha_event(self, event_name, data, kwargs):
# self.run_in(self.refresh_presence_state_timer, 10)
def refresh_presence_state_timer(self, kwargs):
self.refresh_presence_state()
def sensor_trigger(self, entity, attribute, old, new, kwargs):
self.refresh_presence_state()
def constraint_trigger(self, entity, attribute, old, new, kwargs):
self.refresh_presence_state()
def listen_tags(self):
self.fill_tags_and_constraints()
for tag in self.tags:
self.listen_state(self.sensor_trigger, tag)
for constraint in self.constraint_list:
self.listen_state(self.constraint_trigger, constraint)
def refresh_presence_state(self):
tags_by_room = self.get_tags_state_by_room(self.tags, self.constraint_list)
tags_by_person = self.get_tags_state_by_person(self.tags, self.constraint_list)
homePresence = 'off'
for room in self.args['rooms']:
sensor = '{}{}'.format(self.args['sensor_prefix'],room)
if room in tags_by_room and len(tags_by_room[room]) > 0:
self.create_sensor(sensor, 'on')
homePresence = 'on'
else:
self.create_sensor(sensor, 'off')
for person in tags_by_person:
if len(tags_by_person[person])>0 and tags_by_person[person] != 'not home':
attr = self.get_state(person, attribute='all').get('attributes', None)
# self.log(attr)
# attr['state'] = tags_by_person[person][0]
self.set_state(person, state = tags_by_person[person][0], attributes = attr)
homeSensor = '{}{}'.format(self.args['sensor_prefix'],'home')
self.create_sensor(homeSensor, homePresence)
def get_tags_state_by_room(self, tags, constraint_list):
tags_by_room = {}
for tag in tags:
constraint = '{}{}'.format(self.args['constraint_prefix'],tag.replace(self.args['tag_prefix'],''))
if constraint in constraint_list and not self.constrain_input_boolean(constraint):
continue
room = self.get_state(tag)
if room not in tags_by_room:
tags_by_room[room] = []
tags_by_room[room].append(tag)
return tags_by_room
def get_tags_state_by_person(self, tags, constraint_list):
tags_by_person = {}
for tag in tags:
constraint = '{}{}'.format(self.args['constraint_prefix'],tag.replace(self.args['tag_prefix'],''))
if constraint in constraint_list and not self.constrain_input_boolean(constraint):
continue
tagName = tag.replace(self.args['tag_prefix'],'')
if '_' in tagName:
tagName = tagName.split('_')[0]
person = '{}{}'.format(self.args['person_prefix'], tagName)
person_state = self.get_state(person)
if person_state != None:
# self.log('{}: {}'.format(person, person_state))
if person not in tags_by_person:
tags_by_person[person] = []
tags_by_person[person].append(self.get_state(tag))
return tags_by_person
# if person_state in self.get_state('person'):
# self.log('{} found'.format(person))
# else:
# self.log('{} not found'.format(person))
# self.log('{}: {}'.format(person, person_state))
# tag_state = self.get_state(tag)
# if room not in tags_by_room:
# tags_by_room[room] = []
# tags_by_room[room].append(tag)
# return tags_by_room
def fill_tags_and_constraints(self):
self.tags = []
self.constraint_list = []
devices = self.get_state()
for tag in devices:
if not tag.startswith(self.args['tag_prefix']):
continue
self.tags.append(tag)
constraint = '{}{}'.format(self.args['constraint_prefix'],tag.replace(self.args['tag_prefix'],''))
if constraint in devices and constraint not in self.constraint_list:
self.constraint_list.append(constraint)
def create_sensor(self, entity_id, state, attributes = None):
current_state = self.get_state(entity_id)
if (current_state == state):
return
# self.log('Updating {} state ({}->{})'.format(entity_id, current_state, state))
if attributes == None:
attributes = {}
attributes['description'] = 'Created and updated from appdaemon (tags_presence)'
self.set_state(entity_id, state = state, attributes = attributes)
@@ -0,0 +1,17 @@
#################################
# HOME PRESENSE #
#################################
tags_presence:
module: tags_presence
class: TagsPresence
tag_prefix: sensor.tag_
sensor_prefix: sensor.presence_
constraint_prefix: input_boolean.tag_
person_prefix: person.
rooms:
- hall
- balcony
- kitchen
- mainroom
global_dependencies:
- globals
@@ -0,0 +1,108 @@
import appdaemon.plugins.hass.hassapi as hass
import datetime
#
# Water control application.
# Controls water valve when all leave and no water needed devices is working.
#
# Args:
# water_valve = water valve entity
# notify - notify entity to send message
# constraint (optional) = input_boolean to enable\disable this automation
#
# Release Notes
#
# Version 1.0:
# Initial Version
class WaterValveControl(hass.Hass):
standby_power_limit = 15
current_wait_cycle = 0
wait_cycles = 3
timer = None
listen_event_handle_list = []
timers = []
def initialize(self):
if 'water_valve' not in self.args or 'water_devices' not in self.args or 'notify' not in self.args:
self.error("Please provide water_valve, water_devices and notify in config!")
return
self.listen_event_handle_list.append(self.listen_event(self.away_mode, "away_mode"))
self.listen_event_handle_list.append(self.listen_event(self.return_home_mode, "return_home_mode"))
self.log(self.get_turned_on_devices())
def return_home_mode(self, event_id, event_args, kwargs):
self.timer = None
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
self.turn_on(self.args['water_valve'])
for device in self.args['water_devices']:
self.turn_on(device)
self.cancel_current_timer()
def away_mode(self, event_id, event_args, kwargs):
self.timer = None
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
powered_on = self.get_turned_on_devices()
if (len(powered_on) > 0):
device_names = ""
for device in powered_on:
if device_names != '':
device_names += ", "
device_names += self.friendly_name(device)
self.notify('В доме остались требующие воды устройства ({}), подача воды не будет отключена.'.format(device_names), name = self.args['notify'])
self.cancel_current_timer()
self.timers.append(self.run_every(self.wait_when_device_is_done, self.datetime()+datetime.timedelta(seconds=5), 5*60))
else:
self.turn_off(self.args['water_valve'])
for device in self.args['water_devices']:
self.turn_off(device)
def wait_when_device_is_done(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
powered_on = self.get_turned_on_devices()
self.log('still powered on: {}'.format(powered_on))
if len(powered_on) == 1:
self.current_wait_cycle = 0
elif len(powered_on) == 0 and self.current_wait_cycle<self.wait_cycles:
self.log("Current wait cycle: {}".format(self.current_wait_cycle))
self.current_wait_cycle = self.current_wait_cycle+1
elif len(powered_on) == 0 and self.current_wait_cycle>=self.wait_cycles:
self.current_wait_cycle = 0
self.notify('Устройства требующие воды закончили свою работу, отключаю подачу воды.', name = self.args['notify'])
self.turn_off_water_devices()
self.cancel_current_timer()
def turn_off_water_devices():
self.log("Turning off water devices.")
self.turn_off(self.args['water_valve'])
for device in self.args['water_devices']:
self.turn_off(device)
def get_turned_on_devices(self):
powered_on_devices = []
devices = self.args['water_devices']
for device in devices:
state = self.get_state(device)
attributes = self.get_state(device, attribute = "all")
if attributes == None:
continue
if 'attributes' in attributes:
attributes = attributes['attributes']
if 'load_power' not in attributes:
continue
load_power = attributes['load_power']
self.log('{} state: {}, load power is: {}'.format(device, state, load_power))
if state == 'off' or load_power < self.standby_power_limit:
continue
powered_on_devices.append(device)
return powered_on_devices
def cancel_current_timer(self):
for timer in self.timers:
self.log('canceling timer')
self.cancel_timer(timer)
self.timers = []
@@ -0,0 +1,14 @@
#################################
# HOME PRESENSE #
#################################
water_action:
module: water_action
class: WaterValveControl
water_valve: switch.water_valve
notify: telegram
constraint: input_boolean.water_control
water_devices:
- switch.plug_158d0001a68c64 # Посудомойка
- switch.plug_158d0001a4cfb4 # стиралка
global_dependencies:
- globals
@@ -0,0 +1,63 @@
import appdaemon.plugins.hass.hassapi as hass
#
# Water leak detector
# close water valve and send a report
#
# Args:
#
# sensor = water leak sensor group
# valve = water valve - app will close it, when leak is detected
# notify = notify name to send notification
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class WaterLeak(hass.Hass):
ringtone = 2
gw_mac = ''
sensors = []
def initialize(self):
if 'sensor' not in self.args or 'valve' not in self.args or 'notify' not in self.args:
self.error("Please provide sensor, valve and notify in config!")
return
self.sensors = self.get_water_leak_sensors()
self.log("leak sensors: {}".format(self.sensors))
for sensor in self.sensors:
self.listen_state(self.water_leak, sensor)
def get_water_leak_sensors(self):
sensors = []
devices = self.get_state()
for sensor in devices:
if not sensor.startswith(self.args['sensor_prefix']):
continue
sensors.append(sensor)
return sensors
def water_leak(self, entity, attribute, old, new, kwargs):
if new == "on":
self.send_alarm()
def send_alarm(self):
self.log('water leak alarm triggered!')
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
#close water valve
self.turn_off(self.args['valve'])
## to ensure that water is closed. It is important!!!
self.call_service("mqtt/publish", topic = 'home/watercontrol/bath/valve/set', payload = 'close')
self.call_service("xiaomi_aqara/play_ringtone", gw_mac=self.gw_mac, ringtone_id=self.ringtone, ringtone_vol=100)
message = "ВНИМАНИЕ! В ваше отстутствие сработал датчик протечки воды!!! Выключаю подачу воды."
self.notify(message, name = self.args['notify'])
message = "Состояние датчиков протечки:\n"
for entity_id in self.sensors:
message += '{} ({}) = {}\n'.format(self.friendly_name(entity_id), entity_id, self.get_state(entity_id))
self.notify(message, name = self.args['notify'])
@@ -0,0 +1,14 @@
#################################
# WATER LEAK #
#################################
water_leak:
module: water_leak
class: WaterLeak
constraint: input_boolean.water_leak_control
sensor: group.water_leak_sensors
valve: switch.water_valve
sensor_prefix: binary_sensor.water_leak_
notify: telegram
gw_mac: !secret aqara1_mac
global_dependencies:
- globals
+365
View File
@@ -0,0 +1,365 @@
import sys
sys.path.insert(0, '/emp_mos_api')
import globals
import datetime as dt
from automation import Automation, Base
from datetime import datetime, tzinfo, timedelta
from emp_mos_api.mos import MosAPI, Watercounter, Water, EmpServerException
#from mos_lib import MosAPI, Watercounter, Water, EmpServerException
class WaterCounterSender(Automation):
timezone = '+03:00'
def initialize(self):
super().initialize()
self.run_daily(self.send_mosru_counters, dt.time(12, 00, 0))
def send_mosru_counters(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
self.log('Starting sending counters value to mos.ru')
new_values = []
self.app.update_watercounters()
water_hot = self.app.water_hot
water_cold = self.app.water_cold
counters = [water_hot, water_cold]
for counter in counters:
counter_type = counter['type']
checkup = counter['checkup'].replace(self.timezone,'')
checkup_date = datetime.strptime(checkup, '%Y-%m-%d')
# подходит дата поверки
if checkup_date < datetime.now():
self.error('Counter {} checkup date is expired at {}!'.format(counter['counterId'], checkup))
continue
our_sensor = ""
if counter_type == 1:
our_sensor = self.entity_ids['cold']
elif counter_type == 2:
our_sensor = self.entity_ids['hot']
real_value = int(float(self.get_state(our_sensor)))
self.log("Updating counter {} ({}) to real value: {}".format(counter['counterId'], our_sensor, real_value))
new_values.append(Watercounter.serialize_for_send(counter, real_value))
if new_values:
try:
self.app.send_watercounters(new_values)
self.log('Counters values was sent to mos.ru')
except EmpServerException as err:
self.log('error sending: {}'.format(err))
class PowerCounterSender(Automation):
timezone = '+03:00'
def initialize(self):
super().initialize()
if 'power_total' not in self.entity_ids:
self.log('Please specify power_total for PowerCounterSender.')
return
self.run_daily(self.send_mosru_counters, dt.time(14, 00, 0))
# self.send_mosru_counters("")
def send_mosru_counters(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
today = datetime.today()
if today.day < 15 or today.day > 26:
self.log("We can send power values between days 15 and 26.")
return
power_total = round(float(self.get_state(self.entity_ids['power_total'])))
period = datetime.now().strftime('%Y-%m-%d')
if 'threshold' in self.args and 'power_mosenergosbyt_total' in self.args:
power_mosenergosbyt_total = float(self.get_state(self.args['power_mosenergosbyt_total']))
threshold = self.args['threshold']
diff = power_total-power_mosenergosbyt_total
if diff<threshold:
self.log('power_total: {}, power_mosenergosbyt_total: {}, diff: {}, threshold: {}'.format(power_total, power_mosenergosbyt_total, diff, threshold))
self.log('skipping send')
return
self.log('Starting sending power counters value ({}) to mosru.'.format(power_total))
new_values = [{'counter_id': 'T1', 'indication': power_total, 'period': period}]
try:
result = self.app.send_electrocounters(new_values)
self.log(result)
except EmpServerException as err:
self.log('error sending: {}'.format(err))
class WaterCounterSensor(Automation):
timezone = '+03:00'
def initialize(self):
super().initialize()
self.timer = self.run_every(self.update_sensors, self.datetime()+timedelta(seconds=10), 1*60*60)
def update_sensors(self, args) -> None:
self.log('Updating water counters')
self.app.update_watercounters()
if 'hot' in self.entity_ids:
water_hot = self.app.water_hot
self.create_water_entity(self.entity_ids['hot'], water_hot)
if 'cold' in self.entity_ids:
water_cold = self.app.water_cold
self.create_water_entity(self.entity_ids['cold'], water_cold)
def create_water_entity(self, entity_id, counter):
indication_max = 0
indication_date = ""
for indication in counter['indications']:
current = float(indication['indication'])
if (current > indication_max):
indication_max = current
indication_date = indication['period']
indication_date = indication_date.replace(self.timezone,'')
counter_type = counter['type']
checkup = counter['checkup']
type_desc = ""
type_desc_ru = ""
if counter_type == 1:
type_desc = "cold"
type_desc_ru = "Холодная"
elif counter_type == 2:
type_desc = "hot"
type_desc_ru = "Горячая"
icon = "mdi:av-timer"
checkup = checkup.replace(self.timezone,'')
checkup_date = datetime.strptime(checkup, '%Y-%m-%d')
# подходит дата поверки
if (checkup_date - timedelta(days=31)) < datetime.now():
icon = "mdi:alert-octagon"
attributes = {}
attributes['unit_of_measurement'] = 'm3'
attributes['date'] = indication_date
attributes['checkup'] = checkup
attributes['icon'] = icon
attributes['friendly_name'] = "{}(mos.ru)".format(type_desc_ru)
attributes['description'] = 'Created and updated from appdaemon ({})'.format(__name__)
self.set_state(entity_id, state = indication_max, attributes = attributes)
class PowerCounterSensor(Automation):
timezone = '+03:00'
def initialize(self):
super().initialize()
self.timer = self.run_every(self.update_sensors, self.datetime()+timedelta(seconds=30), 1*60*60)
def update_sensors(self, args) -> None:
self.log('Updating power counters')
self.app.update_power()
self.log('power data: {}'.format(self.app._power_counters))
if 'power_total' in self.entity_ids:
power_total = self.app.power_total
self.create_power_entity(self.entity_ids['power_total'], power_total, 'kWh', 'Счетчик')
if 'power_balance' in self.entity_ids:
power_balance = self.app.power_balance
self.create_power_entity(self.entity_ids['power_balance'], power_balance, '₽', 'Баланс')
def create_power_entity(self, entity_id, value, unit, description):
icon = "mdi:av-timer"
attributes = {}
attributes['unit_of_measurement'] = unit
attributes['icon'] = icon
attributes['friendly_name'] = "{}(mos.ru)".format(description)
attributes['description'] = 'Created and updated from appdaemon ({})'.format(__name__)
self.set_state(entity_id, state = value, attributes = attributes)
class EpdBalanceSensor(Automation):
def initialize(self):
super().initialize()
self.timer = self.run_every(self.update_sensors, self.datetime()+timedelta(seconds=40), 1*60*60)
def update_sensors(self, args) -> None:
if 'epd_balance' in self.entity_ids:
self.app.update_epd()
epd_balance = self.app.epd_balance
epd_description = self.app.epd_description
self.log('Current epd balance: {}'.format(epd_balance))
entity_id = self.entity_ids['epd_balance']
attributes = {
'unit_of_measurement': '₽',
'icon': 'mdi:bank-transfer',
'date': datetime.now().strftime('%d.%m.%Y'),
'unpaid': epd_description,
'friendly_name': 'Баланс (ЕПД)',
'description': 'Created and updated from appdaemon ({})'.format(__name__)
}
self.set_state(entity_id, state = epd_balance, attributes = attributes)
class MosruBase(Base):
_api = None
_flat_id = None
_guid='4421a3fb0567714b27dec84d3c3862dc'
_app_version = '3.5.0.195 (102)'
_user_agent = 'okhttp/3.8.1'
_dev_user_agent = 'Android'
_timezone = '+03:00'
def initialize(self):
super().initialize()
if not globals.check_properties(self,['login', 'pwd', 'token', 'paycode', 'session_id']):
return
self._api = MosAPI(token=self.properties['token'],
user_agent=self._user_agent,
guid=self._guid,
dev_user_agent=self._dev_user_agent,
dev_app_version=self._app_version,
timeout=6)
self._set_flat_id()
def _set_flat_id(self):
''' Set flat id by properties or pay code '''
if 'flat_id' in self.properties:
self._flat_id = self.properties['flat_id']
return
if (self._flat_id != None):
return
self._check_login()
flats = self._api.get_flats()
for flat in flats:
if (int(flat['paycode']) != self.properties['paycode']):
continue
self._flat_id = flat['flat_id']
def _check_login(self):
self._api.set_session(self.properties['session_id'])
# if not self._api.is_active():
# self._api.login(self.properties['login'], self.properties['pwd'])
class MosruPower(MosruBase):
_power_counters = None
@property
def power_total(self):
if (self._power_counters == None):
self.update_power()
return self._power_counters['zones'][0]['value']
@property
def power_balance(self):
if (self._power_counters == None):
self.update_power()
return float(self._power_counters['balance'])
def update_power(self) -> None:
self._check_login()
self._power_counters = self._api.get_electrocounters(self._flat_id)
def send_electrocounters(self, new_values) -> None:
self._check_login()
self._api.send_electrocounters(self._flat_id, new_values)
class MosruWater(MosruBase):
_water_counters = None
@property
def water_hot(self):
"""Return the current state of counter."""
if (self._water_counters == None):
self.update_watercounters()
hots_json = list(filter(lambda x: x['type'] == Water.HOT, self._water_counters))
return hots_json[0]
@property
def water_cold(self):
"""Return the current state of counter."""
if (self._water_counters == None):
self.update_watercounters()
colds_json = list(filter(lambda x: x['type'] == Water.COLD, self._water_counters))
return colds_json[0]
@property
def water_hot_value(self):
hot_value = Watercounter.last_value(self.water_hot)
return hot_value
@property
def water_cold_value(self):
cold_value = Watercounter.last_value(self.water_cold)
return cold_value
def send_watercounters(self, new_values) -> None:
self._check_login()
self._api.send_watercounters(self._flat_id, new_values)
def update_watercounters(self) -> None:
self._check_login()
self._water_counters = self._api.get_watercounters(self._flat_id)['counters']
class MosruEpd(MosruBase):
_epd_data = None
@property
def epd_description(self) -> str:
if (self._epd_data == None):
self.update_epd()
unpaid = []
for date in self._epd_data:
epd = self._epd_data[date]
if epd['payment_status'] == 'PAID':
continue
unpaid.append('{}: {}'.format(date, epd['amount']))
return ", ".join(unpaid)
@property
def epd_balance(self) -> float:
"""Return the current state of counter."""
if (self._epd_data == None):
self.update_epd()
balance = 0
for date in self._epd_data:
epd = self._epd_data[date]
epd_is_paid = epd['payment_status'] == 'PAID'
if epd_is_paid:
continue
balance = balance - float(epd['amount'].replace(',','.'))
return balance
def update_epd(self) -> None:
self._check_login()
data = {}
dateEnd = datetime.now().date()
dateStart = dateEnd - timedelta(6*365/12)
date_start = dateStart.strftime('%d.%m.%Y')
date_end = dateEnd.strftime('%d.%m.%Y')
epds = self._api.get_epd(self._flat_id, date_start, date_end)
for i in range(0, 3): #retries
if epds != []:
break
epds = self._api.get_epd(self._flat_id, date_start, date_end)
if len(epds) == 0:
self.log('epd not found for date: {}'.format(date_str))
return
for epd in epds:
date_str = epd['period']
data[date_str] = epd
# epd_total = epd_first['amount']
# epd_is_paid = epd_first['is_paid']
# self.log(" - Дата: {}, сумма: {}, оплачен: {}.".format(date_str, epd_total, epd_is_paid))
self._epd_data = data
class MosruClient(MosruEpd, MosruWater, MosruPower):
def initialize(self):
super().initialize()
self.log('Flat id is: {}'.format(self._flat_id))
# self.log('epd balance: {}'.format(self.epd_balance))
# self.log('cold: {}'.format(self.water_cold))
# self.log('hot: {}'.format(self.water_hot))
# self.log('power_counter: {}, balance: {}'.format(self.power_total, self.power_balance))
+127
View File
@@ -0,0 +1,127 @@
#################################
# MOS.RU DATA RETRIEVER #
#################################
mosru_client:
class: MosruClient
module: mosru
properties:
login: !secret mosru_login
pwd: !secret mosru_pwd
token: !secret mosru_token
paycode: !secret mosru_paycode
flat_id: !secret mosru_flat_id
session_id: !secret mosru_session
global_dependencies:
- globals
epd_sensor:
class: EpdBalanceSensor
module: mosru
entity_ids:
epd_balance: sensor.epd_balance
app: mosru_client
dependencies:
- mosru_client
global_dependencies:
- globals
power_sensor:
class: PowerCounterSensor
module: mosru
entity_ids:
power_balance: sensor.power_mosenergosbyt_balance
power_total: sensor.power_mosenergosbyt_total
app: mosru_client
dependencies:
- mosru_client
global_dependencies:
- globals
water_sensor:
class: WaterCounterSensor
module: mosru
entity_ids:
hot: sensor.counter_mosru_hot
cold: sensor.counter_mosru_cold
app: mosru_client
dependencies:
- mosru_client
global_dependencies:
- globals
water_sender:
class: WaterCounterSender
module: mosru
entity_ids:
hot: sensor.counter_hot
cold: sensor.counter_cold
app: mosru_client
dependencies:
- mosru_client
global_dependencies:
- globals
power_sender:
class: PowerCounterSender
module: mosru
power_mosenergosbyt_total: sensor.power_mosenergosbyt_total
threshold: 100
entity_ids:
power_total: sensor.power_main_total
app: mosru_client
dependencies:
- mosru_client
global_dependencies:
- globals
#---------------------------------------------------------
mosru_client_belorechenskaya:
class: MosruClient
module: mosru
properties:
login: !secret mosru_login
pwd: !secret mosru_pwd
token: !secret mosru_token
paycode: !secret mosru_belorechenskaya_paycode
flat_id: !secret mosru_belorechenskaya_flat_id
session_id: !secret mosru_session
global_dependencies:
- globals
epd_sensor_belorechenskaya:
class: EpdBalanceSensor
module: mosru
entity_ids:
epd_balance: sensor.epd_balance_belorechenskaya
app: mosru_client_belorechenskaya
dependencies:
- mosru_client_belorechenskaya
global_dependencies:
- globals
power_sensor_belorechenskaya:
class: PowerCounterSensor
module: mosru
entity_ids:
power_balance: sensor.power_mosenergosbyt_belorechenskaya_balance
power_total: sensor.power_mosenergosbyt_belorechenskaya_total
app: mosru_client_belorechenskaya
dependencies:
- mosru_client_belorechenskaya
global_dependencies:
- globals
water_sensor_belorechenskaya:
class: WaterCounterSensor
module: mosru
entity_ids:
hot: sensor.counter_mosru_belorechenskaya_hot
cold: sensor.counter_mosru_belorechenskaya_cold
app: mosru_client_belorechenskaya
dependencies:
- mosru_client_belorechenskaya
global_dependencies:
- globals
@@ -0,0 +1,30 @@
import appdaemon.plugins.hass.hassapi as hass
# from automation import Automation # type: ignore
"""
Monitor events and output changes to the verbose_log. Nice for debugging purposes.
Arguments:
- events: List of events to monitor
"""
class Monitor(hass.Hass):
def initialize(self) -> None:
# super().initialize()
self.listen_event_handle_list = []
events = self.args['events']
if events != None:
for event in self.split_device_list(self.args["events"]):
self.log('watching event "{}" for state changes'.format(event))
self.listen_event_handle_list.append(self.listen_event(self.changed, event))
if len(self.listen_event_handle_list) == 0:
self.log('watching all events for state changes')
self.listen_event_handle_list.append(self.listen_event(self.changed))
def changed(self, event_name, data, kwargs):
self.log(event_name + ': ' + str(data))
def terminate(self):
for listen_event_handle in self.listen_event_handle_list:
self.cancel_listen_event(listen_event_handle)
@@ -0,0 +1,6 @@
# eventMonitor:
# module: eventMonitor
# class: Monitor
# global_dependencies:
# - automation
# events:
@@ -0,0 +1,67 @@
import appdaemon.plugins.hass.hassapi as hass
import appdaemon.plugins.mqtt.mqttapi as mqtt
import globals
import json
import random
import time
import datetime
class HappyBubblesToDeviceTracker(mqtt.Mqtt, hass.Hass):
base_topic = "happy-bubbles/presence/ha"
tag_timers = {}
def initialize(self) -> None:
self.log('Initialize HappyBubblesToDeviceTracker')
self.mqtt_subscribe('{}/#'.format(self.base_topic), namespace='mqtt')
self.listen_event(self.event_listener, 'MQTT_MESSAGE', namespace='mqtt')
def event_listener(self, event_name, data, kwargs):
topic = data.get('topic', '')
if self.base_topic not in topic:
return
location = topic.replace('{}/'.format(self.base_topic), "")
payload = json.loads(data['payload']) if data.get('payload') else {}
# self.log('[{}] topic: {}, payload: {}'.format(event_name, topic, payload))
id = payload.get('id', None)
name = payload.get('name', None)
distance = payload.get('distance', None)
# devices = self.get_state()
# device_name = "device_tracker.tag_ble_{}".format(id)
# if device_name in devices:
# self.log('tag found in devices')
self.move_tag(id, isHome=True)
self.restart_timer(id)
self.log('Beacon {} ({}) moved to {} (distance: {}).'.format(name, id, location, distance))
def restart_timer(self, id):
if id in self.tag_timers:
self.cancel_timer(self.tag_timers[id])
self.tag_timers[id] = self.run_in(self.tag_away_timer_callback, 60, id=id)
def tag_away_timer_callback(self, kwargs):
id = kwargs.get('id', None)
if id == None:
self.log('id is forgotten')
return
self.log('awaying tag:{}'.format(id))
self.move_tag(id, isHome=False)
def move_tag(self, id, isHome = False):
hb_topic = "owntracks/tag/ble_{}".format(id)
tst = int(round(time.time(),0))
payload = '{"_type":"location","acc":18,"alt":0,"batt":100,"lat":55.7899036,"lon":37.6595347,"tst":1579857481}'
jsobj = json.loads(payload)
jsobj['tst'] = tst
if isHome:
jsobj['lat'] = self.args['home_lat']
jsobj['lon'] = self.args['home_lon']
else:
jsobj['lat'] = 0
jsobj['lon'] = 0
payload = json.dumps(jsobj)
self.mqtt_publish(hb_topic, payload, qos = 0, retain = False, namespace='mqtt')
@@ -0,0 +1,8 @@
# happyBubblesToDeviceTracker:
# module: happyBubblesToDeviceTracker
# class: HappyBubblesToDeviceTracker
# global_dependencies:
# - globals
# constrain: input_boolean.constraint_test
# home_lat: !secret home_latitude
# home_lon: !secret home_longitude
@@ -0,0 +1,37 @@
import appdaemon.plugins.hass.hassapi as hass
import appdaemon.plugins.mqtt.mqttapi as mqtt
import globals
import json
import random
"""
Monitor events and output changes to the verbose_log. Nice for debugging purposes.
Arguments:
- events: List of events to monitor
"""
class RassToHappyBubbles(mqtt.Mqtt):
base_topic = "home/presence"
def initialize(self) -> None:
self.log('Initialize RassToHappyBubbles')
self.mqtt_subscribe('{}/#'.format(self.base_topic), namespace='mqtt')
self.listen_event(self.event_listener, 'MQTT_MESSAGE', namespace='mqtt')
def event_listener(self, event_name, data, kwargs):
topic = data.get('topic', '')
if self.base_topic not in topic:
return
#array = ['hall', 'balcony']
#random.shuffle(array)
#device=array[0]
device = topic.replace('{}/'.format(self.base_topic), "")
payload = json.loads(data['payload']) if data.get('payload') else {}
id = payload.get('id', None)
hb_topic="happy-bubbles/ble/{}/raw/{}".format(device,id)
payload['hostname'] = device
payload['mac'] = payload.get('id', '')
payload_data = json.dumps(payload)
self.mqtt_publish(hb_topic, payload_data, qos = 0, retain = False, namespace='mqtt')
# self.log('publishing to: {}: {}'.format(hb_topic, payload_data))
@@ -0,0 +1,6 @@
# rassToHappybubbles:
# module: rassToHappybubbles
# class: RassToHappyBubbles
# global_dependencies:
# - globals
# constrain: input_boolean.constraint_test
+20
View File
@@ -0,0 +1,20 @@
import appdaemon.plugins.hass.hassapi as hass
import os
"""
Monitor events and output changes to the verbose_log. Nice for debugging purposes.
Arguments:
- events: List of events to monitor
"""
class Test(hass.Hass):
notify = None
def initialize(self) -> None:
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):
if new != "" and new != "unknown":
self.log("doorbell call")
self.toggle("switch.0x00158d00020f3a29_switch")
+5
View File
@@ -0,0 +1,5 @@
test:
module: test
class: Test
dependencies:
- notify