diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7db96ae --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +python: + - '3.5' +install: + - 'pip3 install homeassistant' + - 'mv secrets.yaml.sample secrets.yaml' +script: + - 'cd hass/settings/ && hass -c . --script check_config | tee check.output' + - '! less check.output | grep -q ERROR' + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b78878 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +#SHELL := /bin/bash + +ARGS = `arg="$(filter-out $@,$(MAKECMDGOALS))" && echo $${arg:-${1}}` + +lint: + @echo "Checking python syntax with pyflakes" + @pyflakes appdaemon + +update_secrets_sample: + @echo "Masking passwords..." + @cat secrets.yaml | sed "s/\:.*/\: xxxxxxxxx/g" > secrets.yaml.sample #mask passwords + @sed -i "s/latitude:.*/latitude: 1.0/g" secrets.yaml.sample #must be a number + @sed -i "s/longitude:.*/longitude: 1.0/g" secrets.yaml.sample #must be a number + @sed -i "s/aqara1_mac:.*/aqara1_mac: xxxxxxxxxxxx/g" secrets.yaml.sample #len 12 + @sed -i "s/aqara1_key:.*/aqara1_key: xxxxxxxxxxxxxxxx/g" secrets.yaml.sample #len 16 + @sed -i "s/mirobot_key:.*/mirobot_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/g" secrets.yaml.sample #len 32 + @sed -i "s/powerstrip_key:.*/powerstrip_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/g" secrets.yaml.sample #len 32 + @sed -i "s/telegram_chat:.*/telegram_chat: 0/g" secrets.yaml.sample #must be a number + @sed -i "s/timezone:.*/timezone: Europe\/London/g" secrets.yaml.sample #must be valid + +commit: update_secrets_sample + git add . + git commit -m "$(call ARGS,\"updating configuration\")" + git push + +%: + @: diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a3455b --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# home-assistant-config +[![Build Status](https://travis-ci.org/monster1025/hass-config.svg?branch=master)](https://travis-ci.org/monster1025/hass-config) + +My Home Assistant Configuration + + +Device List: +- Xiaomi Gateway (sensors: motion, door, plug, buttons, temperature\humidity). +- Xiaomi scale (via BLE app). +- Xiaomi robot vacuum (v1). +- Broadlink (sp-mini, rm mini). +- ESP8266 (water_valve and water counters). +- BLE Presence control (raspberry=room assistant, nut and miband as tags). +- Work presence (apacs integration). +- Yeelight WiFi bulbs. +- SOnOff relay for light. +- Yandex.Traffic +- NarodMon for 'external sensors' diff --git a/appdaemon/docker/Dockerfile b/appdaemon/docker/Dockerfile new file mode 100644 index 0000000..77e7e39 --- /dev/null +++ b/appdaemon/docker/Dockerfile @@ -0,0 +1,10 @@ +ARG VERSION +FROM acockburn/appdaemon:${VERSION} + +#log encoding temporary fix +RUN sed -i -e 's/safe_enc = lambda s: str(s).encode("utf-8", "replace").decode("ascii", "replace")/safe_enc = lambda s: str(s)/g' /usr/src/app/appdaemon/utils.py + +RUN pip3 install \ + emp_mos_api==0.9 \ + sqlalchemy \ + mysqlclient \ No newline at end of file diff --git a/appdaemon/settings/appdaemon.yaml b/appdaemon/settings/appdaemon.yaml new file mode 100644 index 0000000..683d474 --- /dev/null +++ b/appdaemon/settings/appdaemon.yaml @@ -0,0 +1,17 @@ +log: + logfile: STDOUT + errorfile: STDERR + +appdaemon: + threads: 10 + api_port: 5000 + api_key: !secret ha_password + time_zone: !secret timezone + narodmon_key: !secret narodmon_key + + plugins: + HASS: + type: hass + ha_url: !secret ha_url + #ha_key: !secret ha_password + token: !secret ha_token \ No newline at end of file diff --git a/appdaemon/settings/apps/alarm_clock/alarm_clock.py b/appdaemon/settings/apps/alarm_clock/alarm_clock.py new file mode 100644 index 0000000..609c7ed --- /dev/null +++ b/appdaemon/settings/apps/alarm_clock/alarm_clock.py @@ -0,0 +1,74 @@ +import appdaemon.plugins.hass.hassapi as hass +from datetime import datetime, date, timedelta + +# +# Basic alarm clock. +# It will trigger 'alarmclock_trigger' event on alarm time. +# +# Args: +# +# time = input_datetime for time (alarm time). +# state = input_boolean for alarm state (on/off) +# days = list of days when alarm is active +# prealarm_delta (optional, in minutes, default=15) = time befor main alarm, when 'alarmclock_prealarm' event will fire (you can trigger slow light on before main alarm sound). +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class AlarmClock(hass.Hass): + alarmtimer = None + prealarmtimer = None + prealarm_delta = 15 + def initialize(self): + if "days" not in self.args or "time" not in self.args or "state" not in self.args: + self.error("Please provide days, time and state in config!") + return + if 'prealarm_delta' in self.args: + self.prealarm_delta = self.args['prealarm_delta'] + self.log('Alarm will trigger in days: {}.'.format(self.args['days'])) + self.listen_state(self.alarm_time_changed, self.args['time']) + self.listen_state(self.alarm_time_changed, self.args['state']) + self.alarm_time_changed(None, None, None, None, None) + + def alarm_time_changed(self, entity, attribute, old, new, kwargs): + #cacnel old timer + self.cancel_current_timer() + + alarm_state = self.get_state(self.args['state']) + if alarm_state == 'off': + self.log('Turning off alarm.') + return + + #set new timer + time_string = self.get_state(self.args["time"]) + if time_string == "unknown": + return + alarm_time = self.parse_time(time_string) + self.run_timer(alarm_time) + + def run_timer(self, alarm_time): + prealarm_time = (datetime.combine(date.min, alarm_time) - timedelta(0, self.prealarm_delta * 60)).time() + self.alarmtimer = self.run_daily(self.alarmclock_trigger, alarm_time) + self.prealarmtimer = self.run_daily(self.alarmclock_prealarm, prealarm_time) + self.log("Alarm was set to {}, prealarm: {}.".format(alarm_time, prealarm_time)) + + def cancel_current_timer(self): + if self.alarmtimer != None and self.prealarmtimer != None: + self.cancel_timer(self.alarmtimer) + self.cancel_timer(self.prealarmtimer) + + def alarmclock_trigger(self, kwargs): + if not self.constrain_days(self.args['days']): + return + self.log("Alarmclock triggered!") + self.fire_event("alarmclock_alarm") + + def alarmclock_prealarm(self, kwargs): + if not self.constrain_days(self.args['days']): + return + self.log("Pre alarmclock triggered!") + self.fire_event("alarmclock_prealarm") diff --git a/appdaemon/settings/apps/alarm_clock/alarm_clock.yaml b/appdaemon/settings/apps/alarm_clock/alarm_clock.yaml new file mode 100644 index 0000000..67f3449 --- /dev/null +++ b/appdaemon/settings/apps/alarm_clock/alarm_clock.yaml @@ -0,0 +1,22 @@ +################################# +# ALARM CLOCK # +################################# +alarmclock_weekdays: + module: alarm_clock + class: AlarmClock + state: input_boolean.alarmclock_weekdays + time: input_datetime.alarmclock_weekdays + days: mon,tue,wed,thu,fri + prealarm_delta: 15 + global_dependencies: + - globals + +alarmclock_weekend: + module: alarm_clock + class: AlarmClock + state: input_boolean.alarmclock_weekend + time: input_datetime.alarmclock_weekend + days: sat,sun + prealarm_delta: 15 + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/alarm_clock/alarm_clock_action.py b/appdaemon/settings/apps/alarm_clock/alarm_clock_action.py new file mode 100644 index 0000000..b7ad124 --- /dev/null +++ b/appdaemon/settings/apps/alarm_clock/alarm_clock_action.py @@ -0,0 +1,55 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +# +# Very special case, this will run sound 'alarm' on xiaomi gateway based on alarm clock event (alarmclock_trigger) +# and stop it when sensor became active. +# Args: +# +# sensor = sensor, which will stop alarm (bath motion sensor for me) +# ringtone = (default = 20) xiaomi ringtone id. +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class AlarmclockAction(hass.Hass): + timer = None + ringtone = 20 + volume = 10 + gw_mac = '' + def initialize(self): + if not globals.check_args(self, ["sensor","gw_mac"]): + return + self.gw_mac = self.args['gw_mac'] + if 'ringtone' in self.args: + self.ringtone = self.args['ringtone'] + self.listen_event(self.alarmclock_alarm, "alarmclock_alarm") + self.listen_event(self.alarmclock_stop, "alarmclock_stop") + self.listen_state(self.sensor_trigger, self.args['sensor']) + + def alarmclock_stop(self, event_id, event_args, kwargs): + self.sensor_trigger(None, None, None, 'on', None) + + def sensor_trigger(self, entity, attribute, old, new, kwargs): + if new == "on" and self.timer != None: + self.call_service("xiaomi_aqara/stop_ringtone", gw_mac=self.gw_mac) + self.cancel_current_timer() + + def alarmclock_alarm(self, event_id, event_args, kwargs): + self.log('Alarm triggered!') + self.volume = 10 + self.timer = self.run_every(self.timer_tick, self.datetime(), 17) + + def timer_tick(self, kwargs): + self.call_service("xiaomi_aqara/play_ringtone", gw_mac=self.gw_mac, ringtone_id=self.ringtone, ringtone_vol=self.volume) + if self.volume < 100: + self.volume = self.volume + 10 + + def cancel_current_timer(self): + if self.timer != None: + self.log('canceling timer') + self.cancel_timer(self.timer) + self.timer = None diff --git a/appdaemon/settings/apps/alarm_clock/alarm_clock_action.yaml b/appdaemon/settings/apps/alarm_clock/alarm_clock_action.yaml new file mode 100644 index 0000000..dc83170 --- /dev/null +++ b/appdaemon/settings/apps/alarm_clock/alarm_clock_action.yaml @@ -0,0 +1,8 @@ +alarm_clock_action: + module: alarm_clock_action + class: AlarmclockAction + sensor: group.bath_motion #bath motion sensor + gw_mac: !secret aqara1_mac + ringtone: 20 + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/alarm_clock/alarm_clock_prealarm.py b/appdaemon/settings/apps/alarm_clock/alarm_clock_prealarm.py new file mode 100644 index 0000000..15ebb69 --- /dev/null +++ b/appdaemon/settings/apps/alarm_clock/alarm_clock_prealarm.py @@ -0,0 +1,83 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# Pre alarm event. +# This app will slowly turn on the light after 'alarmclock_alarm' event. It will fire 15 mins(by default) before alarm. +# and stop when sensor became active. +# +# Args: +# sensor = sensor, which will stop pre-alarm (bath motion sensor for me) +# entity = entity to slowly turn on. Must support brightness control. +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class AlarmclockPrealarm(hass.Hass): + brightness = 1 + color_temp = 588 + timer = None + def initialize(self): + if "sensor" not in self.args or "entity" not in self.args: + self.error("Please provide sensor and entity in config!") + return + self.listen_event(self.alarmclock_prealarm, "alarmclock_prealarm") + self.listen_event(self.alarmclock_stop, "alarmclock_stop") + self.listen_state(self.sensor_trigger, self.args['sensor']) + + def alarmclock_stop(self, event_id, event_args, kwargs): + self.sensor_trigger(None, None, None, 'on', None) + + def sensor_trigger(self, entity, attribute, old, new, kwargs): + if new == "on": + if self.timer != None: + self.brightness = 255 + self.color_temp = 240 + self.timer_tick(None) + self.cancel_current_timer() + + def cancel_current_timer(self): + if self.timer != None: + self.log('canceling timer') + self.cancel_timer(self.timer) + self.timer = None + + def alarmclock_prealarm(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.log('Prealarm triggered!') + self.brightness = 1 + self.color_temp = 588 + self.timer = self.run_every(self.timer_tick, self.datetime(), 10) + + def timer_tick(self, kwargs): + attributes = self.get_state(self.args['entity'], attribute = "all") + if 'attributes' in attributes: + attributes = attributes['attributes'] + # we have a group here + if 'entity_id' in attributes: + for entity_id in attributes['entity_id']: + self.make_entity_action(entity_id, None) + else: + # simple entity + self.make_entity_action(self.args['entity'], attributes) + + if (self.brightness < 245): + self.brightness = self.brightness + 2 + elif (self.color_temp > 220): + self.color_temp = self.color_temp - 30 + else: + self.cancel_current_timer() + self.log("brightness: {}, color_temp: {}".format(self.brightness, self.color_temp)) + + def make_entity_action(self, entity_id, attributes): + if attributes == None: + attributes = self.get_state(entity_id, attribute = "all") + if 'attributes' in attributes: + attributes = attributes['attributes'] + #state = self.get_state(entity_id) + self.turn_on(entity_id, brightness = self.brightness, color_temp = self.color_temp) + # self.log(attributes) diff --git a/appdaemon/settings/apps/alarm_clock/alarm_clock_prealarm.yaml b/appdaemon/settings/apps/alarm_clock/alarm_clock_prealarm.yaml new file mode 100644 index 0000000..12e44cb --- /dev/null +++ b/appdaemon/settings/apps/alarm_clock/alarm_clock_prealarm.yaml @@ -0,0 +1,7 @@ +alarm_clock_prealarm: + module: alarm_clock_prealarm + class: AlarmclockPrealarm + sensor: group.bath_motion #bath motion sensor + entity: group.mainroom_color + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/api/alice.py b/appdaemon/settings/apps/api/alice.py new file mode 100644 index 0000000..97b418d --- /dev/null +++ b/appdaemon/settings/apps/api/alice.py @@ -0,0 +1,129 @@ +import appdaemon.plugins.hass.hassapi as hass +import json + +# +# Yandex.Alice integration +# +# Args: +# +# +# +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class YandexAlice(hass.Hass): + + def initialize(self): + self.register_endpoint(self.api_call) + self.sessionStorage = {} + self.log('started') + + def api_call(self, request): + # js = json.dumps(request, indent=4, sort_keys=True) + # self.log('Request: %s' % js) + + response = { + "version": request['version'], + "session": request['session'], + "response": { + "end_session": False + } + } + self.handle_dialog(request, response) + + # self.log('response: {}'.format(response['response']['text'])) + # js = json.dumps(response, indent=4, sort_keys=True) + # self.log('Response: %s' % js) + + return response, 200 + + # Функция для непосредственной обработки диалога. + def handle_dialog(self, req, res): + user_id = req['session']['user_id'] + command = req['request']['original_utterance'].lower() + command_password = req['request']['command'].lower().replace(" ", "") + password = "123456" + self.log('command: {}'.format(command)) + + if command == "ping": + res['response']['text'] = 'pong =)' + return + + if user_id not in self.sessionStorage: + self.sessionStorage[user_id] = { 'auth': False } + res['response']['text'] = 'Привет. Я могу управлять твоим домом. Для начала работы назови пароль.' + return + + if command_password != password and self.sessionStorage[user_id]['auth'] == False: + res['response']['text'] = 'Неправильный пароль: %s.' % command_password + return + + # Обрабатываем ответ пользователя. + if command_password == password and self.sessionStorage[user_id]['auth'] == False: + self.sessionStorage[user_id]['auth'] = True + res['response']['text'] = 'Вы авторизованы.' + return + + self.handle_command(req, res) + return + + # Функция возвращает две подсказки для ответа. + def handle_command(self, req, res): + user_id = req['session']['user_id'] + command = req['request']['original_utterance'].lower() + command = command.replace('попроси управление домом', '').strip() + command = command.replace('алиса', '').strip() + isAuth = self.sessionStorage[user_id]['auth'] == True + + if not isAuth: + res['response']['text'] = 'Вы не авторизованы.' + return + + if command in ["включи свет", "включить свет"]: + res['response']['text'] = 'Включаю свет.' + self.turn_on("group.all_lights") + self.turn_on("switch.kitchen_mini_light") + return + + if command in ["включи свет в зале", "включить свет в зале","включи свет в гостинной", "включить свет в гостинной"]: + res['response']['text'] = 'Свет включен.' + self.turn_on("group.mainroom_light") + return + + if command in ["выключи свет в зале", "выключить свет в зале","выключи свет в гостинной", "выключить свет в гостинной"]: + res['response']['text'] = 'Свет выключен.' + self.turn_off("group.mainroom_light") + return + + if command in ["выключи свет", "выключить свет", "выключи весь свет"]: + res['response']['text'] = 'Свет выключен.' + self.turn_off("group.all_lights") + self.turn_off("switch.kitchen_mini_light") + return + + if command in ["хватит", "алиса, хватит", "алиса хватит", "отмена"]: + res['response']['text'] = 'Хорошо' + res['response']['end_session'] = True + return + + if command in ["я ушел", "алиса я ушел"]: + self.call_service("alarm_control_panel/alarm_arm_away", entity_id="alarm_control_panel.ha_alarm") + res['response']['text'] = 'Приятной дороги.' + return + + if command in ["я вернулся", "я пришёл", "я пришел"]: + self.call_service("alarm_control_panel/alarm_disarm", entity_id="alarm_control_panel.ha_alarm") + res['response']['text'] = 'Добро пожаловать домой.' + return + + if command in ["запусти навык управление домом"]: + res['response']['text'] = 'Уже запущен.' + return + + res['response']['text'] = 'Извините, я не знаю команды %s.' % command + return \ No newline at end of file diff --git a/appdaemon/settings/apps/api/alice.yaml b/appdaemon/settings/apps/api/alice.yaml new file mode 100644 index 0000000..b85b470 --- /dev/null +++ b/appdaemon/settings/apps/api/alice.yaml @@ -0,0 +1,9 @@ +################################# +# YANDEX.ALICE # +################################# +alice: + module: alice + class: YandexAlice + global_dependencies: + - globals + \ No newline at end of file diff --git a/appdaemon/settings/apps/api/mi_scale.py b/appdaemon/settings/apps/api/mi_scale.py new file mode 100644 index 0000000..fa07758 --- /dev/null +++ b/appdaemon/settings/apps/api/mi_scale.py @@ -0,0 +1,47 @@ +import appdaemon.plugins.hass.hassapi as hass +import datetime +import globals + +# +# App recieves xiaomi scale readings, parse user weight and set sensor in home-assistant +# +# Args: +# +# sensor = generated sensor name +# friendly_name (optional) = sensor friendly name +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class MiScale(hass.Hass): + def initialize(self): + if not globals.check_args(self, ["sensor"]): + return + self.register_endpoint(self.api_call) + self.log('started') + + def api_call(self, data): + # filter only my data + prev_weight = 70 + weight_date = datetime.datetime.now() + for current in data: + if abs(current['Weight'] - prev_weight) > 5: + continue + # self.log(current) + prev_weight = current['Weight'] + weight_date = current['Time'] + + attributes = {} + attributes['datetime'] = weight_date + attributes['unit_of_measurement'] = 'kg' + attributes['description'] = 'Created and updated from appdaemon (mi_scale)' + if 'friendly_name' in self.args: + attributes['friendly_name'] = self.args['friendly_name'] + + self.set_state(self.args['sensor'], state = prev_weight, attributes = attributes) + return 'ok', 200 + diff --git a/appdaemon/settings/apps/api/mi_scale.yaml b/appdaemon/settings/apps/api/mi_scale.yaml new file mode 100644 index 0000000..8b55757 --- /dev/null +++ b/appdaemon/settings/apps/api/mi_scale.yaml @@ -0,0 +1,10 @@ +################################# +# SCALE # +################################# +mi_scale: + module: mi_scale + class: MiScale + sensor: sensor.xiaomi_scale + friendly_name: "Весы" + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/api/tags.py b/appdaemon/settings/apps/api/tags.py new file mode 100644 index 0000000..a7116f8 --- /dev/null +++ b/appdaemon/settings/apps/api/tags.py @@ -0,0 +1,34 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# App to serve tags list via api call +# +# Args: +# +# secrets_file = path to secrets file +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class TagsList(hass.Hass): + secrets_file = '/conf/secrets.yaml' + def initialize(self): + self.register_endpoint(self.api_call) + if 'secrets_file' in self.args: + self.secrets_file = self.args['secrets_file'] + + def api_call(self, data): + tags = [] + + with open(self.secrets_file) as f: + content = f.readlines() + for line in content: + if (line.startswith('tag_')): + line = line.replace('\n','') + tag = line.split(':')[1].strip() + tags.append(tag) + return tags, 200 diff --git a/appdaemon/settings/apps/api/tags.yaml b/appdaemon/settings/apps/api/tags.yaml new file mode 100644 index 0000000..c06083e --- /dev/null +++ b/appdaemon/settings/apps/api/tags.yaml @@ -0,0 +1,9 @@ +################################# +# TAGS # +################################# +tags: + module: tags + class: TagsList + secrets_file: /conf/secrets.yaml + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/apps.yaml b/appdaemon/settings/apps/apps.yaml new file mode 100644 index 0000000..f6f0415 --- /dev/null +++ b/appdaemon/settings/apps/apps.yaml @@ -0,0 +1,28 @@ +################################################################# +## Global +################################################################# +global_modules: + - globals + - automation + +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 \ No newline at end of file diff --git a/appdaemon/settings/apps/automation.py b/appdaemon/settings/apps/automation.py new file mode 100644 index 0000000..06d0f06 --- /dev/null +++ b/appdaemon/settings/apps/automation.py @@ -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) diff --git a/appdaemon/settings/apps/climate/climate.old_py b/appdaemon/settings/apps/climate/climate.old_py new file mode 100644 index 0000000..95192c5 --- /dev/null +++ b/appdaemon/settings/apps/climate/climate.old_py @@ -0,0 +1,337 @@ +import appdaemon.plugins.hass.hassapi as hass +import struct +import binascii +# for PID +import time + +class Climate(hass.Hass): + ir_topic = "home/remote/rm2/code/set" + target_temp = 24.0 #default + pid = PID(P = 0.2, I=0.0, D=0.0) + + def initialize(self): + if "target_temp" not in self.args or "temp_sensor" not in self.args or "ha_panel" not in self.args or "door_window" not in self.args: + self.error("Please provide target_temp, temp_sensor, ha_panel, door_window in config!") + return + self.listen_state(self.target_temp_changed, self.args['target_temp']) + self.listen_state(self.temp_sensor_changed, self.args['temp_sensor']) + self.listen_state(self.constraint_changed, self.args['constraint']) + self.listen_state(self.ha_panel_changed, self.args['ha_panel']) + self.listen_state(self.door_window_changed, self.args['door_window']) + self.target_temp_changed("", "", "", "", "") + + def door_window_changed(self, entity, attribute, old, new, kwargs): + self.do_action() + + def ha_panel_changed(self, entity, attribute, old, new, kwargs): + self.do_action() + + def constraint_changed(self, entity, attribute, old, new, kwargs): + self.do_action() + + def target_temp_changed(self, entity, attribute, old, new, kwargs): + self.target_temp = float(self.get_state(self.args['target_temp'])) + self.pid.SetPoint = self.target_temp + self.log('Target temp changed to: ' + str(self.target_temp)) + self.do_action() + + def temp_sensor_changed(self, entity, attribute, old, new, kwargs): + self.log('New room temp: ' + str(new)) + self.do_action() + + def split_off(self): + remote = Remote() + code = remote.set_mode("NONE", "NONE", self.target_temp, "OFF") + self.call_service("mqtt/publish", topic = self.ir_topic, payload = code.decode("utf-8")) + + def do_action(self): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + self.log("Temperature control is disabled.") + self.split_off() + return + + # Проверить alarm_panel + ha_panel_state = self.get_state(self.args['ha_panel']) + if (ha_panel_state != 'disarmed'): + self.log("Nobody home. Turning off split.") + self.split_off() + return + + # Проверить датчики дверей и окон + door_window_state = self.get_state(self.args['door_window']) + if (door_window_state != 'off'): + self.log("Balcony door is opened. Turning off split.") + self.split_off() + return + + room_temp = float(self.get_state(self.args['temp_sensor'])) + self.pid.update(room_temp) + output = self.pid.output + self.log("PID: " + str(output) + ".") + + # Определяем режим + mode = "COOLING" + room_temp = float(self.get_state(self.args['temp_sensor'])) + if (room_temp >= self.target_temp): + mode = "COOLING" + else: + mode = "HEATING" + + temp_diff = abs(room_temp - self.target_temp) + if (output < 0): + self.log("Temperature differs on {} degree, turn on split for {}".format(temp_diff, mode)) + remote = Remote() + code = remote.set_mode(mode, "2", int(self.target_temp), "ON") + self.call_service("mqtt/publish", topic = self.ir_topic, payload = code.decode("utf-8")) + else: + self.log("Temperature is ok, turn off split.") + self.split_off() + +# +# App to control climate device (split system) +# +# Args: +# +# notify = notification platform to send notifications to +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +# ------------------------------------------------------------------------------------------------- +""" +Library that genrates LG air conditioner remote codes +""" +FIRST_BYTE = 136 # b10001000 + +STATE = { + "ON": 0, + "OFF": 24, # b11000 + "CHANGE_MODE": 1, +} + +MODE = { + "HEATING": 4, # b100 + "AUTO": 3, # b011 + "DEHUIDIFICATION": 1, # b001 + "COOLING": 0, + "NONE": 0, +} + +TEMPERATURE_OFFSET = 15 + +FAN = { + "1": 1, + "2": 0, + "3": 2, + "4": 4, # b100 + "NONE": 5, # b101 +} + +FIRST_HIGH = 8271 +FIRST_LOW = 4298 +ZERO_AND_ONE_HIGH = 439 +ZERO_LOW = 647 +ONE_LOW = 1709 + +BUFFER_SIZE = 59 + +def test_bit(num, offset): + """ + Test the num(int) if at the given offset bit is 1 + """ + mask = 1 << offset + return num & mask + +def set_bit(num, offset): + """ + Set bit to at the given offset to 1 + """ + mask = 1 << offset + return num | mask + +class Remote(object): + """ + Library that genrates LG air conditioner remote codes + """ + + def __init__(self): + self.codes = [0] * BUFFER_SIZE + self.crc = 0 + + def set_mode(self, mode, fan, temperature, state): + """ + Generate code and put it in the buffer + """ + self.codes[0] = FIRST_HIGH + self.codes[1] = FIRST_LOW + self.crc = 0 + + self.fill_buffer(0, 8, FIRST_BYTE) + self.fill_buffer(8, 5, STATE[state]) + + if state == 'OFF': + self.fill_buffer(13, 3, MODE['NONE']) + else: + self.fill_buffer(13, 3, MODE[mode]) + + if state == 'OFF': + self.fill_buffer(16, 4, 0) + else: + self.fill_buffer(16, 4, temperature - TEMPERATURE_OFFSET) + + self.fill_buffer(20, 1, 0) # jet + + if state == 'OFF': + self.fill_buffer(21, 3, FAN['NONE']) + else: + self.fill_buffer(21, 3, FAN[fan]) + + self.fill_buffer(24, 4, self.crc) + self.codes[BUFFER_SIZE - 1] = ZERO_AND_ONE_HIGH + self.codes = binascii.hexlify(self.lirc2broadlink(self.codes)) + return self.codes + + def lirc2broadlink(self, pulses): + array = bytearray() + + for pulse in pulses: + pulse = int(pulse * 269 / 8192) # 32.84ms units + + if pulse < 256: + array += bytearray(struct.pack('>B', pulse)) # big endian (1-byte) + else: + array += bytearray([0x00]) # indicate next number is 2-bytes + array += bytearray(struct.pack('>H', pulse)) # big endian (2-bytes) + + packet = bytearray([0x26, 0x00]) # 0x26 = IR, 0x00 = no repeats + packet += bytearray(struct.pack(' 0: + index = 2 + 2 * (pos + bits-i) + self.codes[index] = ZERO_AND_ONE_HIGH + + if test_bit(value, i - 1) != 0: + self.codes[index + 1] = ONE_LOW + else: + self.codes[index + 1] = ZERO_LOW + + if test_bit(value, i - 1) != 0: + bitset = 0 + bitset = set_bit(bitset, (128 + i - pos - bits - 1) % 4) + self.crc = self.crc + bitset + + i -= 1 + + +# ------------------- PID CONTROLLER -------------------------------------------- +class PID: + """PID Controller + """ + + def __init__(self, P=0.2, I=0.0, D=0.0): + + self.Kp = P + self.Ki = I + self.Kd = D + + self.sample_time = 0.00 + self.current_time = time.time() + self.last_time = self.current_time + + self.clear() + + def clear(self): + """Clears PID computations and coefficients""" + self.SetPoint = 0.0 + + self.PTerm = 0.0 + self.ITerm = 0.0 + self.DTerm = 0.0 + self.last_error = 0.0 + + # Windup Guard + self.int_error = 0.0 + self.windup_guard = 20.0 + + self.output = 0.0 + + def update(self, feedback_value): + """Calculates PID value for given reference feedback + .. math:: + u(t) = K_p e(t) + K_i \int_{0}^{t} e(t)dt + K_d {de}/{dt} + .. figure:: images/pid_1.png + :align: center + Test PID with Kp=1.2, Ki=1, Kd=0.001 (test_pid.py) + """ + error = self.SetPoint - feedback_value + + self.current_time = time.time() + delta_time = self.current_time - self.last_time + delta_error = error - self.last_error + + if (delta_time >= self.sample_time): + self.PTerm = self.Kp * error + self.ITerm += error * delta_time + + if (self.ITerm < -self.windup_guard): + self.ITerm = -self.windup_guard + elif (self.ITerm > self.windup_guard): + self.ITerm = self.windup_guard + + self.DTerm = 0.0 + if delta_time > 0: + self.DTerm = delta_error / delta_time + + # Remember last time and last error for next calculation + self.last_time = self.current_time + self.last_error = error + + self.output = self.PTerm + (self.Ki * self.ITerm) + (self.Kd * self.DTerm) + + def setKp(self, proportional_gain): + """Determines how aggressively the PID reacts to the current error with setting Proportional Gain""" + self.Kp = proportional_gain + + def setKi(self, integral_gain): + """Determines how aggressively the PID reacts to the current error with setting Integral Gain""" + self.Ki = integral_gain + + def setKd(self, derivative_gain): + """Determines how aggressively the PID reacts to the current error with setting Derivative Gain""" + self.Kd = derivative_gain + + def setWindup(self, windup): + """Integral windup, also known as integrator windup or reset windup, + refers to the situation in a PID feedback controller where + a large change in setpoint occurs (say a positive change) + and the integral terms accumulates a significant error + during the rise (windup), thus overshooting and continuing + to increase as this accumulated error is unwound + (offset by errors in the other direction). + The specific problem is the excess overshooting. + """ + self.windup_guard = windup + + def setSampleTime(self, sample_time): + """PID that should be updated at a regular interval. + Based on a pre-determined sampe time, the PID decides if it should compute or return immediately. + """ + self.sample_time = sample_time diff --git a/appdaemon/settings/apps/climate/climate.py b/appdaemon/settings/apps/climate/climate.py new file mode 100644 index 0000000..3013666 --- /dev/null +++ b/appdaemon/settings/apps/climate/climate.py @@ -0,0 +1,276 @@ +import appdaemon.plugins.hass.hassapi as hass +import struct +import binascii + +class Climate(hass.Hass): + ir_topic = "home/remote/rm2/code/set" + split_state = "None" + timer = None + + def initialize(self): + self.listen_event_handle_list = [] + if "ha_panel" not in self.args or "door_window" not in self.args: + self.error("Please provide temp_sensor, ha_panel, door_window in config!") + return + self.listen_event_handle_list.append(self.listen_state(self.constraint_changed, self.args['constraint'])) + self.listen_event_handle_list.append(self.listen_state(self.ha_panel_changed, self.args['ha_panel'])) + self.listen_event_handle_list.append(self.listen_state(self.door_window_changed, self.args['door_window'])) + + self.listen_event_handle_list.append(self.listen_state(self.nest_changed, 'binary_sensor.nest_y1')) + self.listen_event_handle_list.append(self.listen_state(self.nest_changed, 'binary_sensor.nest_w1')) + self.do_action() + + def nest_changed(self, entity, attribute, old, new, kwargs): + self.do_action() + + def door_window_changed(self, entity, attribute, old, new, kwargs): + self.do_action() + + def ha_panel_changed(self, entity, attribute, old, new, kwargs): + self.do_action() + + def constraint_changed(self, entity, attribute, old, new, kwargs): + self.do_action() + + def split_off(self): + if self.split_state != "on" and self.split_state != "None": + return + remote = Remote() + code = remote.set_mode("NONE", "NONE", 18, "OFF") + self.call_service("mqtt/publish", topic = self.ir_topic, payload = code.decode("utf-8")) + self.split_state = "off" + if (self.timer != None): + self.cancel_timer(self.timer) + self.timer = self.run_in(self.run_in_split_off, 15) + + def run_in_split_off(self, args): + self.turn_off("switch.plug_158d00010dd98d") + + def split_on(self, mode, temp): + self.turn_on("switch.plug_158d00010dd98d") + if (self.timer != None): + self.cancel_timer(self.timer) + self.timer = self.run_in(self.run_in_split_mode, 10, mode=mode, temp=temp, state="ON") + + def run_in_split_mode(self, args): + remote = Remote() + code = remote.set_mode(args['mode'], "2", args['temp'], args['state']) + self.call_service("mqtt/publish", topic = self.ir_topic, payload = code.decode("utf-8")) + + def set_color_entity(self, to): + if 'color_entity' not in self.args: + return + entity = self.args['color_entity'] + if to == "hot": + self.turn_on(entity, rgb_color=[255,0,0], brightness = 100) + if to == "cold": + self.turn_on(entity, rgb_color=[0,0,255], brightness = 100) + if to == "off": + self.turn_off(entity) + + def do_action(self): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + self.log("Temperature control is disabled.") + self.split_off() + return + + # Проверить alarm_panel + ha_panel_state = self.get_state(self.args['ha_panel']) + if (ha_panel_state != 'disarmed'): + self.log("Nobody home. Turning off split.") + self.split_off() + return + + # Проверить датчики дверей и окон + door_window_state = self.get_state(self.args['door_window']) + if (door_window_state != 'off'): + self.log("Balcony door is opened. Turning off split.") + self.split_off() + return + + nest_target_cool = self.get_state('climate.living_room', attribute="temperature") + nest_target_hot = nest_target_cool + if nest_target_cool == None and nest_target_hot == None: + nest_target_cool = self.get_state('climate.living_room', attribute="target_temp_high") + nest_target_hot = self.get_state('climate.living_room', attribute="target_temp_low") + + nest_w1 = "off" + nest_y1 = "off" + + nest_w1 = self.get_state('binary_sensor.nest_w1') + nest_y1 = self.get_state('binary_sensor.nest_y1') + + if nest_y1 == "on": + self.split_state = "on" + self.log("Turn on split for COOLING to {}.".format(nest_target_cool)) + self.split_on("COOLING", 20) + self.set_color_entity('cold') + + elif nest_w1 == "on": + self.split_state = "on" + self.log("Turn on split for HEATING to {}".format(nest_target_hot)) + self.split_on("HEATING", 28) + self.set_color_entity('hot') + + else: + self.log("Nest is reporting that temp is ok.") + self.split_off() + self.set_color_entity('off') + + 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) + +# +# App to control climate device (split system) +# +# Args: +# +# notify = notification platform to send notifications to +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +# ------------------------------------------------------------------------------------------------- +""" +Library that genrates LG air conditioner remote codes +""" +FIRST_BYTE = 136 # b10001000 + +STATE = { + "ON": 0, + "OFF": 24, # b11000 + "CHANGE_MODE": 1, +} + +MODE = { + "HEATING": 4, # b100 + "AUTO": 3, # b011 + "DEHUIDIFICATION": 1, # b001 + "COOLING": 0, + "NONE": 0, +} + +TEMPERATURE_OFFSET = 15 + +FAN = { + "1": 1, + "2": 0, + "3": 2, + "4": 4, # b100 + "NONE": 5, # b101 +} + +FIRST_HIGH = 8271 +FIRST_LOW = 4298 +ZERO_AND_ONE_HIGH = 439 +ZERO_LOW = 647 +ONE_LOW = 1709 + +BUFFER_SIZE = 59 + +def test_bit(num, offset): + """ + Test the num(int) if at the given offset bit is 1 + """ + mask = 1 << offset + return num & mask + +def set_bit(num, offset): + """ + Set bit to at the given offset to 1 + """ + mask = 1 << offset + return num | mask + +class Remote(object): + """ + Library that genrates LG air conditioner remote codes + """ + + def __init__(self): + self.codes = [0] * BUFFER_SIZE + self.crc = 0 + + def set_mode(self, mode, fan, temperature, state): + """ + Generate code and put it in the buffer + """ + self.codes[0] = FIRST_HIGH + self.codes[1] = FIRST_LOW + self.crc = 0 + + self.fill_buffer(0, 8, FIRST_BYTE) + self.fill_buffer(8, 5, STATE[state]) + + if state == 'OFF': + self.fill_buffer(13, 3, MODE['NONE']) + else: + self.fill_buffer(13, 3, MODE[mode]) + + if state == 'OFF': + self.fill_buffer(16, 4, 0) + else: + self.fill_buffer(16, 4, temperature - TEMPERATURE_OFFSET) + + self.fill_buffer(20, 1, 0) # jet + + if state == 'OFF': + self.fill_buffer(21, 3, FAN['NONE']) + else: + self.fill_buffer(21, 3, FAN[fan]) + + self.fill_buffer(24, 4, self.crc) + self.codes[BUFFER_SIZE - 1] = ZERO_AND_ONE_HIGH + self.codes = binascii.hexlify(self.lirc2broadlink(self.codes)) + return self.codes + + def lirc2broadlink(self, pulses): + array = bytearray() + + for pulse in pulses: + pulse = int(pulse * 269 / 8192) # 32.84ms units + + if pulse < 256: + array += bytearray(struct.pack('>B', pulse)) # big endian (1-byte) + else: + array += bytearray([0x00]) # indicate next number is 2-bytes + array += bytearray(struct.pack('>H', pulse)) # big endian (2-bytes) + + packet = bytearray([0x26, 0x00]) # 0x26 = IR, 0x00 = no repeats + packet += bytearray(struct.pack(' 0: + index = 2 + 2 * (pos + bits-i) + self.codes[index] = ZERO_AND_ONE_HIGH + + if test_bit(value, i - 1) != 0: + self.codes[index + 1] = ONE_LOW + else: + self.codes[index + 1] = ZERO_LOW + + if test_bit(value, i - 1) != 0: + bitset = 0 + bitset = set_bit(bitset, (128 + i - pos - bits - 1) % 4) + self.crc = self.crc + bitset + + i -= 1 diff --git a/appdaemon/settings/apps/climate/climate.yaml b/appdaemon/settings/apps/climate/climate.yaml new file mode 100644 index 0000000..ca5e9d7 --- /dev/null +++ b/appdaemon/settings/apps/climate/climate.yaml @@ -0,0 +1,14 @@ +################################# +# CLIMATE # +################################# +mainroom_climate: + module: climate + class: Climate + constraint: input_boolean.mainroom_climate_control + target_temp: input_number.mainroom_temperature + temp_sensor: sensor.temperature_158d0001149b3c #mainroom temp sensor + ha_panel: alarm_control_panel.ha_alarm + door_window: group.mainroom_door_window + color_entity: light.gateway_light_f0b429aa1463 + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/cube.py b/appdaemon/settings/apps/cube.py new file mode 100644 index 0000000..832b9ca --- /dev/null +++ b/appdaemon/settings/apps/cube.py @@ -0,0 +1,47 @@ +#cube_tap_twice +import appdaemon.plugins.hass.hassapi as hass + +# +# This app reacts to xiaomi cube events +# +# Args: +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Cube(hass.Hass): + def initialize(self): + self.listen_event(self.cube_flip90, "cube_flip90") + self.listen_event(self.cube_flip180, "cube_flip180") + self.listen_event(self.cube_shake_air, "cube_shake_air") + self.listen_event(self.cube_move, "cube_move") + self.listen_event(self.cube_tap_twice, "cube_tap_twice") + self.listen_event(self.cube_swing, "cube_swing") + + def cube_flip90(self, event_id, event_args, kwargs): + self.log("cube_flip90") + # turn on next light + self.fire_event("button_mainroom_click") + + def cube_flip180(self, event_id, event_args, kwargs): + self.log("cube_flip180") + + def cube_shake_air(self, event_id, event_args, kwargs): + self.log("cube_shake_air") + + def cube_move(self, event_id, event_args, kwargs): + self.log("cube_move") + + def cube_swing(self, event_id, event_args, kwargs): + self.log("cube_swing") + + def cube_tap_twice(self, event_id, event_args, kwargs): + self.log("cube_tap_twice") + attributes = self.get_state("group.mainroom_light", attribute = "all") + if 'attributes' in attributes: + attributes = attributes['attributes'] + self.log(attributes) + for entity_id in attributes["entity_id"]: + self.turn_off(entity_id) \ No newline at end of file diff --git a/appdaemon/settings/apps/doorbells/doorbell.py b/appdaemon/settings/apps/doorbells/doorbell.py new file mode 100644 index 0000000..e3c07af --- /dev/null +++ b/appdaemon/settings/apps/doorbells/doorbell.py @@ -0,0 +1,47 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# 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 != "off": + return + self.log("doorbell call") + 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']) + for x in range(3): + self.send_image() + + + def send_image(self): + self.call_service("camera/snapshot", entity_id="camera.entrance", filename="/config/www/camera_image.jpg") + extra_data = {'photo': {'file':'/config/www/camera_image.jpg', 'capture': 'Фото звонившего.'}} + self.notify("Фото звонившего", name = self.args['notify'], data=extra_data) + diff --git a/appdaemon/settings/apps/doorbells/doorbell.yaml b/appdaemon/settings/apps/doorbells/doorbell.yaml new file mode 100644 index 0000000..48fba6e --- /dev/null +++ b/appdaemon/settings/apps/doorbells/doorbell.yaml @@ -0,0 +1,11 @@ +################################# +# INTERCOM # +################################# +doorbell: + module: doorbell + class: Doorbell + sensor: binary_sensor.door_window_sensor_158d0001f34374 + notify: telegram + gw_mac: !secret aqara1_mac + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/doorbells/intercom.py b/appdaemon/settings/apps/doorbells/intercom.py new file mode 100644 index 0000000..6d5c02e --- /dev/null +++ b/appdaemon/settings/apps/doorbells/intercom.py @@ -0,0 +1,36 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# App to send notification when intercom is ringing +# +# Args: +# +# notify = notification platform to send notifications to +# ringtone (optional) = ringtone to play +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Intercom(hass.Hass): + ringtone = 10 + gw_mac = '' + def initialize(self): + if "notify" not in self.args: + self.error("Please provide notify 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_event(self.domofon_call, "domofon_call") + + def domofon_call(self, event_id, event_args, kwargs): + self.log("domofon_call") + self.log(event_args) + 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']) diff --git a/appdaemon/settings/apps/doorbells/intercom.yaml b/appdaemon/settings/apps/doorbells/intercom.yaml new file mode 100644 index 0000000..65f47be --- /dev/null +++ b/appdaemon/settings/apps/doorbells/intercom.yaml @@ -0,0 +1,10 @@ +################################# +# INTERCOM # +################################# +intercom: + module: intercom + class: Intercom + notify: telegram + gw_mac: !secret aqara1_mac + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/fixes/automation_fix.py b/appdaemon/settings/apps/fixes/automation_fix.py new file mode 100644 index 0000000..e64614b --- /dev/null +++ b/appdaemon/settings/apps/fixes/automation_fix.py @@ -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') \ No newline at end of file diff --git a/appdaemon/settings/apps/fixes/automation_fix.yaml b/appdaemon/settings/apps/fixes/automation_fix.yaml new file mode 100644 index 0000000..751ba87 --- /dev/null +++ b/appdaemon/settings/apps/fixes/automation_fix.yaml @@ -0,0 +1,6 @@ +################################# +# FIXES # +################################# +automation_fix: + module: automation_fix + class: AutomationFix \ No newline at end of file diff --git a/appdaemon/settings/apps/globals.py b/appdaemon/settings/apps/globals.py new file mode 100644 index 0000000..895c9ff --- /dev/null +++ b/appdaemon/settings/apps/globals.py @@ -0,0 +1,74 @@ +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 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") + diff --git a/appdaemon/settings/apps/light/kitchen_led_controller.py b/appdaemon/settings/apps/light/kitchen_led_controller.py new file mode 100644 index 0000000..f1ac649 --- /dev/null +++ b/appdaemon/settings/apps/light/kitchen_led_controller.py @@ -0,0 +1,18 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals + +class KitchenLedController(hass.Hass): + listen_event_handle_list = [] + def initialize(self): + if not globals.check_args(self,['watch_entity']): + return + + self.listen_event_handle_list.append(self.listen_state(self.entity_changed, self.args['watch_entity'])) + + def entity_changed(self, entity, attribute, old, new, kwargs): + stete = "off" + if new == "off": + stete = "off" + if new == "on": + stete = "on" + self.call_service("mqtt/publish", topic = "home/kitchen-led/kitchen-led/state/set", payload = stete) diff --git a/appdaemon/settings/apps/light/kitchen_led_controller.yaml b/appdaemon/settings/apps/light/kitchen_led_controller.yaml new file mode 100644 index 0000000..98614ef --- /dev/null +++ b/appdaemon/settings/apps/light/kitchen_led_controller.yaml @@ -0,0 +1,9 @@ +################################# +# LIGHT BUTTONS # +################################# +kitchen_led_controller: + module: kitchen_led_controller + class: KitchenLedController + watch_entity: light.kitchen + global_dependencies: + - globals diff --git a/appdaemon/settings/apps/light/lightbutton.py b/appdaemon/settings/apps/light/lightbutton.py new file mode 100644 index 0000000..3e2d976 --- /dev/null +++ b/appdaemon/settings/apps/light/lightbutton.py @@ -0,0 +1,86 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals + +class LightButton(hass.Hass): + 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")) + + def event_detected(self, event_name, data, kwargs): + if data["entity_id"] != self.args["sensor"]: + return + 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") + + 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) + + def set_on(self, entity_id): + if entity_id.startswith("light"): + self.call_service("light/turn_on", entity_id = entity_id, transition = 1, brightness = 255) + else: + self.turn_on(entity_id) + + def set_off(self, entity_id): + if entity_id.startswith("light"): + self.call_service("light/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) diff --git a/appdaemon/settings/apps/light/lightbutton.yaml b/appdaemon/settings/apps/light/lightbutton.yaml new file mode 100644 index 0000000..2bd19fc --- /dev/null +++ b/appdaemon/settings/apps/light/lightbutton.yaml @@ -0,0 +1,46 @@ +################################# +# LIGHT BUTTONS # +################################# +light_button_main: + module: lightbutton + class: LightButton + sensor: binary_sensor.switch_158d00013f81a9 + entities: light.yeelight_rgb_34ce008fe328,light.yeelight_rgb_286c071065c7,light.yeelight_white_34ce0087ff33,light.yeelight_white_f0b4290e5444,light.yeelight_white_f0b429a8f6df + global_dependencies: + - globals + +light_button_hall: + module: lightbutton + class: LightButton + sensor: binary_sensor.switch_158d000102f7e6 + entities: group.hall_light + control_input: input_boolean.hall_light_control + global_dependencies: + - globals + +light_button_bath: + module: lightbutton + class: LightButton + sensor: binary_sensor.switch_158d0001256810 + entities: group.bath_light + control_input: input_boolean.bath_light_control + global_dependencies: + - globals + +light_button_kitchen: + module: lightbutton + class: LightButton + sensor: binary_sensor.switch_158d00012d5720 + entities: group.kitchen_light + control_input: input_boolean.kitchen_light_control + global_dependencies: + - globals + +light_button_balcony: + module: lightbutton + class: LightButton + sensor: binary_sensor.wall_switch_158d000183a5fe + entities: group.balcony_light + control_input: input_boolean.balcony_light_control + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/light/lightcontrol.py b/appdaemon/settings/apps/light/lightcontrol.py new file mode 100644 index 0000000..44232c3 --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol.py @@ -0,0 +1,82 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals + +# +# 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 + + #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 + + if self.get_state(self.args['control_entity']) == 'off': + self.turn_on(self.args['control_entity']) + self.log("Light on ({}).".format(self.args['control_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 + if self.get_state(self.args['control_entity']) == 'on': + self.turn_off(self.args['control_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 \ No newline at end of file diff --git a/appdaemon/settings/apps/light/lightcontrol.yaml b/appdaemon/settings/apps/light/lightcontrol.yaml new file mode 100644 index 0000000..1954046 --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol.yaml @@ -0,0 +1,43 @@ +################################# +# LIGHT CONTROL # +################################# +light_control_mainroom: + module: lightcontrol + class: LightControl + sensor: group.mainroom_motion + control_entity: group.mainroom_light + timeout: 60 + constraint: input_boolean.mainroom_light_control + global_dependencies: + - globals + +light_control_kitchen: + module: lightcontrol + class: LightControl + sensor: group.kitchen_motion + control_entity: group.kitchen_light + timeout: 300 + constraint: input_boolean.kitchen_light_control + global_dependencies: + - globals + +light_control_hall: + module: lightcontrol + class: LightControl + sensor: group.hall_motion + control_entity: group.hall_light + timeout: 120 + constraint: input_boolean.hall_light_control + global_dependencies: + - globals + +light_control_balcony: + module: lightcontrol + class: LightControl + sensor: group.balcony_door_window + control_entity: group.balcony_light + timeout: 3 + constraint: input_boolean.balcony_light_control + after_sundown: True + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/light/lightcontrol_bath.py b/appdaemon/settings/apps/light/lightcontrol_bath.py new file mode 100644 index 0000000..7f3a8f0 --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol_bath.py @@ -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 \ No newline at end of file diff --git a/appdaemon/settings/apps/light/lightcontrol_bath.yaml b/appdaemon/settings/apps/light/lightcontrol_bath.yaml new file mode 100644 index 0000000..c2ea1f6 --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol_bath.yaml @@ -0,0 +1,13 @@ +################################# +# LIGHT CONTROL # +################################# +light_control_bath: + module: lightcontrol_bath + class: LightControlBath + sensor: group.bath_motion + door_sensor: binary_sensor.door_window_sensor_158d000111a469 #TODO: make it group + control_entity: group.bath_light + timeout: 60 + constraint: input_boolean.bath_light_control + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/balance.py b/appdaemon/settings/apps/notifiers/balance.py new file mode 100644 index 0000000..d23aaa3 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/balance.py @@ -0,0 +1,56 @@ +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="" + 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_monster") + + def is_number(self, s): + try: + float(s) + return True + except ValueError: + return False \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/balance.yaml b/appdaemon/settings/apps/notifiers/balance.yaml new file mode 100644 index 0000000..b489ade --- /dev/null +++ b/appdaemon/settings/apps/notifiers/balance.yaml @@ -0,0 +1,15 @@ +################################# +# SENSOR BATTERY LOW REMINDER # +################################# +balance_low_report: + class: Balance + module: balance + entity_ids: + mosenergosbyt_balance: sensor.power_mosenergosbyt_balance + epd_balance: sensor.epd_balance + + always_send: 0 + threshold: -100 + notify: telegram_monster + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/battery.py b/appdaemon/settings/apps/notifiers/battery.py new file mode 100644 index 0000000..435cfe0 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/battery.py @@ -0,0 +1,54 @@ +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) + + 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 device in devices: + battery = None + if "attributes" in devices[device]: + if "battery" in devices[device]["attributes"]: + battery = devices[device]["attributes"]["battery"] + if "battery_level" in devices[device]["attributes"]: + battery = devices[device]["attributes"]["battery_level"] + if battery != None: + if battery < self.args["threshold"]: + low.append(device) + values[device] = 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") diff --git a/appdaemon/settings/apps/notifiers/battery.yaml b/appdaemon/settings/apps/notifiers/battery.yaml new file mode 100644 index 0000000..83be96d --- /dev/null +++ b/appdaemon/settings/apps/notifiers/battery.yaml @@ -0,0 +1,11 @@ +################################# +# SENSOR BATTERY LOW REMINDER # +################################# +sensor_battery_low_report: + class: Battery + module: battery + constraint: input_boolean.battery_report + always_send: 0 + threshold: 10 + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/power.py b/appdaemon/settings/apps/notifiers/power.py new file mode 100644 index 0000000..e7f32e7 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/power.py @@ -0,0 +1,55 @@ +import appdaemon.plugins.hass.hassapi as hass +from datetime import timedelta +import datetime +import globals + +# +# App to monitor high power usage when away. +# +# Args: +# +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class PowerWatchdog(hass.Hass): + listen_event_handle_list = [] + notified_today = False + standby_power_limit = 10 + + def initialize(self): + if not globals.check_args(self, ['entity', 'thresold', 'ha_panel', 'water_devices']): + return + self.listen_event_handle_list.append(self.listen_state(self.power_changed, self.args['entity'])) + + def power_changed(self, entity, attribute, old, new, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + ha_panel_state = self.get_state(self.args['ha_panel']) + if (ha_panel_state != "armed_away"): + return + # self.log('Power usage changed - {}kWh.'.format(new)) + + # if water devices is on - then skipping this alert + devices = self.args['water_devices'] + for device in devices: + state = self.get_state(device) + attributes = self.get_state(device, attribute = "all")['attributes'] + if 'load_power' not in attributes: + continue + load_power = attributes['load_power'] + # self.log('{} load power is: {}'.format(device, load_power)) + if state == 'on' and load_power > self.standby_power_limit: + return + + power = float(new) + if power > self.args['thresold']: + if not self.notified_today: + self.notified_today = True + self.log('Power usage too much when away - {}kWh.'.format(new)) + self.notify('В ваше отсутствие наблюдается высокое потребление электроэнергии ({}), не оставили ли вы работающие устройства?'.format(power), name = 'telegram') diff --git a/appdaemon/settings/apps/notifiers/power.yaml b/appdaemon/settings/apps/notifiers/power.yaml new file mode 100644 index 0000000..9ef1916 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/power.yaml @@ -0,0 +1,15 @@ +################################# +# SENSOR BATTERY LOW REMINDER # +################################# +watchdog_power: + class: PowerWatchdog + module: power + entity: sensor.power_active_power + ha_panel: alarm_control_panel.ha_alarm + thresold: 0.6 + water_devices: + - switch.plug_158d0001a68c64 # Посудомойка + - switch.plug_158d0001a4cfb4 # стиралка + + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/watchdog.py b/appdaemon/settings/apps/notifiers/watchdog.py new file mode 100644 index 0000000..7bf963b --- /dev/null +++ b/appdaemon/settings/apps/notifiers/watchdog.py @@ -0,0 +1,43 @@ +import appdaemon.plugins.hass.hassapi as hass +from datetime import timedelta +import datetime +import globals + +# +# App to monitor entity update time. +# +# Args: +# +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Watchdog(hass.Hass): + listen_event_handle_list = [] + timezone = 3 + def initialize(self): + if not globals.check_args(self, ['entity','update_interval', 'timeout']): + return + self.timer = self.run_every(self.watchdog_check, self.datetime(), self.args['update_interval']) + + def watchdog_check(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + entity = self.args['entity'] + attributes = self.get_state(entity, attribute='all') + if not 'last_changed' in attributes: + return + 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 = datetime.datetime.now() - last_changed_date + 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 = "telegram") \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/watchdog.yaml b/appdaemon/settings/apps/notifiers/watchdog.yaml new file mode 100644 index 0000000..06455b2 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/watchdog.yaml @@ -0,0 +1,11 @@ +################################# +# SENSOR BATTERY LOW REMINDER # +################################# +watchdog_sensor_power_active_power: + class: Watchdog + module: watchdog + entity: sensor.power_active_power + update_interval: 60 + timeout: 360 + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/water_counters.py b/appdaemon/settings/apps/notifiers/water_counters.py new file mode 100644 index 0000000..dbd8682 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/water_counters.py @@ -0,0 +1,97 @@ +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}) + time = datetime.time(10, 0, 0) + self.run_daily(self.check_checkup_dates, time) + self.run_daily(self.check_send_dates, time) + + 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() + values = {} + send_devices = [] + for device in devices: + send_date = None + if "attributes" in devices[device]: + if "date" in devices[device]["attributes"]: + send_date = devices[device]["attributes"]["date"] + if send_date != None: + self.log('found device {} with send date {}'.format(device, send_date)) + if send_date == "": + send_devices.append(device) + 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(device) + values[device] = 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 + + devices = self.get_state() + values = {} + checkup_devices = [] + for device in devices: + checkup = None + if "attributes" in devices[device]: + if "checkup" in devices[device]["attributes"]: + checkup = devices[device]["attributes"]["checkup"] + if checkup != None: + self.log('found device {} with checkup date {}'.format(device, 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(device) + values[device] = 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") \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/water_counters.yaml b/appdaemon/settings/apps/notifiers/water_counters.yaml new file mode 100644 index 0000000..0eacba2 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/water_counters.yaml @@ -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 \ No newline at end of file diff --git a/appdaemon/settings/apps/people/person.old b/appdaemon/settings/apps/people/person.old new file mode 100644 index 0000000..b83d3b7 --- /dev/null +++ b/appdaemon/settings/apps/people/person.old @@ -0,0 +1,48 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +# +# Person +# Script that creates persons with their properties. +# +# Args: +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Person(hass.Hass): + view_name = 'group.persons' + view_friendly_name = 'Люди' + tags = [] + def initialize(self): + self.log('args: {}'.format(self.args)) + self.listen_event_handle_list = [] + self.add_person() + # self.listen_event_handle_list.append(self.listen_event(self.event_detected, "click")) + + def event_detected(self, event_name, data, kwargs): + self.log("ButtonClicked: {}".format(data["entity_id"])) + #self.check_batteries({"force": 1}) + + def add_person(self): + view = self.get_state(self.view_name, attribute = 'all') + self.log(view) + if (view == None): + self.set_state(self.view_name, state="on",attributes={"view": False, "hidden": False, "assumed_state": "on", "friendly_name": self.view_friendly_name,"entity_id": []}) + + # attributes = {} + # attributes['unit_of_measurement'] = sensor['unit'] + # attributes['description'] = 'Created and updated from appdaemon (narodmon)' + # if 'friendly_name' in self.args: + # attributes['friendly_name'] = self.args['friendly_name'] + + # self.set_state(self.args['entity'], state = sensor['value'], attributes = attributes) + + + 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) diff --git a/appdaemon/settings/apps/people/person.py b/appdaemon/settings/apps/people/person.py new file mode 100644 index 0000000..30bdab9 --- /dev/null +++ b/appdaemon/settings/apps/people/person.py @@ -0,0 +1,107 @@ +import globals +from automation import Base +from globals import CONF_PEOPLE +from typing import Any, Tuple, Union +# +# Person +# Script that creates persons with their properties. +# +# Args: +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Person(Base): + """Define a class to represent a person.""" + def initialize(self) -> None: + """Initialize.""" + super().initialize() + self._add_person_to_globals() + # Listen for changes to the device trackers (to initiate re-rendering + # if needed): + for tracker in self.device_trackers: + self.listen_state(self._device_tracker_changed_cb, tracker) + + @property + def first_name(self) -> str: + """Return the person's name.""" + return self.name.title() + + @property + def device_trackers(self) -> list: + """Return the device trackers associated with the person.""" + return self.properties['device_trackers'] + + @property + def notifiers(self) -> list: + """Return the notifiers associated with the person.""" + return self.properties['notifiers'] + + @property + def presence_sensor(self) -> str: + """Return the entity ID of the generated presence status sensor.""" + return 'sensor.{0}_presence_status'.format(self.name) + + @property + def presence_input_select(self) -> str: + """Return the input select related to the person's presence.""" + return self.properties['presence_input_select'] + + @property + def location(self) -> str: + """Get the current location from combined device trackers.""" + raw_location = globals.most_common([ + self.get_tracker_state(tracker_entity) + for tracker_entity in self.properties['device_trackers'] + ]) + + if raw_location not in ('home', 'not_home'): + return raw_location + + return self.get_state(self.presence_input_select) + + #return raw_location + + def _device_tracker_changed_cb( # pylint: disable=too-many-arguments + self, entity: Union[str, dict], attribute: str, old: str, new: str, + kwargs: dict) -> None: + """Respond when a device tracker changes state.""" + if new == old: + return + + self._render_presence_status_sensor() + + def _add_person_to_globals(self): + self.global_vars.setdefault(CONF_PEOPLE, []) + + existing_element = None + for element in self.global_vars[CONF_PEOPLE]: + if element.first_name == self.first_name: + existing_element = element + + if existing_element == None: + self.global_vars[CONF_PEOPLE].append(self) + else: + index = self.global_vars[CONF_PEOPLE].index(existing_element) + self.global_vars[CONF_PEOPLE][index] = self + + def _render_presence_status_sensor(self): + """Update the presence status sensor.""" + if self.location in ('Home', 'Just Arrived'): + picture_state = 'home' + else: + picture_state = 'away' + self.log('Setting presence sensor {} state to {}.'.format(self.presence_sensor, self.location)) + self.set_state( + self.presence_sensor, + state=self.location, + attributes={ + 'friendly_name': + self.first_name, + 'entity_picture': + '/local/{0}-{1}.png'.format(self.name, picture_state), + }) \ No newline at end of file diff --git a/appdaemon/settings/apps/people/person.yaml b/appdaemon/settings/apps/people/person.yaml new file mode 100644 index 0000000..10ad735 --- /dev/null +++ b/appdaemon/settings/apps/people/person.yaml @@ -0,0 +1,49 @@ +danil: + module: person + class: Person + priority: 10 + properties: + at_home_sensor: binary_sensor.danil_home + device_trackers: + - sensor.tag_monster_nut + notifiers: + - telegram_monster + presence_input_select: input_select.danil_presence_status + + dependencies: + - automation + global_dependencies: + - globals + +sveta: + module: person + class: Person + priority: 10 + properties: + at_home_sensor: binary_sensor.sveta_home + device_trackers: + - sensor.tag_nut_guest + notifiers: + - telegram_sveta + presence_input_select: input_select.sveta_presence_status + dependencies: + - automation + global_dependencies: + - globals + +mama: + module: person + class: Person + priority: 10 + properties: + at_home_sensor: binary_sensor.mama_home + device_trackers: + - sensor.tag_mamik_band + - sensor.tag_nut_blue + notifiers: + - telegram_mama + presence_input_select: input_select.mama_presence_status + dependencies: + - automation + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/home/alarm_panel.py b/appdaemon/settings/apps/presence/home/alarm_panel.py new file mode 100644 index 0000000..031a0bb --- /dev/null +++ b/appdaemon/settings/apps/presence/home/alarm_panel.py @@ -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']) \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/home/alarm_panel.yaml b/appdaemon/settings/apps/presence/home/alarm_panel.yaml new file mode 100644 index 0000000..951acfe --- /dev/null +++ b/appdaemon/settings/apps/presence/home/alarm_panel.yaml @@ -0,0 +1,11 @@ +################################# +# HOME PRESENSE # +################################# +alarm_panel: + module: alarm_panel + class: AlarmPanelBySensor + sensor: group.presence + ha_panel: alarm_control_panel.ha_alarm + constraint: input_boolean.presence_control + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/home/alarm_panel_action.py b/appdaemon/settings/apps/presence/home/alarm_panel_action.py new file mode 100644 index 0000000..7b489ad --- /dev/null +++ b/appdaemon/settings/apps/presence/home/alarm_panel_action.py @@ -0,0 +1,33 @@ +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): + 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']) + + 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') + + def return_home_mode(self): + self.log('return_home_mode') + self.fire_event('return_home_mode') \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/home/alarm_panel_action.yaml b/appdaemon/settings/apps/presence/home/alarm_panel_action.yaml new file mode 100644 index 0000000..80d99f3 --- /dev/null +++ b/appdaemon/settings/apps/presence/home/alarm_panel_action.yaml @@ -0,0 +1,9 @@ +################################# +# HOME PRESENSE # +################################# +alarm_panel_action: + module: alarm_panel_action + class: AlarmPanelAction + ha_panel: alarm_control_panel.ha_alarm + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/home/entities_action.py b/appdaemon/settings/apps/presence/home/entities_action.py new file mode 100644 index 0000000..d071524 --- /dev/null +++ b/appdaemon/settings/apps/presence/home/entities_action.py @@ -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) diff --git a/appdaemon/settings/apps/presence/home/entities_action.yaml b/appdaemon/settings/apps/presence/home/entities_action.yaml new file mode 100644 index 0000000..4935f28 --- /dev/null +++ b/appdaemon/settings/apps/presence/home/entities_action.yaml @@ -0,0 +1,18 @@ +################################# +# HOME PRESENSE # +################################# +entities_action: + module: entities_action + class: EntitiesByPresence + away_entities: + - group.all_lights + - switch.kitchen_mini_light + - switch.plug_158d0001104a0c # телевизор + - switch.plug_158d0001a68b18 # термопот + return_entities: + - light.yeelight_rgb_34ce008fe328 + - light.hall + - switch.plug_158d0001104a0c # телевизор + - switch.plug_158d0001a68b18 # термопот + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/home/report_action.py b/appdaemon/settings/apps/presence/home/report_action.py new file mode 100644 index 0000000..6e02ac7 --- /dev/null +++ b/appdaemon/settings/apps/presence/home/report_action.py @@ -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']) diff --git a/appdaemon/settings/apps/presence/home/report_action.yaml b/appdaemon/settings/apps/presence/home/report_action.yaml new file mode 100644 index 0000000..3327852 --- /dev/null +++ b/appdaemon/settings/apps/presence/home/report_action.yaml @@ -0,0 +1,10 @@ +################################# +# HOME PRESENSE # +################################# +report_action: + module: report_action + class: ReportByPresence + entity: group.report_entities + notify: telegram + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/home/tags_presence.py b/appdaemon/settings/apps/presence/home/tags_presence.py new file mode 100644 index 0000000..331b1e2 --- /dev/null +++ b/appdaemon/settings/apps/presence/home/tags_presence.py @@ -0,0 +1,91 @@ +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) + 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') + else: + self.create_sensor(sensor, 'off') + + 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 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) \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/home/tags_presence.yaml b/appdaemon/settings/apps/presence/home/tags_presence.yaml new file mode 100644 index 0000000..98e503c --- /dev/null +++ b/appdaemon/settings/apps/presence/home/tags_presence.yaml @@ -0,0 +1,15 @@ +################################# +# HOME PRESENSE # +################################# +tags_presence: + module: tags_presence + class: TagsPresence + tag_prefix: sensor.tag_ + sensor_prefix: sensor.presence_ + constraint_prefix: input_boolean.tag_ + rooms: + - hall + - balcony + - kitchen + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/home/water_action.py b/appdaemon/settings/apps/presence/home/water_action.py new file mode 100644 index 0000000..623df40 --- /dev/null +++ b/appdaemon/settings/apps/presence/home/water_action.py @@ -0,0 +1,89 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# 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 + timer = None + 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(self.away_mode, "away_mode") + 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.timer = self.run_every(self.wait_when_device_is_done, self.datetime(), 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) == 0): + # self.turn_off(self.args['water_valve']) + # for device in self.args['water_devices']: + # self.turn_off(device) + self.notify('Устройства требующие воды закончили свою работу.', name = self.args['notify']) + self.cancel_current_timer() + + 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' in attributes: + attributes = attributes['attributes'] + if 'load_power' not in attributes: + continue + load_power = attributes['load_power'] + self.log('{} load power is: {}'.format(device, load_power)) + if state == 'on' and load_power < self.standby_power_limit: + continue + powered_on_devices.append(device) + return powered_on_devices + + def cancel_current_timer(self): + if self.timer != None: + self.log('canceling timer') + self.cancel_timer(self.timer) + self.timer = None diff --git a/appdaemon/settings/apps/presence/home/water_action.yaml b/appdaemon/settings/apps/presence/home/water_action.yaml new file mode 100644 index 0000000..41dc905 --- /dev/null +++ b/appdaemon/settings/apps/presence/home/water_action.yaml @@ -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 \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/work/work.py b/appdaemon/settings/apps/presence/work/work.py new file mode 100644 index 0000000..05989ac --- /dev/null +++ b/appdaemon/settings/apps/presence/work/work.py @@ -0,0 +1,47 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# Work leave reminder +# Send reminder about 'time to leave your work' +# +# Args: +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class WorkPresence(hass.Hass): + def initialize(self): + self.listen_event(self.office_enter, "office_enter") + self.listen_event(self.office_leave, "office_leave") + self.listen_event(self.office_leave_reminder, "office_leave_reminder") + + def office_leave_reminder(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.log('leave_reminder') + enter_time = self.get_state('sensor.work_enter_time') + leave_time = self.get_state('sensor.work_plan_leave_time') + if 'payload' in event_args: + leave_time = event_args['payload'] + message = "Ты пришел на работу в {}, работаешь до {}. Пора домой!".format(enter_time, leave_time) + self.call_service('notify/telegram', message=message) + + def office_leave(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.log('leave') + work_to_home_time = self.get_state('sensor.work_to_home_time') + message="Время в пути до дома {} минут.".format(work_to_home_time) + self.call_service('notify/telegram', message=message) + + def office_enter(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.log('enter') + enter_time = event_args.get('payload', None) + if enter_time == '' or enter_time == None: + return + self.log(enter_time) + self.call_service('notify/telegram', message='Добро пожаловать в офис. Приятного дня =)))') \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/work/work.yaml b/appdaemon/settings/apps/presence/work/work.yaml new file mode 100644 index 0000000..408162d --- /dev/null +++ b/appdaemon/settings/apps/presence/work/work.yaml @@ -0,0 +1,9 @@ +################################# +# WORK PRESENSE # +################################# +work_presence: + module: work + class: WorkPresence + constraint: input_boolean.work_presence_report + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/security/lock.py b/appdaemon/settings/apps/security/lock.py new file mode 100644 index 0000000..a79ff35 --- /dev/null +++ b/appdaemon/settings/apps/security/lock.py @@ -0,0 +1,42 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +# +# Управление замком Xiaomi +# +# Args: +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Lock(hass.Hass): + def initialize(self): + self.listen_event_handle_list = [] + if not globals.check_args(self, ['notify']): + return + locks = globals.get_group_entities(self, "group.all_locks") + for lock in locks: + self.listen_event_handle_list.append(self.listen_state(self.lock_state_changed, lock)) + + def lock_state_changed(self, entity, attribute, old, new, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + state = new + changed_by = self.get_state(entity, attribute = 'changed_by') + verified_wrong_times = self.get_state(entity, attribute = 'verified_wrong_times') + + if verified_wrong_times > 0: + message = "Попытка №{} отрыть замок с неправильным пальцем или паролем!".format(verified_wrong_times) + self.notify(message, name = self.args['notify']) + + if state == "unlocked": + user = changed_by + user_var = "user_{}".format(changed_by) + if user_var in self.args: + user = self.args[user_var] + message = "Открыта дверь пользователем: {}.".format(user) + self.notify(message, name = self.args['notify']) diff --git a/appdaemon/settings/apps/security/lock.yaml b/appdaemon/settings/apps/security/lock.yaml new file mode 100644 index 0000000..166090d --- /dev/null +++ b/appdaemon/settings/apps/security/lock.yaml @@ -0,0 +1,11 @@ +lock: + module: lock + class: Lock + constraint: input_boolean.lock_report + user_65536: Данил (указ) + user_65537: Данил (сред) + user_65538: Света (указ) + user_65539: Мама (указ) + notify: telegram + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/security/security_sensors.py b/appdaemon/settings/apps/security/security_sensors.py new file mode 100644 index 0000000..70949a2 --- /dev/null +++ b/appdaemon/settings/apps/security/security_sensors.py @@ -0,0 +1,48 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# Security sensors alarm notification +# Send notification if some movement is detecter or door any is open +# while nobody is home +# +# Args: +# sensor - gorup of sensors to listen +# ha_panel - alarm panel entity +# notify - notify entity to send message +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class SecuritySensorsReport(hass.Hass): + def initialize(self): + if 'sensor' not in self.args or 'ha_panel' not in self.args or 'notify' not in self.args: + self.error("Please provide sensor, ha_panel and notify in config!") + return + + attributes = self.get_state(self.args['sensor'], attribute = "all") + if 'attributes' in attributes: + attributes = attributes['attributes'] + if 'entity_id' in attributes: + for entity_id in attributes['entity_id']: + self.listen_state(self.sensor_trigger, entity_id) + + 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 old != new and new == "on": + alarm_panel_state = self.get_state(self.args['ha_panel']) + if alarm_panel_state != "armed_away": + return + self.alarm_triggered(entity, old, new) + + def alarm_triggered(self, entity, old, new): + self.log('alarm triggered') + self.call_service("alarm_control_panel/alarm_trigger", entity_id = self.args['ha_panel']) + + friendly_name = self.friendly_name(entity) + entity = entity.replace('_', '-') + message = "В ваше отсутствие сработал датчик безопастности {} ({}) = {}->{}!".format(friendly_name, entity, old, new) + self.notify(message, name = self.args['notify']) \ No newline at end of file diff --git a/appdaemon/settings/apps/security/security_sensors.yaml b/appdaemon/settings/apps/security/security_sensors.yaml new file mode 100644 index 0000000..cf02473 --- /dev/null +++ b/appdaemon/settings/apps/security/security_sensors.yaml @@ -0,0 +1,9 @@ +security_sensors: + module: security_sensors + class: SecuritySensorsReport + sensor: group.security_sensors + constraint: input_boolean.security_sensors_report + ha_panel: alarm_control_panel.ha_alarm + notify: telegram + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/sensors/mos_lib.py b/appdaemon/settings/apps/sensors/mos_lib.py new file mode 100644 index 0000000..1d85958 --- /dev/null +++ b/appdaemon/settings/apps/sensors/mos_lib.py @@ -0,0 +1,628 @@ +# -*- coding: utf-8 -*- +from __future__ import print_function +import requests +from datetime import datetime, tzinfo +from copy import deepcopy + + +class AuthException(Exception): + pass + +class EmpServerException(Exception): + pass + + +class Client(object): + """ + Клиента + """ + def __init__(self, **kwargs): + self.token = kwargs.get('token') + self.guid = kwargs.get('guid') + + #requests + self.verify = kwargs.get('verify', True) + self.timeout = kwargs.get('timeout', 3.0) + + self.session_id = None # emp mos ru + self.user_agent = kwargs.get('user_agent') + self.dev_app_version = kwargs.get('dev_app_version') + self.dev_user_agent = kwargs.get('dev_user_agent') + self.headers = {'Cache-Control': 'no-cache', + 'Host': 'emp.mos.ru', + 'Connection': 'Keep-Alive', + 'Accept-Encoding': 'gzip', + 'User-Agent': self.user_agent} + self.pheaders = deepcopy(self.headers) + self.pheaders.update({ + 'X-Cache-ov': '15552000', + 'X-Cache-ov-mode': 'FORCE_NETWORK' + }) + self.session = requests.Session() + + def raise_for_status(self, answer): + """ + :param answer: JSON + { + u'errorCode': 0, # 0-OK, + 401-Auth error + u'execTime': 0.138262, + u'errorMessage': u'', # error message + u'session_id': u'6c3333333c33333e44e21e7d43c46e03', + u'result': None or JSON + } + :return: + """ + # print('Exec time:', answer['execTime']) + if answer['errorCode'] == 401: + raise AuthException() + + if answer['errorCode'] != 0: + if answer['errorMessage']: + raise EmpServerException(u'{0} (code:{1})'.format(answer['errorMessage'], answer['errorCode'])) + raise EmpServerException(u'code:{0}'.format(answer['errorCode'])) + + def is_active(self): + """ + :return: True если уже залогинился + """ + return self.session_id is not None + + def login(self, telephone, pwd): + """ + :param telephone: Телефон. Вид: 7xxxxxxxxxx + :param pwd: Пароль из приложения Госуслуги Москвы + :return: JSON + { + u'is_filled': True, + u'surname': u'x', + u'name': u'x', + u'session_id': u'6c3333333c33333e44e21e7d43c46e03'}, + u'request_id': u'UN=PRO-12345678-1234-1234-1234-123456789123' + } + """ + login_data = { + 'device_info': { + 'guid': self.guid, + 'user_agent': self.dev_user_agent, + 'app_version': self.dev_app_version + }, + 'auth': { + 'login': telephone, + 'password': pwd, + 'guid': self.guid + } + } + + ret = self.session.post('https://emp.mos.ru/v1.0/auth/virtualLogin', + params={'token': self.token}, + headers={'Content-Type': 'application/json; charset=UTF-8', + 'Connection': 'Keep-Alive', + 'Accept-Encoding': 'gzip', + 'User-Agent': self.user_agent, + 'cache-control': 'no-cache', + 'Host': 'emp.mos.ru', + 'Accept': '*/*'}, + verify=self.verify, + timeout=self.timeout, + json=login_data) + + response = ret.json() + self.raise_for_status(response) + self.session_id = response['session_id'] + return response['result'] + + def get_profile(self): + """ + :return: JSON + { + u'drive_license': None, + u'firstname': u'x', + u'middlename': u'x', + u'lastname': u'x', + u'birthdate': u'dd.mm.YYYY', + u'msisdn': u'71234567890', # your telephone + u'email_confirmed': True, + u'email': u'x@x.ru' + } + """ + assert self.session_id + + ret = self.session.get('https://emp.mos.ru/v1.0/profile/get', + params={'token': self.token, + 'info[guid]': self.guid, + 'auth[session_id]': self.session_id + }, + headers=self.pheaders, + verify=self.verify, + timeout=self.timeout) + + response = ret.json() + self.raise_for_status(response) + return response['result']['profile'] + + def get_flats(self): + """ + :return: JSON array + [{ + u'name': u'x', + u'paycode': u'1234567890', + u'flat_id': u'1234567', + u'electro_account': None, + u'flat_number': u'0', + u'unad': u'0', + u'address': u'x', + u'electro_device': None, + u'unom': u'1234567' # Идентификатор дома + },{...} + ] + """ + assert self.session_id + + ret = self.session.get('https://emp.mos.ru/v1.0/flat/get', + params={'token': self.token, + 'info[guid]': self.guid, + 'auth[session_id]': self.session_id + }, + headers=self.pheaders, + verify=self.verify, + timeout = self.timeout) + + response = ret.json() + self.raise_for_status(response) + return response['result'] + + def get_watercounters(self, flat_id): + """ + :param flat_id: unicode string from flat_response + response[0]['flat_id'] + :return: + { + u'stat_title': u'x', + u'archive': [ + { + u'cold_indication': 1.55, + u'hot_indication': 1.2, + u'period': u'2017-08-31+03:00' + }, { + u'cold_indication': 1.3, + u'hot_indication': 0.39999999999998, + u'period': u'2017-09-30+03:00' + }, { + +10 эл-тов. Всего 12. + }], + u'counters': [ + { + u'checkup': u'2021-08-08+03:00', # дата поверки + u'counterId': 123456, # id счетчика в emp.mos.ru + u'num': u'123456', # серийный номер счетчика + u'type': 1, # ХВС + u'indications': [ + { + u'indication': u'200.24', + u'period': u'2018-07-31+03:00' + }, { + u'indication': u'200.6', + u'period': u'2018-06-30+03:00' + }, { + u'indication': u'200.5', + u'period': u'2018-05-31+03:00' + }, { + u'indication': u'200.5', + u'period': u'2018-04-30+03:00' + }] + }, + {..} + ] + } + """ + assert self.session_id + wheaders = deepcopy(self.headers) + wheaders.update({ + 'X-Clears-tags': 'WATER_COUNTERS', + 'X-Cache-ov-mode': 'DEFAULT', + 'Content-Type': 'application/json; charset=UTF-8' }) + + wcrequest = { + 'flat_id': flat_id, + 'is_widget': False, + 'info': { + 'guid': self.guid + }, + 'auth': { + 'session_id': self.session_id + } + } + + ret = self.session.post('https://emp.mos.ru/v1.0/watercounters/get', + params={'token': self.token}, + headers=wheaders, + verify=self.verify, + timeout=self.timeout, + json=wcrequest) + + response = ret.json() + self.raise_for_status(response) + return response['result'] + + def send_watercounters(self, flat_id, counters_data): + """ + :param flat_id: flat_response['flat_id'] + :param counters_data: array of + [{ + 'counter_id': u'123456', # ['counters'][0]['counterId'] + 'period': datetime.now().strftime("%Y-%m-%d"), + 'indication': 'xxx,xx' + }, { + ... + }] + :return: + """ + assert self.session_id + + wheaders = deepcopy(self.headers) + wheaders.update({ + 'X-Clears-tags': 'WATER_COUNTERS', + 'Content-Type': 'application/json; charset=UTF-8' + }) + + wcrequest = {'flat_id': flat_id, + 'counters_data': counters_data, + 'info': { + 'guid': self.guid + }, + 'auth': { + 'session_id': self.session_id + }} + + ret = self.session.post('https://emp.mos.ru/v1.0/watercounters/addValues', + params={'token': self.token}, + headers=wheaders, + verify=self.verify, + timeout=self.timeout, + json=wcrequest) + + response = ret.json() + self.raise_for_status(response) + return response['result'] + + def get_electrocounters(self, flat_id): + """ + :param flat_id: unicode string from flat_response + response[0]['flat_id'] + :return: + { + "address": "город ...", + "electro_account": "851xxxx475", + "electro_device": "04xxx17", + "balance": 0, + "is_debt": false, + "description": "Внимание! Передача показаний возможна с 15 по 26 число месяца. При вводе текущих показаний обращайте внимание на значность счетчиков. В случае если показания введены с неверной значностью (большей, чем значность счетчика), они не будут загружены.", + "sh_znk": 5, + "zones": [{ + "name": "T1", + "value": "6887" + }], + "intervals": [{ + "name": "T1", + "value": "00:00-23:59" + }] + } + """ + assert self.session_id + wheaders = deepcopy(self.headers) + wheaders.update({ + 'X-Clears-tags': 'ELECTRO_COUNTERS', + 'X-Cache-ov-mode': 'DEFAULT', + 'Content-Type': 'application/json; charset=UTF-8' }) + + wcrequest = { + 'flat_id': flat_id, + 'is_widget': False, + 'info': { + 'guid': self.guid, + 'user_agent': self.dev_user_agent, + 'app_version': self.dev_app_version + }, + 'auth': { + 'session_id': self.session_id + } + } + + ret = self.session.post('https://emp.mos.ru/v1.0/electrocounters/get', + params={'token': self.token}, + headers=wheaders, + verify=self.verify, + timeout=self.timeout, + json=wcrequest) + + response = ret.json() + self.raise_for_status(response) + return response['result'] + + def send_electrocounters(self, flat_id, counters_data): + """ + :param flat_id: flat_response['flat_id'] + :param counters_data: array of + [{ + 'counter_id': u'123456', # ['counters'][0]['counterId'] + 'period': datetime.now().strftime("%Y-%m-%d"), + 'indication': 'xxx,xx' + }, { + ... + }] + :return: + """ + assert self.session_id + + wheaders = deepcopy(self.headers) + wheaders.update({ + 'X-Clears-tags': 'ELECTRO_COUNTERS', + 'Content-Type': 'application/json; charset=UTF-8' + }) + + wcrequest = {'flat_id': flat_id, + 'counters_data': counters_data, + 'info': { + 'guid': self.guid, + 'user_agent': self.dev_user_agent, + 'app_version': self.dev_app_version + }, + 'auth': { + 'session_id': self.session_id + }} + ret = self.session.post('https://emp.mos.ru/v1.0/electrocounters/addValues', + params={'token': self.token}, + headers=wheaders, + verify=self.verify, + timeout=self.timeout, + json=wcrequest) + + response = ret.json() + self.raise_for_status(response) + return response['result'] + + def get_epd(self, flat_id, period, is_debt=True): + """ + :param flat_id: unicode string from flat_response + response[0]['flat_id'] + :param period: unicode string represents date in 27.09.2018 format + :param is_debt: True/False + :return: + { + "is_debt": true, + "is_paid": true, + "amount": 2982.77, + "service_code": "emp.zkh", + "insurance": 61.93, + "ammount_insurance": 3044.7 + } + """ + assert self.session_id + wheaders = deepcopy(self.headers) + wheaders.update({ + 'X-Clears-tags': 'EPD', + 'X-Cache-ov-mode': 'DEFAULT', + 'Content-Type': 'application/json; charset=UTF-8' }) + + wcrequest = { + 'flat_id': flat_id, + 'period': period, + 'is_debt': is_debt, + 'info': { + 'guid': self.guid + }, + 'auth': { + 'session_id': self.session_id + } + } + + ret = self.session.post('https://emp.mos.ru/v1.1/epd/get', + params={'token': self.token}, + headers=wheaders, + verify=self.verify, + timeout=self.timeout, + json=wcrequest) + + response = ret.json() + self.raise_for_status(response) + return response['result'] + + def get_car_fines(self, sts_number): + """ + :param sts_number: unicode string contains car sts_numer + :return: + { + "paid": [{ + "seriesAndNumber": "xxxxx", + "date": "2018-08-30+03:00", + "offence_place": "МОСКВА Г. МКАД,xxxx, ВНЕШНЯЯ СТОРОНА", + "offenceType": "12.9ч.2 - Превышение установленной скорости движения транспортного средства на величину от 20 до 40 километров в час ", + "cost": "500", + "is_discount": false, + "drive_license": null, + "sts_number": "xxxxxxx", + "executionState": "Исполнено", + "is_fssp": false + }], + "unpaid": [] + } + """ + assert self.session_id + wheaders = deepcopy(self.headers) + wheaders.update({ + 'X-Clears-tags': 'FORCE_NETWORK', + 'X-Cache-ov-mode': 'DEFAULT', + 'Content-Type': 'application/json; charset=UTF-8' }) + + wcrequest = { + 'sts_number': sts_number, + 'info': { + 'guid': self.guid + }, + 'auth': { + 'session_id': self.session_id + } + } + + ret = self.session.post('https://emp.mos.ru/v1.0/offence/getOffence', + params={'token': self.token}, + headers=wheaders, + verify=self.verify, + timeout=self.timeout, + json=wcrequest) + + response = ret.json() + self.raise_for_status(response) + return response['result'] + + def logout(self, timeout=None): + """ + Почему то очень долго выполняется (5 сек) + """ + if self.session_id: + logout_data = { + 'info': { + 'guid': self.guid + }, + 'auth': { + 'session_id': self.session_id + } + } + ret = self.session.post('https://emp.mos.ru/v1.0/auth/logout', + params={'token': self.token}, + headers=self.headers, + timeout=timeout or self.timeout, + json=logout_data) + + response = ret.json() + self.raise_for_status(response) + self.session_id = None + return response['result'] + + +class MosAPI(object): + """ + kwargs: + + token: уникальный ключ приложения + guid: некий уникальный ключ + https_verify: ключ verify в GET, POST запросах, по умолчанию 'False' + timeout: ключ timeout в GET, POST запросах + user_agent: версия веб клиента + dev_user_agent: 'Android' для ОС Android + dev_app_version: версия ОС + """ + + def __init__(self, **kwargs): + self.kwargs = kwargs + self._clients = {'default': Client(**kwargs)} + + def client(self, client_id='default', **kwargs): + if client_id and \ + client_id not in self._clients: + k = self.kwargs + k.update(kwargs) + self._clients[client_id] = Client(**k) + return self._clients[client_id] + + # if only one client + def is_active(self): + return self.client().is_active() + def login(self, *args): + return self.client().login(*args) + def logout(self, *args): + return self.client().logout(*args) + + def get_profile(self): + return self.client().get_profile() + def get_flats(self): + return self.client().get_flats() + def get_watercounters(self, *args): + return self.client().get_watercounters(*args) + def send_watercounters(self, *args): + return self.client().send_watercounters(*args) + def get_electrocounters(self, *args): + return self.client().get_electrocounters(*args) + def send_electrocounters(self, *args): + return self.client().send_electrocounters(*args) + def get_epd(self, *args): + return self.client().get_epd(*args) + def get_car_fines(self, *args): + return self.client().get_car_fines(*args) + +class Water(): + COLD = 1 + HOT = 2 + + @staticmethod + def water_abbr(water): + if water == Water.COLD: return u'ХВС' + elif water == Water.HOT: return u'ГВС' + + @staticmethod + def name(water): + if water == Water.COLD: return u'холодная вода' + elif water == Water.HOT: return u'горячая вода' + + +class Watercounter(): + """ + [{ 'counterId': 1437373, # внутреннее id счетчика + 'type': 1, # тип воды + 'num': '417944', # серийный номер счетчика + 'checkup': '2023-09-25+03:00', # дата следующей поверки + 'indications': + [{'period': '2018-08-31+03:00', 'indication': '21.38'}, + {'period': '2018-07-31+03:00', 'indication': '20.7'}, + {'period': '2018-06-30+03:00', 'indication': '19'}] + }, + {...} + ] + + list(filter(lambda x: x['num'] == num, response['counters'])) + list(filter(lambda x: x['counterId'] == id, response['counters'])) + list(filter(lambda x: x['type'] == water_type_id, response['counters'])) + """ + + @staticmethod + def last_value(counter): + """ + alphabetical sort data =) + :param counter: counter JSON + :return: float or None + """ + indications = counter['indications'] + indications.sort(key=lambda x: x['period']) + if indications: + return float(indications[-1]['indication']) + + @staticmethod + def water_title(counter): + return Watercounter.humanreadable_name(counter['type']) + + @staticmethod + def checkup(counter): + """ + https://docs.python.org/3/library/datetime.html#datetime.timezone + Если нужно UTC d.replace(tzinfo=None) + + :param counter: counter JSON + :return: datetime с временной зоной + """ + checkup = counter['checkup'] + d = datetime.strptime(checkup, '%Y-%m-%d%z') #'checkup': '2023-09-25+03:00' + return d + + @staticmethod + def serialize_for_send(counter, value): + """ + :param counterId: id счетчика. не номер из приложения! + :param value: значение float + :return: dict + """ + return { + 'counter_id': int(counter['counterId']), + 'period': datetime.now().strftime("%Y-%m-%d"), + 'indication': '{:.2f}'.format(value).replace('.', ',') + } diff --git a/appdaemon/settings/apps/sensors/mosenergosbyt.py b/appdaemon/settings/apps/sensors/mosenergosbyt.py new file mode 100644 index 0000000..b6d1652 --- /dev/null +++ b/appdaemon/settings/apps/sensors/mosenergosbyt.py @@ -0,0 +1,152 @@ +import requests +import hashlib +import globals +import datetime as dt +from xml.etree import ElementTree +from automation import Automation, Base # type: ignore + +class MosEnergoSbytSender(Automation): + def initialize(self): + super().initialize() + self.run_daily(self.send_mosenergosbyt_counters, dt.time(12, 0, 0)) + # self.send_mosenergosbyt_counters({}) + + def send_mosenergosbyt_counters(self, kwargs): + if not 'home_power_total' in self.entity_ids: + self.error('Please define home_power_total in entity_ids.') + return + + current_counter_value = int(float(self.get_state(self.entity_ids['home_power_total']))) + self.log('Sending current counter value: {}'.format(current_counter_value)) + self.app.send(current_counter_value) + + +class MosEnergoSbytUpdater(Automation): + def initialize(self): + super().initialize() + self.timer = self.run_every(self.update_sensors, self.datetime(), 1*60*60) + # self.send_mosenergosbyt_counters({}) + + def update_sensors(self, args) -> None: + self.app.update() + counter_value = self.app.value + counter_date = self.app.date + balance = self.app.balance + self.log('Current counter value is: {} (@{}), balance is: {}'.format(counter_value, counter_date, balance)) + + if 'mosenergosbyt_total' in self.entity_ids: + entity_id = self.entity_ids['mosenergosbyt_total'] + attributes = { + 'unit_of_measurement': 'kWh', + 'date': counter_date, + # 'checkup': checkup, + 'icon': 'mdi:av-timer', + 'friendly_name': 'Счетчик (мосэнергосбыт)', + 'description': 'Created and updated from appdaemon ({})'.format(__name__) + } + self.set_state(entity_id, state = counter_value, attributes = attributes) + + if 'mosenergosbyt_balance' in self.entity_ids: + entity_id = self.entity_ids['mosenergosbyt_balance'] + attributes = { + 'unit_of_measurement': '₽', + 'date': counter_date, + 'icon': 'mdi:bank-transfer', + 'friendly_name': 'Баланс (мосэнергосбыт)', + 'description': 'Created and updated from appdaemon ({})'.format(__name__) + } + self.set_state(entity_id, state = balance, attributes = attributes) + +class MosEnergoSbytCounter(Base): + _version = 11 + _device = "ios/12.1.2/2.14.14(221)" + _session = None + _profile = None + + # def initialize(self): + # super().initialize() + + @property + def value(self) -> float: + """Return the current state of counter.""" + if (self._profile == None): + self.update() + return float(self._profile['SH_POK_STR']) + + @property + def balance(self) -> float: + """Return the current state of counter.""" + if (self._profile == None): + self.update() + balance = -1 * float(self._profile['SM_DEBT']) + if balance == -0: + balance = 0 + return balance + + @property + def date(self) -> str: + """Return the current state of counter.""" + if (self._profile == None): + self.update() + return self._profile['DT_POK'] + + def is_active(self): + """ + :return: True если уже залогинился + """ + return self._session is not None + + def update(self) -> None: + if not self.is_active(): + self._session = self._get_session() + url = 'https://lkkbyt.mosenergosbyt.ru/gate/do?api_version={}&id_session={}&process=refresh&type=1'.format(self._version, self._session) + response = requests.get(url).text + values_response = self._parse_response(response) + self._profile = values_response + # self.log(values_response) + + def send(self, value) -> bool: + if not self.is_active(): + self._session = self._get_session() + + url = 'https://lkkbyt.mosenergosbyt.ru/gate/do?id_session={}&pr_check_ras=0&pr_flat_pu=0&process=dotranspok&type=3&vl_pok_t1={}&vl_pok_t2=0&vl_pok_t3=0'.format(self._session, value) + response = requests.get(url).text + send_response = self._parse_response(response) + result = send_response['KD_RESULT'] + if result == 0: + return True + return False + + def _get_session(self) -> str: + login = str(self.properties['login']) + pass_md5 = hashlib.md5(self.properties['pwd'].encode('utf-8')).hexdigest() + ls = '' + phone = '' + if len(login) == 10: + ls = login + elif len(login) == 11: + phone = login + else: + self.error('unknown login type: {}'.format(login)) + return + url = "https://lkkbyt.mosenergosbyt.ru/gate/do?api_version={}&id_device={}&phone={}&ls={}&process=login&pw_abn={}&type=1".format(self._version, self._device, phone, ls, pass_md5) + response = requests.get(url).text + session_response = self._parse_response(response) + session = session_response['ID_SESSION'] + return session + + def _parse_response(self, response): + et = ElementTree.fromstring(response) + + headers = [] + values = [] + for node in et.findall('./h/p'): + headers.append(node.text) + for node in et.findall('./b/p'): + values.append(node.text) + result = {} + for i in range(0, len(headers)): + header = headers[i] + value = values[i] + result[header] = value + return result diff --git a/appdaemon/settings/apps/sensors/mosenergosbyt.yaml b/appdaemon/settings/apps/sensors/mosenergosbyt.yaml new file mode 100644 index 0000000..63f5483 --- /dev/null +++ b/appdaemon/settings/apps/sensors/mosenergosbyt.yaml @@ -0,0 +1,47 @@ +mosenergo_counter: + module: mosenergosbyt + class: MosEnergoSbytCounter + properties: + login: !secret mosenergo_main_login + pwd: !secret mosenergo_main_pwd + +mosenergo_counter_updater: + module: mosenergosbyt + class: MosEnergoSbytUpdater + entity_ids: + mosenergosbyt_total: sensor.power_mosenergosbyt_total + mosenergosbyt_balance: sensor.power_mosenergosbyt_balance + app: mosenergo_counter + dependencies: + - mosenergo_counter + global_dependencies: + - globals + +mosenergo_counter_sender: + module: mosenergosbyt + class: MosEnergoSbytSender + entity_ids: + home_power_total: sensor.power_main_total + app: mosenergo_counter + dependencies: + - mosenergo_counter + global_dependencies: + - globals + + +mosenergo_alt: + module: mosenergosbyt + class: MosEnergoSbytCounter + properties: + login: !secret mosenergo_alt_login + pwd: !secret mosenergo_alt_pwd + +mosenergo_alt_updater: + module: mosenergosbyt + class: MosEnergoSbytUpdater + app: mosenergo_alt + dependencies: + - automation + - mosenergo_alt + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/sensors/mosru.py b/appdaemon/settings/apps/sensors/mosru.py new file mode 100644 index 0000000..edf6f47 --- /dev/null +++ b/appdaemon/settings/apps/sensors/mosru.py @@ -0,0 +1,281 @@ +import globals +import datetime as dt +from automation import Automation, Base +from datetime import datetime, tzinfo, timedelta +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): + 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 WaterCounterSensor(Automation): + timezone = '+03:00' + def initialize(self): + super().initialize() + self.timer = self.run_every(self.update_sensors, self.datetime(), 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 EpdBalanceSensor(Automation): + def initialize(self): + super().initialize() + self.timer = self.run_every(self.update_sensors, self.datetime(), 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']): + 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): + 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) + +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['is_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['is_paid'] + if epd_is_paid: + continue + balance = balance - epd['amount'] + return balance + + def update_epd(self) -> None: + self._check_login() + data = {} + date = datetime.now().date() + for i in range (0,6): + process_date = date - timedelta(i*365/12) + date_str = process_date.strftime('%d.%m.%Y') + epd = self._api.get_epd(self._flat_id, date_str, False) + for i in range(0, 10): #retries + if epd != []: + break + epd = self._api.get_epd(self._flat_id, date_str, False) + epd_first = epd[0] + data[date_str] = epd_first + # 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)) diff --git a/appdaemon/settings/apps/sensors/mosru.yaml b/appdaemon/settings/apps/sensors/mosru.yaml new file mode 100644 index 0000000..7a4e35f --- /dev/null +++ b/appdaemon/settings/apps/sensors/mosru.yaml @@ -0,0 +1,50 @@ +################################# +# 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 + 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 + +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 + diff --git a/appdaemon/settings/apps/sensors/water_leak.py b/appdaemon/settings/apps/sensors/water_leak.py new file mode 100644 index 0000000..290eecb --- /dev/null +++ b/appdaemon/settings/apps/sensors/water_leak.py @@ -0,0 +1,53 @@ +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 = '' + 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.listen_state(self.water_leak, self.args['sensor']) + + def water_leak(self, entity, attribute, old, new, kwargs): + if new == "on": + self.send_alarm() + + def send_alarm(self): + 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/bath/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" + attributes = self.get_state(self.args['sensor'], attribute = "all") + if 'attributes' in attributes: + attributes = attributes['attributes'] + if 'entity_id' in attributes: + for entity_id in attributes["entity_id"]: + message += '{} ({}) = {}\n'.format(self.friendly_name(entity_id), entity_id, self.get_state(entity_id)) + self.notify(message, name = self.args['notify']) diff --git a/appdaemon/settings/apps/sensors/water_leak.yaml b/appdaemon/settings/apps/sensors/water_leak.yaml new file mode 100644 index 0000000..ec3575d --- /dev/null +++ b/appdaemon/settings/apps/sensors/water_leak.yaml @@ -0,0 +1,13 @@ +################################# +# WATER LEAK # +################################# +water_leak: + module: water_leak + class: WaterLeak + constraint: input_boolean.water_leak_control + sensor: group.water_leak + valve: switch.water_valve + notify: telegram + gw_mac: !secret aqara1_mac + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/test/db.py b/appdaemon/settings/apps/test/db.py new file mode 100644 index 0000000..96e003f --- /dev/null +++ b/appdaemon/settings/apps/test/db.py @@ -0,0 +1,28 @@ +import appdaemon.plugins.hass.hassapi as hass +from datetime import datetime, timedelta + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, scoped_session + +class Db(hass.Hass): + def initialize(self): + # Set up callback and listen for state change of desired switch + self.listen_state(self.entity_changed, self.args["entity_id"]) + self.update_stats() + + def entity_changed(self, entity, attribute, old, new, kwargs): + self.update_stats() + + def update_stats(self): + engine = create_engine(self.args["connectionstring"]) + session_obj = sessionmaker(bind=engine) + session = scoped_session(session_obj) + + results = session.execute('SELECT state,last_changed FROM states where entity_id = "' + self.args["entity_id"] + '" and state<>\'unknown\' and last_changed>=(NOW() - INTERVAL 1 MONTH) order by last_changed limit 1') + row = results.first() + start_value = float(row['state']) + start_date = row['last_changed'] + + current_value = float(self.get_state(self.args["entity_id"])) + diff = round(current_value - start_value,2) + self.log('Counter start value: {} (@{}), current value: {}, diff: {}'.format(start_value, start_date, current_value, diff)) \ No newline at end of file diff --git a/appdaemon/settings/apps/test/db.yaml b/appdaemon/settings/apps/test/db.yaml new file mode 100644 index 0000000..2f4bfff --- /dev/null +++ b/appdaemon/settings/apps/test/db.yaml @@ -0,0 +1,9 @@ +db: + module: db + class: Db + connectionstring: !secret mysql_url + entity_id: sensor.power_total + + debug: true + global_dependencies: + - automation \ No newline at end of file diff --git a/appdaemon/settings/apps/test/eventMonitor.py b/appdaemon/settings/apps/test/eventMonitor.py new file mode 100644 index 0000000..e1076e5 --- /dev/null +++ b/appdaemon/settings/apps/test/eventMonitor.py @@ -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) + \ No newline at end of file diff --git a/appdaemon/settings/apps/test/eventMonitor.yaml b/appdaemon/settings/apps/test/eventMonitor.yaml new file mode 100644 index 0000000..eaeef82 --- /dev/null +++ b/appdaemon/settings/apps/test/eventMonitor.yaml @@ -0,0 +1,6 @@ +# eventMonitor: +# module: eventMonitor +# class: Monitor +# global_dependencies: +# - automation +# events: \ No newline at end of file diff --git a/appdaemon/settings/apps/timers/entity_timer.py b/appdaemon/settings/apps/timers/entity_timer.py new file mode 100644 index 0000000..6bea0b6 --- /dev/null +++ b/appdaemon/settings/apps/timers/entity_timer.py @@ -0,0 +1,34 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +# +# Entity timer +# Turn on and off this entities +# +# Args: +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class EntityTimer(hass.Hass): + 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.on_timer_tick, on_time) + self.off_timer = self.run_daily(self.off_timer_tick, off_time) + + def on_timer_tick(self, kwargs): + self.log('on_timer') + if 'entity' in self.args: + self.turn_on(self.args['entity']) + + def off_timer_tick(self, kwargs): + self.log('off_timer') + if 'entity' in self.args: + self.turn_off(self.args['entity']) \ No newline at end of file diff --git a/appdaemon/settings/apps/timers/entity_timer.yaml b/appdaemon/settings/apps/timers/entity_timer.yaml new file mode 100644 index 0000000..b706fe1 --- /dev/null +++ b/appdaemon/settings/apps/timers/entity_timer.yaml @@ -0,0 +1,35 @@ +led_timer: + module: entity_timer + class: EntityTimer + on_time: "09:00:00" + off_time: "22:00:00" + entity: switch.ledstrip + global_dependencies: + - globals + +led_window_timer: + module: entity_timer + class: EntityTimer + on_time: "09:00:00" + off_time: "23:00:00" + entity: switch.wifi_socket + global_dependencies: + - globals + +xmas_tree_timer: + module: entity_timer + class: EntityTimer + on_time: "09:00:00" + off_time: "23:00:00" + entity: switch.broadlink_switch + global_dependencies: + - globals + +# tv_timer: +# module: entity_timer +# class: EntityTimer +# on_time: "09:00:00" +# off_time: "22:30:00" +# entity: switch.plug_158d0001104a0c +# global_dependencies: +# - globals \ No newline at end of file diff --git a/appdaemon/settings/dashboards/Hello.dash b/appdaemon/settings/dashboards/Hello.dash new file mode 100755 index 0000000..cead237 --- /dev/null +++ b/appdaemon/settings/dashboards/Hello.dash @@ -0,0 +1,14 @@ +# +# Main arguments, all optional +# +title: Hello Panel +widget_dimensions: [120, 120] +widget_margins: [5, 5] +columns: 8 + +label: + widget_type: label + text: Hello World + +layout: + - label(2x2) diff --git a/appdaemon/settings/secrets.yaml b/appdaemon/settings/secrets.yaml new file mode 100755 index 0000000..e69de29 diff --git a/certbot/docker/update_certs.sh b/certbot/docker/update_certs.sh new file mode 100755 index 0000000..5effaa5 --- /dev/null +++ b/certbot/docker/update_certs.sh @@ -0,0 +1,6 @@ +#!/bin/sh +while [ 1 ]; +do + certbot renew --agree-tos --standalone --preferred-challenges http-01 + sleep 1d +done diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e571382 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,154 @@ +version: '3' +services: + mosquitto: + image: "monster1025/alpine86-mosquitto:latest" + container_name: mosquitto + env_file: ./mosquitto/mqtt.env + ports: + - "1883:1883" + restart: always + + mysql: + image: mariadb + container_name: mysql + env_file: ./mysql/mysql.env + ports: + - "3306:3306/tcp" + restart: unless-stopped + volumes: + - /srv/mysql:/var/lib/mysql + + hass: + image: homeassistant/home-assistant + container_name: hass + volumes: + - ./hass/settings:/config + - ./hass/settings/hosts.txt:/etc/hosts:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "8123:8123" + - "9898:9898/udp" + - "4321:4321/udp" + - "51827:51827" #Homekit + depends_on: + - mysql + - mosquitto + - influxdb + - broadlink + network_mode: "host" + restart: always + logging: + driver: json-file + options: + max-size: 50m + + appdaemon: + image: monster1025/appdaemon + build: + context: ./appdaemon + args: + VERSION: 3.0.2 + container_name: appdaemon + depends_on: + - hass + volumes: + - ./appdaemon/settings:/conf + - ./hass/settings/secrets.yaml:/conf/secrets.yaml:ro + - ./hass/settings/hosts.txt:/etc/hosts:ro + - /etc/localtime:/etc/localtime:ro + restart: always + links: + - "hass" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + nginx: + image: nginx + hostname: nginx + restart: always + container_name: nginx + links: + - "hass" + - "appdaemon" + volumes: + - /srv/homeassistant/hosts.txt:/etc/hosts:ro + volumes: + - /srv/ssl:/config/keys + - ./nginx-config/site-confs:/etc/nginx/conf.d + ports: + - "80:80" + - "443:443" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + certbot: + image: certbot/certbot + restart: always + container_name: certbot + volumes: + - ./certbot/docker/update_certs.sh:/update_certs.sh:ro + - /srv/ssl:/etc/letsencrypt + - /var/lib/letsencrypt:/var/lib/letsencrypt + - /etc/localtime:/etc/localtime:ro + ports: + - "8081:80" + entrypoint: ["/update_certs.sh"] + #command: certonly --standalone --agree-tos --standalone-supported-challenges http-01 --email admin@yandex5.ru -d home.yandex5.ru + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + broadlink: + image: monster1025/broadlink + container_name: broadlink + links: + - "mosquitto" + env_file: ./broadlink/broadlink.env + restart: always + logging: + driver: json-file + options: + max-size: 50m +########################### +# GRAFANA # +########################### + grafana: + image: grafana/grafana + container_name: grafana + volumes: + - "/srv/grafana:/var/lib/grafana" + environment: + - GF_SECURITY_ADMIN_PASSWORD=grafanapass + links: + - "influxdb:influxdb" + ports: + - "3000:3000" + restart: always + logging: + driver: json-file + options: + max-size: 50m + + influxdb: + image: influxdb + container_name: influxdb + volumes: + - "/etc/localtime:/etc/localtime:ro" + - "/srv/influxdb/db:/var/lib/influxdb" + - "/srv/influxdb/log:/var/log/influxdb" + ports: + - "8083:8083" + - "8086:8086" + restart: always + logging: + driver: json-file + options: + max-size: 50m \ No newline at end of file diff --git a/hass/settings/configuration.yaml b/hass/settings/configuration.yaml new file mode 100644 index 0000000..c7a0b25 --- /dev/null +++ b/hass/settings/configuration.yaml @@ -0,0 +1,62 @@ +homeassistant: + name: Home + latitude: !secret home_latitude + longitude: !secret home_longitude + elevation: 124 + unit_system: metric + time_zone: !secret timezone + customize: !include_dir_merge_named configuration/customize + customize_domain: !include customize_domain.yaml + packages: !include_dir_named packages/ + +http: + api_password: !secret ha_password + base_url: !secret base_url + use_x_forwarded_for: true + trusted_proxies: + - 172.19.0.0/24 + +# Enables the frontend +frontend: +lovelace: + mode: yaml + +# Enables configuration UI +config: +discovery: + +# Enables support for tracking state changes over time +recorder: !include configuration/recorder.yaml +history: +logbook: + +# Components +automation: !include_dir_merge_list configuration/automation +apple_tv: !include_dir_merge_list configuration/appletv +alarm_control_panel: !include_dir_merge_list configuration/alarm_control_panel +binary_sensor: !include_dir_merge_list configuration/binary_sensor +camera: !include_dir_merge_list configuration/camera +#climate: !include_dir_merge_list configuration/climate +group: !include_dir_merge_named configuration/group +input_boolean: !include_dir_merge_named configuration/input/input_boolean +input_datetime: !include_dir_merge_named configuration/input/input_datetime +#input_number: !include_dir_merge_named configuration/input/input_number +light: !include_dir_merge_list configuration/light +notify: !include_dir_merge_list configuration/notify +media_player: !include_dir_merge_list configuration/media_player +script: !include_dir_merge_named configuration/script +sensor: !include_dir_merge_list configuration/sensor +switch: !include_dir_merge_list configuration/switch +tts: !include_dir_merge_list configuration/tts + +#one item +influxdb: !include configuration/influxdb.yaml +mqtt: !include configuration/mqtt/mqtt.yaml +telegram_bot: !include configuration/bot/telegram_bot.yaml +vacuum: !include configuration/xiaomi_aqara/vacuum.yaml +xiaomi_aqara: !include configuration/xiaomi_aqara/xiaomi_aqara.yaml +nest: !include configuration/nest/nest.yaml +homekit: !include configuration/homekit/homekit.yaml + +#power meter +modbus: !include configuration/modbus.yaml diff --git a/hass/settings/configuration/alarm_control_panel/alarm_control_panel.yaml b/hass/settings/configuration/alarm_control_panel/alarm_control_panel.yaml new file mode 100644 index 0000000..88b1d40 --- /dev/null +++ b/hass/settings/configuration/alarm_control_panel/alarm_control_panel.yaml @@ -0,0 +1,3 @@ +- platform: manual + pending_time: 5 + trigger_time: 5 \ No newline at end of file diff --git a/hass/settings/configuration/appletv/appletv4.yaml b/hass/settings/configuration/appletv/appletv4.yaml new file mode 100644 index 0000000..51ea406 --- /dev/null +++ b/hass/settings/configuration/appletv/appletv4.yaml @@ -0,0 +1,4 @@ +- name: appletv4 + host: !secret atv4_ip + login_id: !secret atv4_login + credentials: !secret atv4_credentials \ No newline at end of file diff --git a/hass/settings/configuration/automation/cube-events.yaml b/hass/settings/configuration/automation/cube-events.yaml new file mode 100644 index 0000000..988f5e3 --- /dev/null +++ b/hass/settings/configuration/automation/cube-events.yaml @@ -0,0 +1,59 @@ +- alias: cube_flip90 + trigger: + platform: event + event_type: cube_action + event_data: + entity_id: binary_sensor.cube_158d00011065e3 + click_type: flip90 + action: + event: cube_flip90 + +- alias: cube_flip180 + trigger: + platform: event + event_type: cube_action + event_data: + entity_id: binary_sensor.cube_158d00011065e3 + click_type: flip180 + action: + event: cube_flip180 + +- alias: cube_shake_air + trigger: + platform: event + event_type: cube_action + event_data: + entity_id: binary_sensor.cube_158d00011065e3 + click_type: shake_air + action: + event: cube_shake_air + +- alias: cube_move + trigger: + platform: event + event_type: cube_action + event_data: + entity_id: binary_sensor.cube_158d00011065e3 + click_type: move + action: + event: cube_move + +- alias: cube_swing + trigger: + platform: event + event_type: cube_action + event_data: + entity_id: binary_sensor.cube_158d00011065e3 + click_type: swing + action: + event: cube_swing + +- alias: cube_tap_twice + trigger: + platform: event + event_type: cube_action + event_data: + entity_id: binary_sensor.cube_158d00011065e3 + click_type: tap_twice + action: + event: cube_tap_twice \ No newline at end of file diff --git a/hass/settings/configuration/automation/homekit.yaml b/hass/settings/configuration/automation/homekit.yaml new file mode 100644 index 0000000..3de2bb9 --- /dev/null +++ b/hass/settings/configuration/automation/homekit.yaml @@ -0,0 +1,7 @@ +- alias: 'Start HomeKit' + trigger: + - platform: homeassistant + event: start + action: + - delay: 00:00:30 # Wait 30 seconds + - service: homekit.start \ No newline at end of file diff --git a/hass/settings/configuration/automation/intercom-events.yaml b/hass/settings/configuration/automation/intercom-events.yaml new file mode 100644 index 0000000..c9cedbc --- /dev/null +++ b/hass/settings/configuration/automation/intercom-events.yaml @@ -0,0 +1,8 @@ +- alias: Domofon event + trigger: + platform: mqtt + topic: home/domofon/domofon/calltime + action: + - event: domofon_call + event_data_template: + payload: '{{ trigger.payload }}' \ No newline at end of file diff --git a/hass/settings/configuration/automation/mqtt-button-events.old_yaml b/hass/settings/configuration/automation/mqtt-button-events.old_yaml new file mode 100644 index 0000000..34b8724 --- /dev/null +++ b/hass/settings/configuration/automation/mqtt-button-events.old_yaml @@ -0,0 +1,76 @@ +##################################################### +# MAINROOM # +##################################################### +- alias: button_living_click + trigger: + platform: mqtt + topic: home/switch/mainroom/status + payload: 'click' + action: + event: button_mainroom_click + +- alias: button_living_double_click + trigger: + platform: mqtt + topic: home/switch/mainroom/status + payload: 'double_click' + action: + event: button_mainroom_double_click + +##################################################### +# BATH # +##################################################### +- alias: button_bath_click + trigger: + platform: mqtt + topic: home/switch/bath/status + payload: 'click' + action: + event: button_bath_click + +- alias: button_bath_double_click + trigger: + platform: mqtt + topic: home/switch/bath/status + payload: 'double_click' + action: + event: button_bath_double_click + + +##################################################### +# HALL # +##################################################### +- alias: button_hall_click + trigger: + platform: mqtt + topic: home/switch/hall/status + payload: 'click' + action: + event: button_hall_click + +- alias: button_hall_double_click + trigger: + platform: mqtt + topic: home/switch/hall/status + payload: 'double_click' + action: + event: button_hall_double_click + +##################################################### +# KITCHEN # +##################################################### +- alias: button_kitchen_click + trigger: + platform: mqtt + topic: home/switch/kitchen/status + payload: 'click' + action: + event: button_kitchen_click + +- alias: button_kitchen_double_click + trigger: + platform: mqtt + topic: home/switch/kitchen/status + payload: 'double_click' + action: + event: button_kitchen_double_click diff --git a/hass/settings/configuration/automation/mqtt-cube-events.old_yaml b/hass/settings/configuration/automation/mqtt-cube-events.old_yaml new file mode 100644 index 0000000..1068043 --- /dev/null +++ b/hass/settings/configuration/automation/mqtt-cube-events.old_yaml @@ -0,0 +1,39 @@ +- alias: cube_flip90 + trigger: + platform: mqtt + topic: 'home/cube/mainroom/status' + payload: 'flip90' + action: + - event: cube_flip90 + +- alias: cube_flip180 + trigger: + platform: mqtt + topic: 'home/cube/mainroom/status' + payload: 'flip180' + action: + - event: cube_flip180 + +- alias: cube_shake_air + trigger: + platform: mqtt + topic: 'home/cube/mainroom/status' + payload: 'shake_air' + action: + - event: cube_shake_air + +- alias: cube_move + trigger: + platform: mqtt + topic: 'home/cube/mainroom/status' + payload: 'move' + action: + - event: cube_move + +- alias: cube_tap_twice + trigger: + platform: mqtt + topic: 'home/cube/mainroom/status' + payload: 'tap_twice' + action: + - event: cube_tap_twice diff --git a/hass/settings/configuration/automation/start_event.yaml b/hass/settings/configuration/automation/start_event.yaml new file mode 100644 index 0000000..2b1dc71 --- /dev/null +++ b/hass/settings/configuration/automation/start_event.yaml @@ -0,0 +1,10 @@ +##################################################### +# MAINROOM # +##################################################### +- alias: ha_start + initial_state: 'on' + trigger: + platform: homeassistant + event: start + action: + event: ha_started diff --git a/hass/settings/configuration/automation/work-presense.yaml b/hass/settings/configuration/automation/work-presense.yaml new file mode 100644 index 0000000..8cae230 --- /dev/null +++ b/hass/settings/configuration/automation/work-presense.yaml @@ -0,0 +1,26 @@ +- alias: "Office welcome" + trigger: + platform: mqtt + topic: work/presence/office/enter_time + action: + - event: office_enter + event_data_template: + payload: '{{ trigger.payload }}' + +- alias: "Work leave reminder" + trigger: + platform: mqtt + topic: work/presence/office/leave_reminder_event + action: + - event: office_leave_reminder + event_data_template: + payload: '{{ trigger.payload }}' + +- alias: "Work leave report" + trigger: + platform: mqtt + topic: work/presence/office/leave_event + action: + - event: office_leave + event_data_template: + payload: '{{ trigger.payload }}' \ No newline at end of file diff --git a/hass/settings/configuration/binary_sensor/door/balcony.yaml.bkp b/hass/settings/configuration/binary_sensor/door/balcony.yaml.bkp new file mode 100644 index 0000000..cd08b3d --- /dev/null +++ b/hass/settings/configuration/binary_sensor/door/balcony.yaml.bkp @@ -0,0 +1,6 @@ +- platform: mqtt + state_topic: "home/magnet/balcony/status" + name: "door_window_sensor_158d00010f7e19" + payload_on: "open" + payload_off: "close" + device_class: opening diff --git a/hass/settings/configuration/binary_sensor/door/bath.yaml.bkp b/hass/settings/configuration/binary_sensor/door/bath.yaml.bkp new file mode 100644 index 0000000..a23e4f1 --- /dev/null +++ b/hass/settings/configuration/binary_sensor/door/bath.yaml.bkp @@ -0,0 +1,6 @@ +- platform: mqtt + state_topic: "home/magnet/bath/status" + name: "door_window_sensor_158d000111a469" + payload_on: "open" + payload_off: "close" + device_class: opening \ No newline at end of file diff --git a/hass/settings/configuration/binary_sensor/door/hall.yaml.bkp b/hass/settings/configuration/binary_sensor/door/hall.yaml.bkp new file mode 100644 index 0000000..0a3102f --- /dev/null +++ b/hass/settings/configuration/binary_sensor/door/hall.yaml.bkp @@ -0,0 +1,6 @@ +- platform: mqtt + state_topic: "home/magnet/hall/status" + name: "door_window_sensor_158d0001130141" + payload_on: "open" + payload_off: "close" + device_class: opening diff --git a/hass/settings/configuration/binary_sensor/motion/bath.yaml.bkp b/hass/settings/configuration/binary_sensor/motion/bath.yaml.bkp new file mode 100644 index 0000000..d09711e --- /dev/null +++ b/hass/settings/configuration/binary_sensor/motion/bath.yaml.bkp @@ -0,0 +1,18 @@ +- platform: mqtt + state_topic: "home/motion/bath/status" + name: "motion_sensor_158d0000f9546b" #"bath_motion" + payload_on: "motion" + payload_off: "no_motion" + device_class: motion +- platform: mqtt + state_topic: "home/sensor_motion.aq2/bath2/status" + name: "motion_sensor_158d000165fed5" #"bath_motion2" + payload_on: "motion" + payload_off: "no_motion" + device_class: motion +- platform: mqtt + state_topic: "home/motion/bath3/status" + name: "motion_sensor_158d0001537e21" #"bath_motion3" + payload_on: "motion" + payload_off: "no_motion" + device_class: motion \ No newline at end of file diff --git a/hass/settings/configuration/binary_sensor/motion/hall.yaml.bkp b/hass/settings/configuration/binary_sensor/motion/hall.yaml.bkp new file mode 100644 index 0000000..8d552f9 --- /dev/null +++ b/hass/settings/configuration/binary_sensor/motion/hall.yaml.bkp @@ -0,0 +1,6 @@ +- platform: mqtt + state_topic: "home/motion/hall/status" + name: "motion_sensor_158d0000e7c7ad" + payload_on: "motion" + payload_off: "no_motion" + device_class: motion diff --git a/hass/settings/configuration/binary_sensor/motion/kitchen.yaml.bkp b/hass/settings/configuration/binary_sensor/motion/kitchen.yaml.bkp new file mode 100644 index 0000000..2a4f092 --- /dev/null +++ b/hass/settings/configuration/binary_sensor/motion/kitchen.yaml.bkp @@ -0,0 +1,12 @@ +- platform: mqtt + state_topic: "home/motion/kitchen/status" + name: "motion_sensor_158d0000fbc664" + payload_on: "motion" + payload_off: "no_motion" + device_class: motion +- platform: mqtt + state_topic: "home/motion/kitchen2/status" + name: "motion_sensor_158d0001535c97" + payload_on: "motion" + payload_off: "no_motion" + device_class: motion \ No newline at end of file diff --git a/hass/settings/configuration/binary_sensor/motion/mainroom.yaml.bkp b/hass/settings/configuration/binary_sensor/motion/mainroom.yaml.bkp new file mode 100644 index 0000000..b12ab84 --- /dev/null +++ b/hass/settings/configuration/binary_sensor/motion/mainroom.yaml.bkp @@ -0,0 +1,6 @@ +- platform: mqtt + state_topic: "home/motion/mainroom/status" + name: "motion_sensor_158d0000f6b2fa" + payload_on: "motion" + payload_off: "no_motion" + device_class: motion diff --git a/hass/settings/configuration/binary_sensor/nest.yaml b/hass/settings/configuration/binary_sensor/nest.yaml new file mode 100644 index 0000000..70e8a2c --- /dev/null +++ b/hass/settings/configuration/binary_sensor/nest.yaml @@ -0,0 +1,41 @@ +- platform: mqtt + state_topic: "home/nest/nest/y1" + name: "nest_y1" + payload_on: "1" + payload_off: "0" + device_class: power + +- platform: mqtt + state_topic: "home/nest/nest/y2" + name: "nest_y2" + payload_on: "1" + payload_off: "0" + device_class: power + +- platform: mqtt + state_topic: "home/nest/nest/w1" + name: "nest_w1" + payload_on: "1" + payload_off: "0" + device_class: power + +- platform: mqtt + state_topic: "home/nest/nest/w2" + name: "nest_w2" + payload_on: "1" + payload_off: "0" + device_class: power + +- platform: mqtt + state_topic: "home/nest/nest/s" + name: "nest_s" + payload_on: "1" + payload_off: "0" + device_class: power + +- platform: mqtt + state_topic: "home/nest/nest/g" + name: "nest_g" + payload_on: "1" + payload_off: "0" + device_class: power diff --git a/hass/settings/configuration/binary_sensor/water/kitchen.yaml.bkp b/hass/settings/configuration/binary_sensor/water/kitchen.yaml.bkp new file mode 100644 index 0000000..a22698d --- /dev/null +++ b/hass/settings/configuration/binary_sensor/water/kitchen.yaml.bkp @@ -0,0 +1,6 @@ +- platform: mqtt + state_topic: "home/sensor_wleak.aq1/kitchen/status" + name: "water_leak_sensor_158d0001d594fa" + payload_on: "leak" + payload_off: "no_leak" + device_class: moisture diff --git a/hass/settings/configuration/bot/telegram_bot.yaml b/hass/settings/configuration/bot/telegram_bot.yaml new file mode 100644 index 0000000..b709164 --- /dev/null +++ b/hass/settings/configuration/bot/telegram_bot.yaml @@ -0,0 +1,6 @@ +- platform: polling + api_key: !secret telegram_key + allowed_chat_ids: + - !secret telegram_group_chat + - !secret telegram_monster_chat + proxy_url: !secret proxy_url diff --git a/hass/settings/configuration/camera/synology.yaml b/hass/settings/configuration/camera/synology.yaml new file mode 100644 index 0000000..fe1822e --- /dev/null +++ b/hass/settings/configuration/camera/synology.yaml @@ -0,0 +1,5 @@ +- platform: synology + url: !secret syno_url + username: admin + password: !secret syno_pass + verify_ssl: False \ No newline at end of file diff --git a/hass/settings/configuration/customize/alarm_control_panel.yaml b/hass/settings/configuration/customize/alarm_control_panel.yaml new file mode 100644 index 0000000..d359408 --- /dev/null +++ b/hass/settings/configuration/customize/alarm_control_panel.yaml @@ -0,0 +1,2 @@ +alarm_control_panel.ha_alarm: + friendly_name: Сигнализация diff --git a/hass/settings/configuration/customize/buttons.yaml b/hass/settings/configuration/customize/buttons.yaml new file mode 100644 index 0000000..56463aa --- /dev/null +++ b/hass/settings/configuration/customize/buttons.yaml @@ -0,0 +1,12 @@ +#Кнопки +binary_sensor.switch_158d000102f7e6: + friendly_name: Прихожая +binary_sensor.switch_158d00012d5720: + friendly_name: Кухня +binary_sensor.switch_158d0001256810: + friendly_name: Ванна +binary_sensor.switch_158d00013f81a9: + friendly_name: Гостиная + +binary_sensor.wall_switch_158d000183a5f0: + friendly_name: Выключатель \ No newline at end of file diff --git a/hass/settings/configuration/customize/cube.yaml b/hass/settings/configuration/customize/cube.yaml new file mode 100644 index 0000000..e1ebdb9 --- /dev/null +++ b/hass/settings/configuration/customize/cube.yaml @@ -0,0 +1,3 @@ +#Куб +binary_sensor.cube_158d00011065e3: + friendly_name: Куб \ No newline at end of file diff --git a/hass/settings/configuration/customize/doors.yaml b/hass/settings/configuration/customize/doors.yaml new file mode 100644 index 0000000..d0b054f --- /dev/null +++ b/hass/settings/configuration/customize/doors.yaml @@ -0,0 +1,17 @@ +#Датчики открытия дверей +binary_sensor.door_window_sensor_158d0001130141: + friendly_name: Входная дверь +binary_sensor.door_window_sensor_158d00010f7e19: + friendly_name: Дверь(Балкон) +binary_sensor.door_window_sensor_158d000111a469: + friendly_name: Дверь(Ванна) +binary_sensor.door_window_sensor_158d0002254373: + friendly_name: Дверь(Гостиная) +binary_sensor.door_window_sensor_158d0001b77b1b: + friendly_name: Дверь(Кухня) +binary_sensor.door_window_sensor_158d0001c0e46d: + friendly_name: Окно 1 +binary_sensor.door_window_sensor_158d0001e74660: + friendly_name: Окно(Кухня) +binary_sensor.door_window_sensor_158d0001f34374: + friendly_name: Звонок \ No newline at end of file diff --git a/hass/settings/configuration/customize/groups_order.yaml b/hass/settings/configuration/customize/groups_order.yaml new file mode 100644 index 0000000..fd228bc --- /dev/null +++ b/hass/settings/configuration/customize/groups_order.yaml @@ -0,0 +1,63 @@ + +group.default_view: + order: 1 +group.sensors: + order: 2 +group.alarmclock: + order: 3 +group.settings: + order: 4 +group.system: + order: 5 + +#default_view +group.home: + order: 11 +group.mainroom: + order: 12 +group.bath: + order: 13 +group.hall: + order: 14 +group.balcony: + order: 15 +group.outside: + order: 16 + +#sensors +group.motion_sensors: + order: 21 +group.water_leak: + order: 22 +group.smoke: + order: 23 +group.doors: + order: 24 +group.water_counters: + order: 25 +group.tags: + order: 26 +group.presence: + order: 27 +group.power: + order: 28 + +#alarmclock +group.alarmclock_weekdays: + order: 31 +group.alarmclock_weekend: + order: 32 + +#settings +group.light_control: + order: 41 +group.automation_control: + order: 42 +group.reports_control: + order: 43 +group.tag_control: + order: 44 + +#system +group.os: + order: 51 diff --git a/hass/settings/configuration/customize/humidity.yaml b/hass/settings/configuration/customize/humidity.yaml new file mode 100644 index 0000000..54e70e0 --- /dev/null +++ b/hass/settings/configuration/customize/humidity.yaml @@ -0,0 +1,15 @@ +#Балкон +sensor.humidity_158d000117d37a: + friendly_name: Влажность(Балкон) + +#Гостиная +sensor.humidity_158d0001149b3c: + friendly_name: Влажность(Гостиная) + +#Ванна +sensor.humidity_158d00012d9ad7: + friendly_name: Влажность(Ванна) + +#Улица +sensor.humidity_158d000149c128: + friendly_name: Влажность(Улица) diff --git a/hass/settings/configuration/customize/illumination.yaml b/hass/settings/configuration/customize/illumination.yaml new file mode 100644 index 0000000..2aed5c4 --- /dev/null +++ b/hass/settings/configuration/customize/illumination.yaml @@ -0,0 +1,3 @@ +# освещенность +sensor.lux_outside: + friendly_name: Освещенность(Улица) \ No newline at end of file diff --git a/hass/settings/configuration/customize/light.yaml b/hass/settings/configuration/customize/light.yaml new file mode 100644 index 0000000..6397440 --- /dev/null +++ b/hass/settings/configuration/customize/light.yaml @@ -0,0 +1,26 @@ +light.yeelight_rgb_34ce008fe328: + friendly_name: Цветная1 + +light.yeelight_rgb_286c071065c7: + friendly_name: Цветная2 + +light.yeelight_white_34ce0087ff33: + friendly_name: Лампа3 + +light.yeelight_white_f0b4290e5444: + friendly_name: Лампа4 + +light.yeelight_white_f0b429a8f6df: + friendly_name: Лампа5 + +light.hall: + friendly_name: Люстра + +light.kitchen: + friendly_name: Люстра + +light.bath: + friendly_name: Свет + +light.balcony: + friendly_name: Свет diff --git a/hass/settings/configuration/customize/lock.yaml b/hass/settings/configuration/customize/lock.yaml new file mode 100644 index 0000000..122ee20 --- /dev/null +++ b/hass/settings/configuration/customize/lock.yaml @@ -0,0 +1,2 @@ +lock.lock_158d000222d5c1: + friendly_name: Замок \ No newline at end of file diff --git a/hass/settings/configuration/customize/motion_sensors.yaml b/hass/settings/configuration/customize/motion_sensors.yaml new file mode 100644 index 0000000..5049406 --- /dev/null +++ b/hass/settings/configuration/customize/motion_sensors.yaml @@ -0,0 +1,22 @@ +#Движение +binary_sensor.motion_sensor_158d0000fbc664: + friendly_name: Кухня +binary_sensor.motion_sensor_158d0001535c97: + friendly_name: Кухня2 +binary_sensor.motion_sensor_158d0000e7c7ad: + friendly_name: Прихожая +binary_sensor.motion_sensor_158d0002044548: + friendly_name: Прихожая2 +binary_sensor.motion_sensor_158d0000f9546b: + friendly_name: Ванна +binary_sensor.motion_sensor_158d000165fed5: + friendly_name: Ванна2 +binary_sensor.motion_sensor_158d0001537e21: + friendly_name: Ванна3 + +binary_sensor.motion_sensor_158d0000f6b2fa: + friendly_name: Гостиная(Тумба) +binary_sensor.motion_sensor_158d0001e80f0d: + friendly_name: Гостиниая(Диван) +binary_sensor.motion_sensor_158d0001e50ce4: + friendly_name: Гостиная(Кровать) \ No newline at end of file diff --git a/hass/settings/configuration/customize/plugs.yaml b/hass/settings/configuration/customize/plugs.yaml new file mode 100644 index 0000000..c0747cc --- /dev/null +++ b/hass/settings/configuration/customize/plugs.yaml @@ -0,0 +1,10 @@ +switch.plug_158d0001104a0c: + friendly_name: Телевизор +switch.plug_158d0001a68b18: + friendly_name: Термопот +switch.plug_158d0001a68c64: + friendly_name: Посудомойка +switch.plug_158d0001a4cfb4: + friendly_name: Стиралка +switch.plug_158d00010dd98d: + friendly_name: Кондиционер \ No newline at end of file diff --git a/hass/settings/configuration/customize/power.yaml b/hass/settings/configuration/customize/power.yaml new file mode 100644 index 0000000..270f3fa --- /dev/null +++ b/hass/settings/configuration/customize/power.yaml @@ -0,0 +1,29 @@ +sensor.power_main_total: + friendly_name: Общий счётчик + +sensor.power_total: + friendly_name: Потреблено итого + +sensor.power_voltage: + friendly_name: Напряжение + +sensor.power_current: + friendly_name: Ток + +sensor.power_frequency: + friendly_name: Частота + +sensor.power_active_power: + friendly_name: Активная нагрузка + +sensor.power_reactive_power: + friendly_name: Реактивная нагрузка + +sensor.power_power_factor: + friendly_name: Коэффициент мощности + +sensor.power_export: + friendly_name: Экспортировано + +sensor.power_import: + friendly_name: Импортировано \ No newline at end of file diff --git a/hass/settings/configuration/customize/smoke.yaml b/hass/settings/configuration/customize/smoke.yaml new file mode 100644 index 0000000..e315cd0 --- /dev/null +++ b/hass/settings/configuration/customize/smoke.yaml @@ -0,0 +1,2 @@ +binary_sensor.smoke_sensor_158d00023115ad: + friendly_name: Датчик дыма(Кухня) diff --git a/hass/settings/configuration/customize/switch.yaml b/hass/settings/configuration/customize/switch.yaml new file mode 100644 index 0000000..1f13747 --- /dev/null +++ b/hass/settings/configuration/customize/switch.yaml @@ -0,0 +1,14 @@ +switch.water_valve: + friendly_name: Подача воды +switch.kitchen_mini_light: + friendly_name: Подсветка +switch.domofon_mode: + friendly_name: Открытие домофона +switch.domofon_open: + friendly_name: Открыть домофон +switch.yolka: + friendly_name: Ёлка +switch.xiaomi_miio_switch: + friendly_name: Удлиннитель +switch.wifi_socket: + friendly_name: Гирлянда-окно \ No newline at end of file diff --git a/hass/settings/configuration/customize/temperature.yaml b/hass/settings/configuration/customize/temperature.yaml new file mode 100644 index 0000000..dab2cc7 --- /dev/null +++ b/hass/settings/configuration/customize/temperature.yaml @@ -0,0 +1,15 @@ +#Балкон +sensor.temperature_158d000117d37a: + friendly_name: Температура(Балкон) + +#Гостиная +sensor.temperature_158d0001149b3c: + friendly_name: Температура(Гостиная) + +#Ванна +sensor.temperature_158d00012d9ad7: + friendly_name: Температура(Ванна) + +#Улица +sensor.temperature_158d000149c128: + friendly_name: Температура(Улица) diff --git a/hass/settings/configuration/customize/vacuum.yaml b/hass/settings/configuration/customize/vacuum.yaml new file mode 100644 index 0000000..4bd0db6 --- /dev/null +++ b/hass/settings/configuration/customize/vacuum.yaml @@ -0,0 +1,3 @@ +vacuum.xiaomi_vacuum_cleaner: + friendly_name: Пылесос + diff --git a/hass/settings/configuration/customize/water_counters.yaml b/hass/settings/configuration/customize/water_counters.yaml new file mode 100644 index 0000000..d3bf596 --- /dev/null +++ b/hass/settings/configuration/customize/water_counters.yaml @@ -0,0 +1,5 @@ +#счетчики +sensor.counter_cold: + friendly_name: Холодная(счетчик) +sensor.counter_hot: + friendly_name: Горячая(счетчик) diff --git a/hass/settings/configuration/customize/water_leak.yaml b/hass/settings/configuration/customize/water_leak.yaml new file mode 100644 index 0000000..a3b2006 --- /dev/null +++ b/hass/settings/configuration/customize/water_leak.yaml @@ -0,0 +1,13 @@ +#Протечка +binary_sensor.water_leak_sensor_158d0001d594fa: + friendly_name: Раковина(Кухня) +binary_sensor.water_leak_sensor_158d0001c34960: + friendly_name: Стояк +binary_sensor.water_leak_sensor_158d0002150c07: + friendly_name: Ванна +binary_sensor.water_leak_sensor_158d0002112b98: + friendly_name: Посудомойка +binary_sensor.water_leak_sensor_158d0002113158: + friendly_name: Стиралка +binary_sensor.water_leak_sensor_158d00023a48f0: + friendly_name: Туалет \ No newline at end of file diff --git a/hass/settings/configuration/group/_views.yaml b/hass/settings/configuration/group/_views.yaml new file mode 100644 index 0000000..6075575 --- /dev/null +++ b/hass/settings/configuration/group/_views.yaml @@ -0,0 +1,60 @@ +default_view: + view: yes + name: Статус + icon: mdi:home + entities: + - group.home + - group.mainroom + - group.kitchen + - group.bath + - group.hall + - group.balcony + - group.outside + +sensors: + view: yes + name: Датчики + icon: mdi:nfc + entities: + - group.motion_sensors + - group.water_leak + - group.smoke + - group.doors + - group.water_counters + - group.tags + - group.presence + - group.power + +cameras: + view: yes + name: Камеры + icon: mdi:cctv + entities: + - camera.entrance + +alarmclock: + view: yes + name: Будильники + icon: mdi:alarm + entities: + - group.alarmclock_weekdays + - group.alarmclock_weekend + +settings: + view: yes + name: Настройки + icon: mdi:settings + entities: + - group.light_control + - group.automation_control + - group.reports_control + - group.tag_control + - group.persons + +system: + view: yes + name: Информация + icon: mdi:monitor-multiple + entities: + - group.os + - group.network diff --git a/hass/settings/configuration/group/alarmclock/alarmclock_weekdays.yaml b/hass/settings/configuration/group/alarmclock/alarmclock_weekdays.yaml new file mode 100644 index 0000000..dfc8ff6 --- /dev/null +++ b/hass/settings/configuration/group/alarmclock/alarmclock_weekdays.yaml @@ -0,0 +1,6 @@ +alarmclock_weekdays: + name: Будильник (будни) + control: hidden + entities: + - input_boolean.alarmclock_weekdays + - input_datetime.alarmclock_weekdays \ No newline at end of file diff --git a/hass/settings/configuration/group/alarmclock/alarmclock_weekend.yaml b/hass/settings/configuration/group/alarmclock/alarmclock_weekend.yaml new file mode 100644 index 0000000..ffa8067 --- /dev/null +++ b/hass/settings/configuration/group/alarmclock/alarmclock_weekend.yaml @@ -0,0 +1,6 @@ +alarmclock_weekend: + name: Будильник (выходные) + control: hidden + entities: + - input_boolean.alarmclock_weekend + - input_datetime.alarmclock_weekend \ No newline at end of file diff --git a/hass/settings/configuration/group/hidden/door-window/balcony.yaml b/hass/settings/configuration/group/hidden/door-window/balcony.yaml new file mode 100644 index 0000000..f932f56 --- /dev/null +++ b/hass/settings/configuration/group/hidden/door-window/balcony.yaml @@ -0,0 +1,5 @@ +balcony_door_window: + name: Двери-Окна + control: hidden + entities: + - binary_sensor.door_window_sensor_158d00010f7e19 diff --git a/hass/settings/configuration/group/hidden/door-window/mainroom.yaml b/hass/settings/configuration/group/hidden/door-window/mainroom.yaml new file mode 100644 index 0000000..c1c0131 --- /dev/null +++ b/hass/settings/configuration/group/hidden/door-window/mainroom.yaml @@ -0,0 +1,5 @@ +mainroom_door_window: + name: Двери-Окна + control: hidden + entities: + - binary_sensor.door_window_sensor_158d00010f7e19 diff --git a/hass/settings/configuration/group/hidden/light/balcony_light.yaml b/hass/settings/configuration/group/hidden/light/balcony_light.yaml new file mode 100644 index 0000000..b06efc0 --- /dev/null +++ b/hass/settings/configuration/group/hidden/light/balcony_light.yaml @@ -0,0 +1,5 @@ +balcony_light: + name: Свет (балкон) + control: hidden + entities: + - light.balcony \ No newline at end of file diff --git a/hass/settings/configuration/group/hidden/light/bath_light.yaml b/hass/settings/configuration/group/hidden/light/bath_light.yaml new file mode 100644 index 0000000..8f33358 --- /dev/null +++ b/hass/settings/configuration/group/hidden/light/bath_light.yaml @@ -0,0 +1,5 @@ +bath_light: + name: Свет (ванна) + control: hidden + entities: + - light.bath \ No newline at end of file diff --git a/hass/settings/configuration/group/hidden/light/hall_light.yaml b/hass/settings/configuration/group/hidden/light/hall_light.yaml new file mode 100644 index 0000000..4cf8176 --- /dev/null +++ b/hass/settings/configuration/group/hidden/light/hall_light.yaml @@ -0,0 +1,5 @@ +hall_light: + name: Свет (прихожая) + control: hidden + entities: + - light.hall \ No newline at end of file diff --git a/hass/settings/configuration/group/hidden/light/kitchen_light.yaml b/hass/settings/configuration/group/hidden/light/kitchen_light.yaml new file mode 100644 index 0000000..412c9f7 --- /dev/null +++ b/hass/settings/configuration/group/hidden/light/kitchen_light.yaml @@ -0,0 +1,6 @@ +kitchen_light: + name: Свет (кухня) + control: hidden + entities: + - light.kitchen + - switch.kitchen_mini_light \ No newline at end of file diff --git a/hass/settings/configuration/group/hidden/light/mainroom_color.yaml b/hass/settings/configuration/group/hidden/light/mainroom_color.yaml new file mode 100644 index 0000000..1504a21 --- /dev/null +++ b/hass/settings/configuration/group/hidden/light/mainroom_color.yaml @@ -0,0 +1,6 @@ +mainroom_color: + name: Цветные лампы(Гостиная) + control: hidden + entities: + - light.yeelight_rgb_34ce008fe328 + - light.yeelight_rgb_286c071065c7 diff --git a/hass/settings/configuration/group/hidden/light/mainroom_light.yaml b/hass/settings/configuration/group/hidden/light/mainroom_light.yaml new file mode 100644 index 0000000..dbd850e --- /dev/null +++ b/hass/settings/configuration/group/hidden/light/mainroom_light.yaml @@ -0,0 +1,9 @@ +mainroom_light: + name: Свет (гостиная) + control: hidden + entities: + - light.yeelight_rgb_34ce008fe328 + - light.yeelight_rgb_286c071065c7 + - light.yeelight_white_34ce0087ff33 + - light.yeelight_white_f0b4290e5444 + - light.yeelight_white_f0b429a8f6df diff --git a/hass/settings/configuration/group/hidden/motion/bath_motion.yaml b/hass/settings/configuration/group/hidden/motion/bath_motion.yaml new file mode 100644 index 0000000..d3cefb9 --- /dev/null +++ b/hass/settings/configuration/group/hidden/motion/bath_motion.yaml @@ -0,0 +1,7 @@ +bath_motion: + name: Движение (ванна) + control: hidden + entities: + - binary_sensor.motion_sensor_158d0000f9546b + - binary_sensor.motion_sensor_158d000165fed5 + - binary_sensor.motion_sensor_158d0001537e21 \ No newline at end of file diff --git a/hass/settings/configuration/group/hidden/motion/hall_motion.yaml b/hass/settings/configuration/group/hidden/motion/hall_motion.yaml new file mode 100644 index 0000000..d75b81a --- /dev/null +++ b/hass/settings/configuration/group/hidden/motion/hall_motion.yaml @@ -0,0 +1,6 @@ +hall_motion: + name: Движение (прихожая) + control: hidden + entities: + - binary_sensor.motion_sensor_158d0000e7c7ad + - binary_sensor.motion_sensor_158d0002044548 \ No newline at end of file diff --git a/hass/settings/configuration/group/hidden/motion/kitchen_motion.yaml b/hass/settings/configuration/group/hidden/motion/kitchen_motion.yaml new file mode 100644 index 0000000..b26a2ab --- /dev/null +++ b/hass/settings/configuration/group/hidden/motion/kitchen_motion.yaml @@ -0,0 +1,6 @@ +kitchen_motion: + name: Движение (кухня) + control: hidden + entities: + - binary_sensor.motion_sensor_158d0000fbc664 + - binary_sensor.motion_sensor_158d0001535c97 \ No newline at end of file diff --git a/hass/settings/configuration/group/hidden/motion/mainroom_motion.yaml b/hass/settings/configuration/group/hidden/motion/mainroom_motion.yaml new file mode 100644 index 0000000..2257453 --- /dev/null +++ b/hass/settings/configuration/group/hidden/motion/mainroom_motion.yaml @@ -0,0 +1,7 @@ +mainroom_motion: + name: Движение (гостиная) + control: hidden + entities: + - binary_sensor.motion_sensor_158d0000f6b2fa + - binary_sensor.motion_sensor_158d0001e50ce4 + - binary_sensor.motion_sensor_158d0001e80f0d \ No newline at end of file diff --git a/hass/settings/configuration/group/hidden/report/report_entities.yaml b/hass/settings/configuration/group/hidden/report/report_entities.yaml new file mode 100644 index 0000000..a5bb16a --- /dev/null +++ b/hass/settings/configuration/group/hidden/report/report_entities.yaml @@ -0,0 +1,16 @@ +report_entities: + name: Датчики для отправки отчета + control: hidden + entities: + - light.yeelight_rgb_34ce008fe328 #mainbulb1 + - light.yeelight_rgb_286c071065c7 + - light.yeelight_white_34ce0087ff33 + - light.yeelight_white_f0b4290e5444 + - light.yeelight_white_f0b429a8f6df + - light.kitchen + - switch.kitchen_mini_light + - light.hall + - light.bath + - switch.plug_158d0001104a0c # телевизор + - switch.plug_158d0001a68b18 # термопот + diff --git a/hass/settings/configuration/group/hidden/security/security_sensors.yaml b/hass/settings/configuration/group/hidden/security/security_sensors.yaml new file mode 100644 index 0000000..2377372 --- /dev/null +++ b/hass/settings/configuration/group/hidden/security/security_sensors.yaml @@ -0,0 +1,21 @@ +security_sensors: + name: Безопасность + entities: + - binary_sensor.motion_sensor_158d0000e7c7ad + - binary_sensor.motion_sensor_158d0002044548 + - binary_sensor.motion_sensor_158d0000fbc664 + - binary_sensor.motion_sensor_158d0001535c97 + - binary_sensor.motion_sensor_158d0000f9546b + - binary_sensor.motion_sensor_158d000165fed5 + - binary_sensor.motion_sensor_158d0001537e21 + - binary_sensor.motion_sensor_158d0000f6b2fa + - binary_sensor.motion_sensor_158d0001e50ce4 + - binary_sensor.motion_sensor_158d0001e80f0d + - binary_sensor.door_window_sensor_158d0001130141 + - binary_sensor.door_window_sensor_158d000111a469 + - binary_sensor.door_window_sensor_158d00010f7e19 + - binary_sensor.door_window_sensor_158d0001e74660 + - binary_sensor.door_window_sensor_158d0001b77b1b + - binary_sensor.door_window_sensor_158d0001c0e46d + - binary_sensor.door_window_sensor_158d0002254373 + - binary_sensor.smoke_sensor_158d00023115ad \ No newline at end of file diff --git a/hass/settings/configuration/group/home/balcony.yaml b/hass/settings/configuration/group/home/balcony.yaml new file mode 100644 index 0000000..ca397e5 --- /dev/null +++ b/hass/settings/configuration/group/home/balcony.yaml @@ -0,0 +1,7 @@ +balcony: + name: Балкон + control: hidden + entities: + - light.balcony + - sensor.temperature_158d000117d37a + - sensor.humidity_158d000117d37a diff --git a/hass/settings/configuration/group/home/bath.yaml b/hass/settings/configuration/group/home/bath.yaml new file mode 100644 index 0000000..2d86b08 --- /dev/null +++ b/hass/settings/configuration/group/home/bath.yaml @@ -0,0 +1,7 @@ +bath: + name: Ванная + control: hidden + entities: + - sensor.temperature_158d00012d9ad7 + - sensor.humidity_158d00012d9ad7 + - light.bath \ No newline at end of file diff --git a/hass/settings/configuration/group/home/hall.yaml b/hass/settings/configuration/group/home/hall.yaml new file mode 100644 index 0000000..ea66480 --- /dev/null +++ b/hass/settings/configuration/group/home/hall.yaml @@ -0,0 +1,5 @@ +hall: + name: Прихожая + control: hidden + entities: + - light.hall \ No newline at end of file diff --git a/hass/settings/configuration/group/home/home.yaml b/hass/settings/configuration/group/home/home.yaml new file mode 100644 index 0000000..07a5d6f --- /dev/null +++ b/hass/settings/configuration/group/home/home.yaml @@ -0,0 +1,12 @@ +home: + name: Дом + control: hidden + entities: + - alarm_control_panel.ha_alarm + - sensor.lux_outside + - switch.water_valve + - vacuum.xiaomi_vacuum_cleaner + - sensor.xiaomi_scale + - switch.domofon_open + - switch.wifi_socket + - switch.yolka \ No newline at end of file diff --git a/hass/settings/configuration/group/home/kitchen.yaml b/hass/settings/configuration/group/home/kitchen.yaml new file mode 100644 index 0000000..e0136c2 --- /dev/null +++ b/hass/settings/configuration/group/home/kitchen.yaml @@ -0,0 +1,10 @@ +kitchen: + name: Кухня + control: hidden + entities: + - light.kitchen + - switch.kitchen_mini_light + - switch.plug_158d0001a68b18 + - switch.plug_158d0001a68c64 + - switch.plug_158d0001a4cfb4 + - switch.xiaomi_miio_switch \ No newline at end of file diff --git a/hass/settings/configuration/group/home/mainroom.yaml b/hass/settings/configuration/group/home/mainroom.yaml new file mode 100644 index 0000000..2ba56ad --- /dev/null +++ b/hass/settings/configuration/group/home/mainroom.yaml @@ -0,0 +1,14 @@ +mainroom: + name: Гостиная + control: hidden + entities: + - sensor.temperature_158d0001149b3c + - sensor.humidity_158d0001149b3c + - light.yeelight_rgb_34ce008fe328 + - light.yeelight_rgb_286c071065c7 + - light.yeelight_white_34ce0087ff33 + - light.yeelight_white_f0b4290e5444 + - light.yeelight_white_f0b429a8f6df + - switch.plug_158d0001104a0c + - switch.plug_158d00010dd98d + - climate.living_room diff --git a/hass/settings/configuration/group/home/outside.yaml b/hass/settings/configuration/group/home/outside.yaml new file mode 100644 index 0000000..4793cd5 --- /dev/null +++ b/hass/settings/configuration/group/home/outside.yaml @@ -0,0 +1,6 @@ +outside: + name: Улица + control: hidden + entities: + - sensor.temperature_158d000149c128 + - sensor.humidity_158d000149c128 \ No newline at end of file diff --git a/hass/settings/configuration/group/sensors/buttons.yaml b/hass/settings/configuration/group/sensors/buttons.yaml new file mode 100644 index 0000000..76f86d9 --- /dev/null +++ b/hass/settings/configuration/group/sensors/buttons.yaml @@ -0,0 +1,7 @@ +buttons: + name: Кнопки + entities: + - binary_sensor.switch_158d000102f7e6 + - binary_sensor.switch_158d00012d5720 + - binary_sensor.switch_158d0001256810 + - binary_sensor.switch_158d00013f81a9 \ No newline at end of file diff --git a/hass/settings/configuration/group/sensors/doors.yaml b/hass/settings/configuration/group/sensors/doors.yaml new file mode 100644 index 0000000..d757a74 --- /dev/null +++ b/hass/settings/configuration/group/sensors/doors.yaml @@ -0,0 +1,17 @@ +doors: + name: Двери + entities: + #входная дверь + - binary_sensor.door_window_sensor_158d0001130141 + #дверь ванная + - binary_sensor.door_window_sensor_158d000111a469 + #дверь балкон + - binary_sensor.door_window_sensor_158d00010f7e19 + #дверь гостиная + - binary_sensor.door_window_sensor_158d0002254373 + #дверь кухня + - binary_sensor.door_window_sensor_158d0001b77b1b + #окно 1 + - binary_sensor.door_window_sensor_158d0001c0e46d + #окно кухня + - binary_sensor.door_window_sensor_158d0001e74660 \ No newline at end of file diff --git a/hass/settings/configuration/group/sensors/motion_sensors.yaml b/hass/settings/configuration/group/sensors/motion_sensors.yaml new file mode 100644 index 0000000..bba392e --- /dev/null +++ b/hass/settings/configuration/group/sensors/motion_sensors.yaml @@ -0,0 +1,17 @@ +motion_sensors: + name: Движение + entities: + #прихожая + - binary_sensor.motion_sensor_158d0000e7c7ad + - binary_sensor.motion_sensor_158d0002044548 + #Кухня + - binary_sensor.motion_sensor_158d0000fbc664 + - binary_sensor.motion_sensor_158d0001535c97 + #Ванна + - binary_sensor.motion_sensor_158d0000f9546b + - binary_sensor.motion_sensor_158d000165fed5 + - binary_sensor.motion_sensor_158d0001537e21 + #Гостиная + - binary_sensor.motion_sensor_158d0000f6b2fa + - binary_sensor.motion_sensor_158d0001e50ce4 + - binary_sensor.motion_sensor_158d0001e80f0d diff --git a/hass/settings/configuration/group/sensors/power.yaml b/hass/settings/configuration/group/sensors/power.yaml new file mode 100644 index 0000000..2e6b2d5 --- /dev/null +++ b/hass/settings/configuration/group/sensors/power.yaml @@ -0,0 +1,15 @@ +power: + name: Электропитание + entities: + - sensor.power_mosenergosbyt_balance + - sensor.power_main_total + - sensor.power_mosenergosbyt_total + - sensor.power_total + - sensor.power_voltage + - sensor.power_current + - sensor.power_frequency + - sensor.power_active_power + - sensor.power_reactive_power + - sensor.power_power_factor + - sensor.power_import + - sensor.power_export diff --git a/hass/settings/configuration/group/sensors/presence.yaml b/hass/settings/configuration/group/sensors/presence.yaml new file mode 100644 index 0000000..d292473 --- /dev/null +++ b/hass/settings/configuration/group/sensors/presence.yaml @@ -0,0 +1,6 @@ +presence: + name: Пристутствие + entities: + - sensor.presence_hall + - sensor.presence_kitchen + - sensor.presence_balcony diff --git a/hass/settings/configuration/group/sensors/smoke.yaml b/hass/settings/configuration/group/sensors/smoke.yaml new file mode 100644 index 0000000..628910d --- /dev/null +++ b/hass/settings/configuration/group/sensors/smoke.yaml @@ -0,0 +1,5 @@ +smoke: + name: Датчики дыма + entities: + #датчик дыма-кухня + - binary_sensor.smoke_sensor_158d00023115ad diff --git a/hass/settings/configuration/group/sensors/tags.yaml b/hass/settings/configuration/group/sensors/tags.yaml new file mode 100644 index 0000000..447cab4 --- /dev/null +++ b/hass/settings/configuration/group/sensors/tags.yaml @@ -0,0 +1,8 @@ +tags: + name: Метки + entities: + - sensor.tag_mamik_band + - sensor.tag_monster_nut + - sensor.tag_nut_blue + - sensor.tag_nut_white + - sensor.tag_nut_guest diff --git a/hass/settings/configuration/group/sensors/water_counters.yaml b/hass/settings/configuration/group/sensors/water_counters.yaml new file mode 100644 index 0000000..fa704f8 --- /dev/null +++ b/hass/settings/configuration/group/sensors/water_counters.yaml @@ -0,0 +1,8 @@ +water_counters: + name: Счетчики воды + entities: + - sensor.epd_balance + - sensor.counter_cold + - sensor.counter_hot + - sensor.counter_mosru_cold + - sensor.counter_mosru_hot diff --git a/hass/settings/configuration/group/sensors/water_leak.yaml b/hass/settings/configuration/group/sensors/water_leak.yaml new file mode 100644 index 0000000..6ff9467 --- /dev/null +++ b/hass/settings/configuration/group/sensors/water_leak.yaml @@ -0,0 +1,9 @@ +water_leak: + name: Протечка + entities: + - binary_sensor.water_leak_sensor_158d0001d594fa + - binary_sensor.water_leak_sensor_158d0001c34960 + - binary_sensor.water_leak_sensor_158d0002150c07 + - binary_sensor.water_leak_sensor_158d0002112b98 + - binary_sensor.water_leak_sensor_158d0002113158 + - binary_sensor.water_leak_sensor_158d00023a48f0 \ No newline at end of file diff --git a/hass/settings/configuration/group/settings/automation_control.yaml b/hass/settings/configuration/group/settings/automation_control.yaml new file mode 100644 index 0000000..a31750c --- /dev/null +++ b/hass/settings/configuration/group/settings/automation_control.yaml @@ -0,0 +1,9 @@ +automation_control: + name: Настройки автоматики + entities: + - input_boolean.presence_control + - input_boolean.water_control + - input_boolean.water_leak_control + - input_boolean.mainroom_climate_control + - switch.domofon_mode + \ No newline at end of file diff --git a/hass/settings/configuration/group/settings/light_control.yaml b/hass/settings/configuration/group/settings/light_control.yaml new file mode 100644 index 0000000..5dd026e --- /dev/null +++ b/hass/settings/configuration/group/settings/light_control.yaml @@ -0,0 +1,8 @@ +light_control: + name: Контроль света + entities: + - input_boolean.mainroom_light_control + - input_boolean.hall_light_control + - input_boolean.bath_light_control + - input_boolean.kitchen_light_control + - input_boolean.balcony_light_control \ No newline at end of file diff --git a/hass/settings/configuration/group/settings/reports_control.yaml b/hass/settings/configuration/group/settings/reports_control.yaml new file mode 100644 index 0000000..ccebbca --- /dev/null +++ b/hass/settings/configuration/group/settings/reports_control.yaml @@ -0,0 +1,8 @@ +reports_control: + name: Настройки отчетов + entities: + - input_boolean.security_sensors_report + - input_boolean.battery_report + - input_boolean.water_counters_report + - input_boolean.work_presence_report + - input_boolean.lock_report diff --git a/hass/settings/configuration/group/settings/tag_control.yaml b/hass/settings/configuration/group/settings/tag_control.yaml new file mode 100644 index 0000000..e59592f --- /dev/null +++ b/hass/settings/configuration/group/settings/tag_control.yaml @@ -0,0 +1,8 @@ +tag_control: + name: Метки + entities: + - input_boolean.tag_mamik_band + - input_boolean.tag_monster_nut + - input_boolean.tag_nut_blue + - input_boolean.tag_nut_white + - input_boolean.tag_nut_guest diff --git a/hass/settings/configuration/group/systeminfo/network.yaml b/hass/settings/configuration/group/systeminfo/network.yaml new file mode 100644 index 0000000..8c1b039 --- /dev/null +++ b/hass/settings/configuration/group/systeminfo/network.yaml @@ -0,0 +1,6 @@ +network: + name: Сеть + entities: + - sensor.speedtest_ping + - sensor.speedtest_download + - sensor.speedtest_upload diff --git a/hass/settings/configuration/group/systeminfo/os.yaml b/hass/settings/configuration/group/systeminfo/os.yaml new file mode 100644 index 0000000..5659928 --- /dev/null +++ b/hass/settings/configuration/group/systeminfo/os.yaml @@ -0,0 +1,8 @@ +os: + name: ОС + entities: + - sensor.processor_use + - sensor.memory_free + - sensor.swap_use_percent + - sensor.disk_use_percent_ + - sensor.ssl_certificate_expiry \ No newline at end of file diff --git a/hass/settings/configuration/homekit/homekit.yaml b/hass/settings/configuration/homekit/homekit.yaml new file mode 100644 index 0000000..ca3aaa5 --- /dev/null +++ b/hass/settings/configuration/homekit/homekit.yaml @@ -0,0 +1,18 @@ +auto_start: False +filter: + include_domains: + - alarm_control_panel + - switch + - light + - vacuum + include_entities: + - sensor.humidity_158d0001149b3c + - sensor.humidity_158d000117d37a + - sensor.humidity_158d00012d9ad7 + - sensor.humidity_158d000149c128 + - sensor.temperature_158d0001149b3c + - sensor.temperature_158d000117d37a + - sensor.temperature_158d00012d9ad7 + - sensor.temperature_158d000149c128 + exclude_entities: + - switch.plug_158d00010dd98d diff --git a/hass/settings/configuration/influxdb.yaml b/hass/settings/configuration/influxdb.yaml new file mode 100644 index 0000000..5582d56 --- /dev/null +++ b/hass/settings/configuration/influxdb.yaml @@ -0,0 +1,12 @@ +host: !secret base_localname +port: 8086 +database: hass +max_retries: 3 +default_measurement: state + +include: + domains: + - sensor + - binary_sensor + - switch + diff --git a/hass/settings/configuration/input/input_boolean/alarmclock.yaml b/hass/settings/configuration/input/input_boolean/alarmclock.yaml new file mode 100644 index 0000000..1af2a02 --- /dev/null +++ b/hass/settings/configuration/input/input_boolean/alarmclock.yaml @@ -0,0 +1,5 @@ +alarmclock_weekdays: + name: Статус + +alarmclock_weekend: + name: Статус \ No newline at end of file diff --git a/hass/settings/configuration/input/input_boolean/automation_control.yaml b/hass/settings/configuration/input/input_boolean/automation_control.yaml new file mode 100644 index 0000000..aba54d0 --- /dev/null +++ b/hass/settings/configuration/input/input_boolean/automation_control.yaml @@ -0,0 +1,49 @@ +presence_control: + name: Контроль присутствия (действия по уходу и приходу) + icon: mdi:account-off + initial: on + +water_leak_control: + name: Контроль протечек + icon: mdi:water-off + initial: on + +water_control: + name: Контроль подачи воды + icon: mdi:water-pump + initial: on + +security_sensors_report: + name: Охрана периметра при отсутствии + icon: mdi:access-point + initial: on + +battery_report: + name: Отчет о состоянии батарей датчиков + icon: mdi:battery-charging-wireless-10 + initial: on + +water_counters_report: + name: Отчет о поверке счетчиков + icon: mdi:alert-octagon + initial: on + +work_presence_report: + name: Отчет 'пора домой' + icon: mdi:account-alert + initial: on + +guest_mode: + name: Гостевой режим + icon: mdi:account-switch + initial: off + +mainroom_climate_control: + name: Контроль климата + icon: mdi:account-switch + initial: on + +lock_report: + name: Отчет замка + icon: mdi:lock-alert + initial: on diff --git a/hass/settings/configuration/input/input_boolean/light_control.yaml b/hass/settings/configuration/input/input_boolean/light_control.yaml new file mode 100644 index 0000000..b2faca7 --- /dev/null +++ b/hass/settings/configuration/input/input_boolean/light_control.yaml @@ -0,0 +1,15 @@ +mainroom_light_control: + name: Контроль света в гостиной + initial: off +hall_light_control: + name: Контроль света в прихожей + initial: on +bath_light_control: + name: Контроль света в ванной + initial: on +kitchen_light_control: + name: Контроль света на кухне + initial: on +balcony_light_control: + name: Контроль света на балконе + initial: on diff --git a/hass/settings/configuration/input/input_boolean/tag_control.yaml b/hass/settings/configuration/input/input_boolean/tag_control.yaml new file mode 100644 index 0000000..8dfd186 --- /dev/null +++ b/hass/settings/configuration/input/input_boolean/tag_control.yaml @@ -0,0 +1,15 @@ +tag_mamik_band: + name: tag_mamik_band + initial: on +tag_monster_nut: + name: tag_monster_nut + initial: on +tag_nut_blue: + name: tag_nut_blue + initial: on +tag_nut_white: + name: tag_nut_white + initial: on +tag_nut_guest: + name: tag_nut_guest + initial: on \ No newline at end of file diff --git a/hass/settings/configuration/input/input_datetime/alarmclock.yaml b/hass/settings/configuration/input/input_datetime/alarmclock.yaml new file mode 100644 index 0000000..59c50e4 --- /dev/null +++ b/hass/settings/configuration/input/input_datetime/alarmclock.yaml @@ -0,0 +1,9 @@ +alarmclock_weekdays: + name: Время + has_date: false + has_time: true + +alarmclock_weekend: + name: Время + has_date: false + has_time: true diff --git a/hass/settings/configuration/input/input_number/mainroom.yaml b/hass/settings/configuration/input/input_number/mainroom.yaml new file mode 100644 index 0000000..a4cb922 --- /dev/null +++ b/hass/settings/configuration/input/input_number/mainroom.yaml @@ -0,0 +1,7 @@ +# mainroom_temperature: +# name: Температура +# initial: 24 +# min: 18 +# max: 28 +# step: 1 +# mode: box \ No newline at end of file diff --git a/hass/settings/configuration/light/balcony.yaml b/hass/settings/configuration/light/balcony.yaml new file mode 100644 index 0000000..56db0f2 --- /dev/null +++ b/hass/settings/configuration/light/balcony.yaml @@ -0,0 +1,7 @@ +- platform: mqtt + name: "balcony" + state_topic: "stat/balcony_light/POWER" + command_topic: "cmnd/balcony_light/POWER" + payload_on: "ON" + payload_off: "OFF" + optimistic: false diff --git a/hass/settings/configuration/light/bath.yaml b/hass/settings/configuration/light/bath.yaml new file mode 100644 index 0000000..af0702b --- /dev/null +++ b/hass/settings/configuration/light/bath.yaml @@ -0,0 +1,7 @@ +- platform: mqtt + name: bath + state_topic: "stat/bathlight/POWER" + command_topic: "cmnd/bathlight/POWER" + payload_on: "ON" + payload_off: "OFF" + optimistic: false diff --git a/hass/settings/configuration/light/hall.yaml b/hass/settings/configuration/light/hall.yaml new file mode 100644 index 0000000..7522d07 --- /dev/null +++ b/hass/settings/configuration/light/hall.yaml @@ -0,0 +1,7 @@ +- platform: mqtt + name: hall + state_topic: stat/balcony/POWER + command_topic: cmnd/balcony/POWER + payload_on: 'ON' + payload_off: 'OFF' + optimistic: false diff --git a/hass/settings/configuration/light/kitchen.yaml b/hass/settings/configuration/light/kitchen.yaml new file mode 100644 index 0000000..3b6a20d --- /dev/null +++ b/hass/settings/configuration/light/kitchen.yaml @@ -0,0 +1,7 @@ +- platform: mqtt + name: "kitchen" + state_topic: "stat/kitchen/POWER" + command_topic: "cmnd/kitchen/POWER" + payload_on: "ON" + payload_off: "OFF" + optimistic: false diff --git a/hass/settings/configuration/modbus.yaml b/hass/settings/configuration/modbus.yaml new file mode 100644 index 0000000..b7d75eb --- /dev/null +++ b/hass/settings/configuration/modbus.yaml @@ -0,0 +1,3 @@ +type: tcp +host: !secret modbus_host +port: !secret modbus_port diff --git a/hass/settings/configuration/mqtt/mqtt.yaml b/hass/settings/configuration/mqtt/mqtt.yaml new file mode 100644 index 0000000..57cb3c3 --- /dev/null +++ b/hass/settings/configuration/mqtt/mqtt.yaml @@ -0,0 +1,12 @@ +discovery: true +broker: !secret mqtt_ip +port: 1883 +client_id: !secret mqtt_clientid +username: !secret mqtt_user +password: !secret mqtt_password +birth_message: + topic: 'hass/status' + payload: 'online' +will_message: + topic: 'hass/status' + payload: 'offline' diff --git a/hass/settings/configuration/nest/nest.yaml b/hass/settings/configuration/nest/nest.yaml new file mode 100644 index 0000000..8b08e80 --- /dev/null +++ b/hass/settings/configuration/nest/nest.yaml @@ -0,0 +1,2 @@ +client_id: !secret nest_client_id +client_secret: !secret nest_client_secret \ No newline at end of file diff --git a/hass/settings/configuration/notify/telegram_group.yaml b/hass/settings/configuration/notify/telegram_group.yaml new file mode 100644 index 0000000..1bac04a --- /dev/null +++ b/hass/settings/configuration/notify/telegram_group.yaml @@ -0,0 +1,4 @@ +- platform: telegram + name: telegram + chat_id: !secret telegram_group_chat + parse_mode: html diff --git a/hass/settings/configuration/notify/telegram_monster.yaml b/hass/settings/configuration/notify/telegram_monster.yaml new file mode 100644 index 0000000..abe5d51 --- /dev/null +++ b/hass/settings/configuration/notify/telegram_monster.yaml @@ -0,0 +1,4 @@ +- platform: telegram + name: telegram_monster + chat_id: !secret telegram_monster_chat + parse_mode: html diff --git a/hass/settings/configuration/recorder.yaml b/hass/settings/configuration/recorder.yaml new file mode 100644 index 0000000..de698a5 --- /dev/null +++ b/hass/settings/configuration/recorder.yaml @@ -0,0 +1,36 @@ +--- +purge_keep_days: 365 +db_url: !secret mysql_url +include: + domains: + - alarm_control_panel + - binary_sensor + - climate + - light + - lock + - sensor + - switch + - vacuum +exclude: + domains: + - automation + - media_player + entities: + - sun.sun + - binary_sensor.belorechenskaya_away + - binary_sensor.cube_158d00011065e3_2 + - binary_sensor.living_room_thermostat_fan + - binary_sensor.living_room_thermostat_has_leaf + - binary_sensor.living_room_thermostat_is_locked + - binary_sensor.living_room_thermostat_is_using_emergency_heat + - binary_sensor.living_room_thermostat_online + - sensor.living_room_thermostat_hvac_state + - sensor.living_room_thermostat_operation_mode + - sensor.living_room_thermostat_target + - sensor.living_room_thermostat_temperature + - sensor.memory_free + - sensor.processor_use + - sensor.ssl_certificate_expiry + - sensor.swap_use_percent + - sensor.belorechenskaya_eta + - sensor.disk_use_percent_ \ No newline at end of file diff --git a/hass/settings/configuration/script/audio_bass_minus.yaml b/hass/settings/configuration/script/audio_bass_minus.yaml new file mode 100644 index 0000000..381f28c --- /dev/null +++ b/hass/settings/configuration/script/audio_bass_minus.yaml @@ -0,0 +1,6 @@ +audio_bass_minus: + sequence: + - service: mqtt.publish + data: + topic: "home/remote/rm2/code/set" + payload: "260050000001339514371412131313131312131313131313131313381337133814371337143714371412133814361437141213131213131313381412131313131238143714371436140005060001334a14000d050000000000000000" diff --git a/hass/settings/configuration/script/audio_bass_plus.yaml b/hass/settings/configuration/script/audio_bass_plus.yaml new file mode 100644 index 0000000..70fc3c1 --- /dev/null +++ b/hass/settings/configuration/script/audio_bass_plus.yaml @@ -0,0 +1,6 @@ +audio_bass_plus: + sequence: + - service: mqtt.publish + data: + topic: "home/remote/rm2/code/set" + payload: "260050000001339414371412131313131213131313131313131313381436143714371437143713381412133813121338141213131313131313381312133814121338143614371437140005060001324a14000d050000000000000000" diff --git a/hass/settings/configuration/script/audio_vol_minus.yaml b/hass/settings/configuration/script/audio_vol_minus.yaml new file mode 100644 index 0000000..f2b2784 --- /dev/null +++ b/hass/settings/configuration/script/audio_vol_minus.yaml @@ -0,0 +1,6 @@ +audio_vol_minus: + sequence: + - service: mqtt.publish + data: + topic: "home/remote/rm2/code/set" + payload: "260050000001329513381313131313121313131313131313131312381338133813371338133813381313121313131313133812131313131313381338123813381313123813381338120005060001314b13000d050000000000000000" diff --git a/hass/settings/configuration/script/audio_vol_plus.yaml b/hass/settings/configuration/script/audio_vol_plus.yaml new file mode 100644 index 0000000..9fb51e0 --- /dev/null +++ b/hass/settings/configuration/script/audio_vol_plus.yaml @@ -0,0 +1,6 @@ +audio_vol_plus: + sequence: + - service: mqtt.publish + data: + topic: "home/remote/rm2/code/set" + payload: "260058000001309613381213131313131213141214121313141113381437143614371437133714371437141213121313143713121313131313131238143713381313123814371436150005030001324916000c5b0001324914000d05" diff --git a/hass/settings/configuration/script/ir_learn_packet.yaml b/hass/settings/configuration/script/ir_learn_packet.yaml new file mode 100644 index 0000000..bb74565 --- /dev/null +++ b/hass/settings/configuration/script/ir_learn_packet.yaml @@ -0,0 +1,6 @@ +ir_learn_packet: + sequence: + - service: mqtt.publish + data: + topic: "home/remote/rm2/learn/learn" + payload: "test" diff --git a/hass/settings/configuration/script/tv_onoff.yaml b/hass/settings/configuration/script/tv_onoff.yaml new file mode 100644 index 0000000..52bc5a3 --- /dev/null +++ b/hass/settings/configuration/script/tv_onoff.yaml @@ -0,0 +1,6 @@ +tv_onoff: + sequence: + - service: mqtt.publish + data: + topic: "home/remote/rm2/code/set" + payload: "2600580000012b901610151016101510161015101535170f163416351634163416351535160f1734160f1635160f15101734160f1610160f1634170f16341635160f1634163515351600052000012b4815000c6500012b4815000d05" diff --git a/hass/settings/configuration/script/tv_screen_mode.yaml b/hass/settings/configuration/script/tv_screen_mode.yaml new file mode 100644 index 0000000..bfa3638 --- /dev/null +++ b/hass/settings/configuration/script/tv_screen_mode.yaml @@ -0,0 +1,6 @@ +tv_screen_mode: + sequence: + - service: mqtt.publish + data: + topic: "home/remote/rm2/code/set" + payload: "2600500000012b901610160f1610160f1610160f1635151016351535163416351535163416101634161015101610153516341610160f1610163416351535161015101634163516341600052200012c4716000d050000000000000000" \ No newline at end of file diff --git a/hass/settings/configuration/script/tv_vol_minus.yaml b/hass/settings/configuration/script/tv_vol_minus.yaml new file mode 100644 index 0000000..e9977f6 --- /dev/null +++ b/hass/settings/configuration/script/tv_vol_minus.yaml @@ -0,0 +1,6 @@ +tv_vol_minus: + sequence: + - service: mqtt.publish + data: + topic: "home/remote/rm2/code/set" + payload: "2600500000012c8f1610151016101510161015101634161016341634163515351634163515101634161016341635153516341610151016101535160f1610151016101535163416351500052100012b4716000d050000000000000000" diff --git a/hass/settings/configuration/script/tv_vol_plus.yaml b/hass/settings/configuration/script/tv_vol_plus.yaml new file mode 100644 index 0000000..4d15a5a --- /dev/null +++ b/hass/settings/configuration/script/tv_vol_plus.yaml @@ -0,0 +1,6 @@ +tv_vol_plus: + sequence: + - service: mqtt.publish + data: + topic: "home/remote/rm2/code/set" + payload: "2600580000012b90160f1610160f1610151016101535160f16351535163416351535163416101535160f1635160f16341635160f16101510163416101634161015101635153515351600052100012b4716000c6500012b4716000d05" diff --git a/hass/settings/configuration/script/water_counter_set.yaml b/hass/settings/configuration/script/water_counter_set.yaml new file mode 100644 index 0000000..bc8b93d --- /dev/null +++ b/hass/settings/configuration/script/water_counter_set.yaml @@ -0,0 +1,10 @@ +# water_counter_set: +# sequence: +# - service: mqtt.publish +# data: +# topic: 'home/bath/bath/cold/set' +# payload: 127110 +# - service: mqtt.publish +# data: +# topic: 'home/bath/bath/hot/set' +# payload: 67270 diff --git a/hass/settings/configuration/sensor/lux_outside.yaml b/hass/settings/configuration/sensor/lux_outside.yaml new file mode 100644 index 0000000..b26af2d --- /dev/null +++ b/hass/settings/configuration/sensor/lux_outside.yaml @@ -0,0 +1,4 @@ +# - platform: template +# sensors: +# lux_outside: +# value_template: '{{0}}' \ No newline at end of file diff --git a/hass/settings/configuration/sensor/power-modbus.yaml b/hass/settings/configuration/sensor/power-modbus.yaml new file mode 100644 index 0000000..44a0bae --- /dev/null +++ b/hass/settings/configuration/sensor/power-modbus.yaml @@ -0,0 +1,67 @@ +- platform: template + sensors: + power_main_total: + unit_of_measurement: 'kWh' + value_template: "{{ states('sensor.power_total') | float + 4733.6 | round(2) }}" + #4733.6 - разница показаний. + +- platform: modbus + scan_interval: 30 + registers: + - name: power_total + slave: 1 + register: 0 + count: 2 + precision: 2 + scale: 0.01 + unit_of_measurement: 'kWh' + # - name: power_export + # slave: 1 + # register: 8 + # count: 2 + # precision: 2 + # scale: 0.01 + # unit_of_measurement: 'kWh' + # - name: power_import + # slave: 1 + # register: 10 + # count: 2 + # precision: 2 + # scale: 0.01 + # unit_of_measurement: 'kWh' + - name: power_voltage + slave: 1 + register: 12 + precision: 1 + scale: 0.1 + unit_of_measurement: 'V' + - name: power_current + slave: 1 + register: 13 + precision: 2 + scale: 0.01 + unit_of_measurement: 'A' + - name: power_active_power + slave: 1 + register: 14 + precision: 3 + scale: 0.001 + unit_of_measurement: 'KW' + - name: power_reactive_power + slave: 1 + register: 15 + precision: 3 + scale: 0.001 + unit_of_measurement: 'Kvar' + - name: power_power_factor + slave: 1 + register: 16 + precision: 3 + scale: 0.001 + unit_of_measurement: '%' + - name: power_frequency + slave: 1 + register: 17 + precision: 2 + scale: 0.01 + unit_of_measurement: 'Hz' \ No newline at end of file diff --git a/hass/settings/configuration/sensor/presence/balcony.yaml b/hass/settings/configuration/sensor/presence/balcony.yaml new file mode 100644 index 0000000..edf34d4 --- /dev/null +++ b/hass/settings/configuration/sensor/presence/balcony.yaml @@ -0,0 +1,3 @@ +# - platform: mqtt +# state_topic: 'home/presence/controller/balcony' +# name: 'presence_balcony' diff --git a/hass/settings/configuration/sensor/presence/hall.yaml b/hass/settings/configuration/sensor/presence/hall.yaml new file mode 100644 index 0000000..66c1319 --- /dev/null +++ b/hass/settings/configuration/sensor/presence/hall.yaml @@ -0,0 +1,3 @@ +# - platform: mqtt +# state_topic: 'home/presence/controller/hall' +# name: 'presence_hall' diff --git a/hass/settings/configuration/sensor/presence/kitchen.yaml b/hass/settings/configuration/sensor/presence/kitchen.yaml new file mode 100644 index 0000000..e3b6e46 --- /dev/null +++ b/hass/settings/configuration/sensor/presence/kitchen.yaml @@ -0,0 +1,3 @@ +# - platform: mqtt +# state_topic: 'home/presence/controller/kitchen' +# name: 'presence_kitchen' diff --git a/hass/settings/configuration/sensor/speedtest.yaml b/hass/settings/configuration/sensor/speedtest.yaml new file mode 100644 index 0000000..184ddef --- /dev/null +++ b/hass/settings/configuration/sensor/speedtest.yaml @@ -0,0 +1,5 @@ +- platform: speedtest + monitored_conditions: + - ping + - download + - upload \ No newline at end of file diff --git a/hass/settings/configuration/sensor/system/certificate.yaml b/hass/settings/configuration/sensor/system/certificate.yaml new file mode 100644 index 0000000..ae7fd48 --- /dev/null +++ b/hass/settings/configuration/sensor/system/certificate.yaml @@ -0,0 +1,2 @@ +- platform: cert_expiry + host: !secret base_host \ No newline at end of file diff --git a/hass/settings/configuration/sensor/system/system.yaml b/hass/settings/configuration/sensor/system/system.yaml new file mode 100644 index 0000000..6e7c024 --- /dev/null +++ b/hass/settings/configuration/sensor/system/system.yaml @@ -0,0 +1,7 @@ +- platform: systemmonitor + resources: + - type: processor_use + - type: memory_free + - type: swap_use_percent + - type: disk_use_percent + arg: / diff --git a/hass/settings/configuration/sensor/tags/mamik_band.yaml b/hass/settings/configuration/sensor/tags/mamik_band.yaml new file mode 100644 index 0000000..8e1ad45 --- /dev/null +++ b/hass/settings/configuration/sensor/tags/mamik_band.yaml @@ -0,0 +1,7 @@ +- platform: mqtt_room + device_id: !secret tag_mamik_band + name: tag_mamik_band + friendly_name: Мама браслет + state_topic: home/presence + timeout: 15 + away_timeout: 120 diff --git a/hass/settings/configuration/sensor/tags/monster_nut.yaml b/hass/settings/configuration/sensor/tags/monster_nut.yaml new file mode 100644 index 0000000..e8d429b --- /dev/null +++ b/hass/settings/configuration/sensor/tags/monster_nut.yaml @@ -0,0 +1,8 @@ +- platform: mqtt_room + device_id: !secret tag_monster_nut + name: tag_monster_nut + friendly_name: Данил метка + state_topic: home/presence + timeout: 15 + away_timeout: 120 + diff --git a/hass/settings/configuration/sensor/tags/nut_blue.yaml b/hass/settings/configuration/sensor/tags/nut_blue.yaml new file mode 100644 index 0000000..30f5408 --- /dev/null +++ b/hass/settings/configuration/sensor/tags/nut_blue.yaml @@ -0,0 +1,6 @@ +- platform: mqtt_room + device_id: !secret tag_nut_blue + name: tag_nut_blue + state_topic: home/presence + timeout: 15 + away_timeout: 120 diff --git a/hass/settings/configuration/sensor/tags/nut_guest.yaml b/hass/settings/configuration/sensor/tags/nut_guest.yaml new file mode 100644 index 0000000..55363e9 --- /dev/null +++ b/hass/settings/configuration/sensor/tags/nut_guest.yaml @@ -0,0 +1,7 @@ +# +- platform: mqtt_room + device_id: !secret tag_nut_guest + name: tag_nut_guest + state_topic: home/presence + timeout: 15 + away_timeout: 120 diff --git a/hass/settings/configuration/sensor/tags/nut_white.yaml b/hass/settings/configuration/sensor/tags/nut_white.yaml new file mode 100644 index 0000000..faceaf3 --- /dev/null +++ b/hass/settings/configuration/sensor/tags/nut_white.yaml @@ -0,0 +1,6 @@ +- platform: mqtt_room + device_id: !secret tag_nut_white + name: tag_nut_white + state_topic: home/presence + timeout: 15 + away_timeout: 120 diff --git a/hass/settings/configuration/sensor/temp_and_humidity/balcony.yaml.bkp b/hass/settings/configuration/sensor/temp_and_humidity/balcony.yaml.bkp new file mode 100644 index 0000000..ceb77d1 --- /dev/null +++ b/hass/settings/configuration/sensor/temp_and_humidity/balcony.yaml.bkp @@ -0,0 +1,10 @@ +- platform: mqtt + state_topic: 'home/sensor_ht/balcony/temperature' + name: 'temperature_158d000117d37a' + unit_of_measurement: '°C' + value_template: '{{ float(value_json) / 100 }}' +- platform: mqtt + state_topic: 'home/sensor_ht/balcony/humidity' + name: 'humidity_158d000117d37a' + unit_of_measurement: '%' + value_template: '{{ float(value_json) / 100 }}' \ No newline at end of file diff --git a/hass/settings/configuration/sensor/temp_and_humidity/bath.yaml.bkp b/hass/settings/configuration/sensor/temp_and_humidity/bath.yaml.bkp new file mode 100644 index 0000000..328b63f --- /dev/null +++ b/hass/settings/configuration/sensor/temp_and_humidity/bath.yaml.bkp @@ -0,0 +1,10 @@ +- platform: mqtt + state_topic: 'home/sensor_ht/bath/temperature' + name: 'temperature_158d00012d9ad7' + unit_of_measurement: '°C' + value_template: '{{ float(value_json) / 100 }}' +- platform: mqtt + state_topic: 'home/sensor_ht/bath/humidity' + name: 'humidity_158d00012d9ad7' + unit_of_measurement: '%' + value_template: '{{ float(value_json) / 100 }}' diff --git a/hass/settings/configuration/sensor/temp_and_humidity/mainroom.yaml.bkp b/hass/settings/configuration/sensor/temp_and_humidity/mainroom.yaml.bkp new file mode 100644 index 0000000..2446f11 --- /dev/null +++ b/hass/settings/configuration/sensor/temp_and_humidity/mainroom.yaml.bkp @@ -0,0 +1,10 @@ +- platform: mqtt + state_topic: 'home/sensor_ht/mainroom/temperature' + name: 'temperature_158d0001149b3c' + unit_of_measurement: '°C' + value_template: '{{ float(value_json) / 100 }}' +- platform: mqtt + state_topic: 'home/sensor_ht/mainroom/humidity' + name: 'humidity_158d0001149b3c' + unit_of_measurement: '%' + value_template: '{{ float(value_json) / 100 }}' diff --git a/hass/settings/configuration/sensor/temp_and_humidity/outside.yaml.bkp b/hass/settings/configuration/sensor/temp_and_humidity/outside.yaml.bkp new file mode 100644 index 0000000..a51b0fe --- /dev/null +++ b/hass/settings/configuration/sensor/temp_and_humidity/outside.yaml.bkp @@ -0,0 +1,10 @@ +- platform: mqtt + state_topic: 'home/sensor_ht/outside/temperature' + name: 'temperature_158d000149c128' + unit_of_measurement: '°C' + value_template: '{{ float(value_json) / 100 }}' +- platform: mqtt + state_topic: 'home/sensor_ht/outside/humidity' + name: 'humidity_158d000149c128' + unit_of_measurement: '%' + value_template: '{{ float(value_json) / 100 }}' diff --git a/hass/settings/configuration/sensor/water_conters.yaml b/hass/settings/configuration/sensor/water_conters.yaml new file mode 100644 index 0000000..0a488fc --- /dev/null +++ b/hass/settings/configuration/sensor/water_conters.yaml @@ -0,0 +1,13 @@ +################################################# +# WATER # +################################################# +- platform: mqtt + state_topic: 'home/bath/bath/cold' + name: 'counter_cold' + value_template: '{{ (value | float ) / 1000 }}' + unit_of_measurement: 'm3' +- platform: mqtt + state_topic: 'home/bath/bath/hot' + name: 'counter_hot' + value_template: '{{ (value | float ) / 1000 }}' + unit_of_measurement: 'm3' diff --git a/hass/settings/configuration/switch/domofon_mode.yaml b/hass/settings/configuration/switch/domofon_mode.yaml new file mode 100644 index 0000000..ef8c8c9 --- /dev/null +++ b/hass/settings/configuration/switch/domofon_mode.yaml @@ -0,0 +1,7 @@ +- platform: mqtt + name: domofon_mode + state_topic: "home/domofon/domofon/mode" + command_topic: "home/domofon/domofon/mode/set" + payload_on: "1" + payload_off: "2" + retain: true \ No newline at end of file diff --git a/hass/settings/configuration/switch/domofon_open.yaml b/hass/settings/configuration/switch/domofon_open.yaml new file mode 100644 index 0000000..5e49fe8 --- /dev/null +++ b/hass/settings/configuration/switch/domofon_open.yaml @@ -0,0 +1,7 @@ +- platform: mqtt + name: domofon_open + state_topic: "home/domofon/domofon/open" + command_topic: "home/domofon/domofon/open/set" + payload_on: "1" + payload_off: "2" + retain: false \ No newline at end of file diff --git a/hass/settings/configuration/switch/kitchen_mini_light.yaml b/hass/settings/configuration/switch/kitchen_mini_light.yaml new file mode 100644 index 0000000..28f2f8f --- /dev/null +++ b/hass/settings/configuration/switch/kitchen_mini_light.yaml @@ -0,0 +1,7 @@ +- platform: mqtt + name: "kitchen_mini_light" + state_topic: "home/plug/kitchen-mini-light/status" + command_topic: "home/plug/kitchen-mini-light/status/set" + payload_on: "on" + payload_off: "off" + retain: false diff --git a/hass/settings/configuration/switch/power-strip.yaml b/hass/settings/configuration/switch/power-strip.yaml new file mode 100644 index 0000000..9608d9a --- /dev/null +++ b/hass/settings/configuration/switch/power-strip.yaml @@ -0,0 +1,4 @@ +- platform: xiaomi_miio + host: !secret powerstrip_ip + token: !secret powerstrip_key + model: zimi.powerstrip.v2 \ No newline at end of file diff --git a/hass/settings/configuration/switch/water_valve.yaml b/hass/settings/configuration/switch/water_valve.yaml new file mode 100644 index 0000000..178dbae --- /dev/null +++ b/hass/settings/configuration/switch/water_valve.yaml @@ -0,0 +1,8 @@ +- platform: mqtt + name: water_valve + state_topic: "home/bath/bath/valve" + command_topic: "home/bath/bath/valve/set" + payload_on: "open" + payload_off: "close" + optimistic: false + retain: false diff --git a/hass/settings/configuration/switch/xmas-2.yaml b/hass/settings/configuration/switch/xmas-2.yaml new file mode 100644 index 0000000..324d622 --- /dev/null +++ b/hass/settings/configuration/switch/xmas-2.yaml @@ -0,0 +1,5 @@ +- platform: broadlink + host: 192.168.1.64 + mac: 'B4430DCB7276' + type: 'sp2' + friendly_name: 'yolka' \ No newline at end of file diff --git a/hass/settings/configuration/switch/xmas-tree.yaml b/hass/settings/configuration/switch/xmas-tree.yaml new file mode 100644 index 0000000..773119a --- /dev/null +++ b/hass/settings/configuration/switch/xmas-tree.yaml @@ -0,0 +1,5 @@ +- platform: xiaomi_miio + name: wifi_socket + host: !secret wifi_socket_ip + token: !secret wifi_socket_key + model: chuangmi.plug.m1 diff --git a/hass/settings/configuration/tts/yandex_tts.yaml b/hass/settings/configuration/tts/yandex_tts.yaml new file mode 100644 index 0000000..06ab348 --- /dev/null +++ b/hass/settings/configuration/tts/yandex_tts.yaml @@ -0,0 +1,5 @@ +- platform: yandextts + api_key: 'd511d71c-6589-4d9b-a096-645271e1d1cc' + language: 'ru-RU' + codec: mp3 + voice: oksana \ No newline at end of file diff --git a/hass/settings/configuration/xiaomi_aqara/vacuum.yaml b/hass/settings/configuration/xiaomi_aqara/vacuum.yaml new file mode 100644 index 0000000..38e25b2 --- /dev/null +++ b/hass/settings/configuration/xiaomi_aqara/vacuum.yaml @@ -0,0 +1,3 @@ +- platform: xiaomi_miio + host: !secret mirobot_ip + token: !secret mirobot_key \ No newline at end of file diff --git a/hass/settings/configuration/xiaomi_aqara/xiaomi_aqara.yaml b/hass/settings/configuration/xiaomi_aqara/xiaomi_aqara.yaml new file mode 100644 index 0000000..58169de --- /dev/null +++ b/hass/settings/configuration/xiaomi_aqara/xiaomi_aqara.yaml @@ -0,0 +1,4 @@ +discovery_retry: 5 +gateways: + - mac: !secret aqara1_mac + key: !secret aqara1_key \ No newline at end of file diff --git a/hass/settings/customize_domain.yaml b/hass/settings/customize_domain.yaml new file mode 100644 index 0000000..2d9b92b --- /dev/null +++ b/hass/settings/customize_domain.yaml @@ -0,0 +1,3 @@ +automation: + initial_state: 'on' + hide_entity: true \ No newline at end of file diff --git a/hass/settings/hosts.txt b/hass/settings/hosts.txt new file mode 100644 index 0000000..7e8deea --- /dev/null +++ b/hass/settings/hosts.txt @@ -0,0 +1,8 @@ +192.168.1.6 mqtt.local +192.168.1.6 hass.local +192.168.1.6 homeassistant +192.168.1.56 mainbulb1.local +192.168.1.52 mainbulb2.local +192.168.1.54 mainbulb3.local +192.168.1.163 mainbulb4.local +192.168.1.53 mainbulb5.local \ No newline at end of file diff --git a/hass/settings/packages/home_presense.yaml b/hass/settings/packages/home_presense.yaml new file mode 100644 index 0000000..acccf96 --- /dev/null +++ b/hass/settings/packages/home_presense.yaml @@ -0,0 +1,28 @@ +--- +input_select: + danil_presence_status: + name: Danil + options: + - 'Away' + - 'Extended Away' + - 'Home' + - 'Just Arrived' + - 'Just Left' + + sveta_presence_status: + name: Sveta + options: + - 'Away' + - 'Extended Away' + - 'Home' + - 'Just Arrived' + - 'Just Left' + + mama_presence_status: + name: Mama + options: + - 'Away' + - 'Extended Away' + - 'Home' + - 'Just Arrived' + - 'Just Left' \ No newline at end of file diff --git a/hass/settings/secrets.yaml.sample b/hass/settings/secrets.yaml.sample new file mode 100644 index 0000000..9d48f67 --- /dev/null +++ b/hass/settings/secrets.yaml.sample @@ -0,0 +1,120 @@ +################################### +# MAIN SETTINGS # +################################### +ha_password: xxxxxxxxx +timezone: Europe/London +base_url: xxxxxxxxx +base_host: xxxxxxxxx +base_localname: xxxxxxxxx +modbus_host: xxxxxxxxx +modbus_port: xxxxxxxxx + +################################### +# RECORDER SETTINGS # +################################### +mysql_url: xxxxxxxxx + +################################### +# APPLETV SETTINGS # +################################### +atv4_ip: xxxxxxxxx +atv4_login: xxxxxxxxx +atv4_credentials: xxxxxxxxx + +################################### +# PROXY SETTINGS # +################################### +proxy_url: xxxxxxxxx + +################################### +# MQTT SETTINGS # +################################### +mqtt_ip: xxxxxxxxx +mqtt_user: xxxxxxxxx +mqtt_password: xxxxxxxxx +mqtt_clientid: xxxxxxxxx + +################################### +# XIAOMI AND AQARA SETTINGS # +################################### +aqara1_mac: xxxxxxxxxxxx +aqara1_key: xxxxxxxxxxxxxxxx + +mirobot_ip: xxxxxxxxx +mirobot_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +powerstrip_ip: xxxxxxxxx +powerstrip_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +wifi_socket_ip: xxxxxxxxx +wifi_socket_key: xxxxxxxxx + +################################### +# NEST # +################################### +nest_client_id: xxxxxxxxx +nest_client_secret: xxxxxxxxx + +################################### +# NOTIFICATION SETTINGS # +################################### +telegram_key: xxxxxxxxx +telegram_group_chat: xxxxxxxxx +telegram_monster_chat: xxxxxxxxx + +################################### +# TAGS # +################################### +tag_mamik_band: xxxxxxxxx +tag_monster_nut: xxxxxxxxx +tag_nut_blue: xxxxxxxxx +tag_nut_white: xxxxxxxxx +tag_nut_guest: xxxxxxxxx + + +################################### +# ZONES # +################################### +home_latitude: 1.0 +home_longitude: 1.0 + +work_latitude: 1.0 +work_longitude: 1.0 + +################################### +# SYNOLOGY SETTINGS # +################################### +syno_url: xxxxxxxxx +syno_pass: xxxxxxxxx + +################################### +# MOSENERGO SETTINGS # +################################### +mosenergo_main_login: xxxxxxxxx +mosenergo_main_pwd: xxxxxxxxx + +mosenergo_alt_login: xxxxxxxxx +mosenergo_alt_pwd: xxxxxxxxx + +# ----------------------------------------------------------- # + +################################### +# APPDAEMON SETTINGS # +################################### +ha_url: xxxxxxxxx +ha_token: xxxxxxxxx + + +################################### +# NARODMON SETTINGS # +################################### +narodmon_key: xxxxxxxxx + +################################### +# MOS.RU SETTINGS # +################################### +mosru_login: xxxxxxxxx +mosru_pwd: xxxxxxxxx +mosru_token: xxxxxxxxx +mosru_flat_id: xxxxxxxxx +mosru_paycode: xxxxxxxxx \ No newline at end of file diff --git a/nginx-config/site-confs/default b/nginx-config/site-confs/default new file mode 100644 index 0000000..a538260 --- /dev/null +++ b/nginx-config/site-confs/default @@ -0,0 +1,23 @@ +server { + listen 80 default_server; + listen 443 default ssl; + + root /config/www; + index index.html index.htm index.php; + + server_name _; + + ssl_certificate /config/keys/cert.crt; + ssl_certificate_key /config/keys/cert.key; + + client_max_body_size 0; + + #letsencrypt challenge response + location ~/.well-known/acme-challenge/(.*)$ { + return 200 '$1'; + } + + location / { + try_files $uri $uri/ /index.html /index.php?$args =404; + } +}