first commit
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -0,0 +1,5 @@
|
||||
test:
|
||||
module: test
|
||||
class: Test
|
||||
dependencies:
|
||||
- notify
|
||||
Reference in New Issue
Block a user