commit 9498a076eef7478e0179f31b13776c75f6d6546f Author: Your Name Date: Tue Mar 10 07:08:59 2026 +0000 updating configuration diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4350e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +*.env +*.db +*.db-shm +*.db-wal +*.log +*.1 +*.fault +__pycache__/ +hass/settings/.storage +hass/settings/*.log +secrets/ +certbot/settings/data/ +hass/settings/custom_components/ +zigbee2mqtt/settings/log/ +zigbee2mqtt/settings/configuration.yaml +zigbee2mqtt/settings/coordinator_backup.json +zigbee2mqtt/settings/state.json +nginx/settings/site-confs/z2m_htpasswd +postgres/settings/data/ +node-red/settings/data/.npm/ +node-red/settings/data/lib/ +node-red/settings/data/node-modules/ +node-red/settings/data/flows_cred.json +node-red/settings/data/*.backup +node-red/settings/data/.config.users.json +node-red/settings/data/package-lock.json +node-red/settings/data/package.json +node-red/settings/data/.config.nodes.json +node-red/settings/data/.config.runtime.json +homeconnect2mqtt/settings/config.json +hass/settings/backups/ +matter-bridge/settings/data/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..aa3cb32 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "secrets"] + path = secrets + url = git@github.com:monster1025/secrets.git + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..24959bf --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +#SHELL := /bin/bash + +ARGS = `arg="$(filter-out $@,$(MAKECMDGOALS))" && echo $${arg:-${1}}` + +update_secrets_sample: + @echo "Masking passwords..." + #hass + @cat ./secrets/hass-alt/settings/secrets.yaml | sed "s/\:.*/\: xxxxxxxxx/g" > hass/settings/secrets.yaml.sample #mask passwords + @sed -i "s/latitude:.*/latitude: 1.0/g" hass/settings/secrets.yaml.sample #must be a number + @sed -i "s/longitude:.*/longitude: 1.0/g" hass/settings/secrets.yaml.sample #must be a number + @sed -i "s/aqara1_mac:.*/aqara1_mac: xxxxxxxxxxxx/g" hass/settings/secrets.yaml.sample #len 12 + @sed -i "s/aqara1_key:.*/aqara1_key: xxxxxxxxxxxxxxxx/g" hass/settings/secrets.yaml.sample #len 16 + @sed -i "s/mirobot_key:.*/mirobot_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/g" hass/settings/secrets.yaml.sample #len 32 + @sed -i "s/powerstrip_key:.*/powerstrip_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/g" hass/settings/secrets.yaml.sample #len 32 + @sed -i "s/telegram_chat:.*/telegram_chat: 0/g" hass/settings/secrets.yaml.sample #must be a number + @sed -i "s/timezone:.*/timezone: Europe\/London/g" hass/settings/secrets.yaml.sample #must be valid + @sed -i "s/telegram_group_chat:.*/telegram_group_chat: 0/g" hass/settings/secrets.yaml.sample #must be valid + @sed -i "s/telegram_monster_chat:.*/telegram_monster_chat: 0/g" hass/settings/secrets.yaml.sample #must be valid + @sed -i "s/purifier_key:.*/purifier_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/g" hass/settings/secrets.yaml.sample #must be valid + @sed -i "s/philips_downlight_token:.*/philips_downlight_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/g" hass/settings/secrets.yaml.sample #must be valid + @sed -i "s/wifi_socket_key:.*/wifi_socket_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/g" hass/settings/secrets.yaml.sample #must be valid + @sed -i "s/modbus_host:.*/modbus_host: modbus.host/g" hass/settings/secrets.yaml.sample #must be valid + @sed -i "s/modbus_port:.*/modbus_port: 9977/g" hass/settings/secrets.yaml.sample #must be valid + @sed -i "s/xiaomi_remote_token:.*/xiaomi_remote_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/g" hass/settings/secrets.yaml.sample #must be valid + #@jq '(.wifi_ssid, .wifi_pwd, .mqtt_user, .mqtt_pwd) |= "XXXX"' -c sls/settings/config.json > sls/settings/config.json.sample + + @echo "Masking env files..." + @find . -name *.env | xargs -I{} cp {} {}.sample + @find . -name *.env.sample | xargs -I{} sed -i "s/\=.*/\=xxxxxxxxx/g" {} + +# @cat zigbee2mqtt/settings/configuration.yaml | sed "s/\:.*/\: xxxxxxxxx/g" > zigbee2mqtt/settings/configuration.yaml.sample #mask passwords + +commit: update_secrets_sample + git add . + git diff-index --quiet HEAD || git commit -a -m "$(call ARGS,\"updating configuration\")" + git push + +%: + @: diff --git a/README.md b/README.md new file mode 100644 index 0000000..dca3e27 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Home Assistant FNV - Документация + +Репозиторий содержит конфигурацию умного дома на базе Home Assistant с интеграцией различных сервисов и устройств. + +## Структура документации + +- [Docker контейнеры и настройки](docs/docker.md) - Описание всех Docker сервисов и их конфигурации +- [Home Assistant конфигурация](docs/home-assistant.md) - Настройки Home Assistant, устройства и интеграции +- [AppDaemon приложения](docs/appdaemon.md) - Скрипты, дашборды и функционал AppDaemon + +## Быстрый старт + +1. Склонируйте репозиторий +2. Настройте переменные окружения в соответствующих `.env` файлах +3. Запустите `docker-compose up -d` + +## Основные компоненты + +- **Home Assistant** - Центральная система умного дома +- **AppDaemon** - Автоматизация и скрипты +- **ESPHome** - Управление ESP устройствами +- **Zigbee2MQTT** - Zigbee устройства +- **Node-RED** - Визуальное программирование +- **Mosquitto** - MQTT брокер +- **PostgreSQL** - База данных +- **Nginx** - Веб-сервер с SSL +- **Matter Bridge** - Интеграция с Matter устройствами \ No newline at end of file diff --git a/appdaemon/docker/Dockerfile b/appdaemon/docker/Dockerfile new file mode 100644 index 0000000..cce683f --- /dev/null +++ b/appdaemon/docker/Dockerfile @@ -0,0 +1,34 @@ +FROM python:3.12 +RUN apt update && apt -y install locales && locale-gen ru_RU.UTF-8 +ENV LANG=ru_RU.UTF-8 +ENV LANGUAGE=ru_RU:ru +ENV LC_ALL=ru_RU.UTF-8 +ENV PYTHONIOENCODING=utf-8 + +VOLUME /conf +VOLUME /certs +EXPOSE 5050 + +# Copy appdaemon into image +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +# Download and build appdaemon, 4.0.0b2 +# --branch=4.0.0b1 +RUN git clone --recurse-submodules https://github.com/home-assistant/appdaemon.git . +RUN pip install --upgrade pip . && pip3 install requests python-dateutil beautifulsoup4 + +# Start script +RUN chmod +x /usr/src/app/dockerStart.sh + +RUN pip3 install \ + attrs==19.1.0 \ + #emp_mos_api==0.12 \ + packaging==16.8 \ + #python-Levenshtein==0.12.0 \ + voluptuous==0.13.1 \ + tinytuya==1.13.1 + +ADD ./emp_mos_api /emp_mos_api + +CMD [ "./dockerStart.sh" ] diff --git a/appdaemon/docker/emp_mos_api/.gitignore b/appdaemon/docker/emp_mos_api/.gitignore new file mode 100644 index 0000000..fe9d61c --- /dev/null +++ b/appdaemon/docker/emp_mos_api/.gitignore @@ -0,0 +1,57 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# PyCharm +.idea diff --git a/appdaemon/docker/emp_mos_api/LICENSE b/appdaemon/docker/emp_mos_api/LICENSE new file mode 100644 index 0000000..1fcff06 --- /dev/null +++ b/appdaemon/docker/emp_mos_api/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/appdaemon/docker/emp_mos_api/MANIFEST.in b/appdaemon/docker/emp_mos_api/MANIFEST.in new file mode 100644 index 0000000..8215683 --- /dev/null +++ b/appdaemon/docker/emp_mos_api/MANIFEST.in @@ -0,0 +1,4 @@ +include *.md +include LICENSE +recursive-include emp_mos_api/*.py +recursive-include emp_mos_api/examples/*.py diff --git a/appdaemon/docker/emp_mos_api/README.md b/appdaemon/docker/emp_mos_api/README.md new file mode 100644 index 0000000..99f5a6e --- /dev/null +++ b/appdaemon/docker/emp_mos_api/README.md @@ -0,0 +1,110 @@ +### Неофициальная библиотека для запросов к Единой мобильной платформе города Москвы + +[Сайт](http://mosapps.mos.ru/dev) +[Единая мобильная платформа города Москвы](http://serviceprovider.if.emp.msk.ru/default/auth) + +Для выполнения запросов требуются: +- уникальный ключ вашего приложения (token), [выданный Правительством Москвы](http://mosapps.mos.ru/dev). +- некий guid (guid) +- при работе с телефона указывается user-agent и версия приложения + +Создаем объект api +``` +from emp_mos_api.mos import MosAPI + +api = MosAPI(token=args.token, + user_agent=args.user_agent, + guid=args.guid, + dev_user_agent=args.dev_user_agent, + dev_app_version=args.dev_app_version) +``` + +## Поддержка вызовов + +### Авторизация +Авторизируемся на сервере при помощи номера телефона и пароля, полученного из приложения +``` +api.login(args.login, args.pwd) +``` +### Завершение сессии +``` +api.logout() +``` +### Получить профиль и адрес +``` +response = api.get_profile() +``` +### Поиск адреса по подстроке +``` +adresses = api.address_search(pattern) +``` + +### Получить список квартир +``` +flats = api.get_flats() +``` +### Удалить квартиру +``` +api.flat_delete(flat_id) +``` +### Добавить квартиру +``` +api.flat_add(name, unom, unad, address, flat_number, paycode) +``` +### Получить электронный платежный документ +``` +api.get_eepd_wait_result(flat_id, period, timeout): + +``` + +### Получить список счетчиков воды +``` +water = api.get_watercounters(flat_id) +``` +### Отправить новые показания воды +``` +api.send_watercounters(flat_id, new_values) +``` +### Получить список счетчиков электроэнергии +``` +electro = api.get_electrocounters(flat_id) +``` +### Отправить новые показания электроэнергии +``` +api.send_electrocounters(flat_id, new_values) +``` +### Получить епд +``` +epd = api.get_epd(flat_id, begin_period, end_period) +``` +### Получить штрафы +``` +fines = api.get_car_fines(sts) +``` + + + +## Примеры: +[examples](https://github.com/dontsovcmc/emp_mos_ru/tree/master/emp_mos_api/examples) + + +## Установка: +``` +[sudo] pip install -U emp-mos-api +``` +Если нет pip, то ставим его: +#### linux +``` +sudo apt-get install python-pip python-dev build-essential +``` +#### windows +``` +python https://bootstrap.pypa.io/get-pip.py +# добавляем пусть до pip в PATH (C:\Python27\Scripts) +перезагружаем терминал +``` + +## Благодарности + +@monster1025 + diff --git a/appdaemon/docker/emp_mos_api/emp_mos_api/__init__.py b/appdaemon/docker/emp_mos_api/emp_mos_api/__init__.py new file mode 100644 index 0000000..8b35865 --- /dev/null +++ b/appdaemon/docker/emp_mos_api/emp_mos_api/__init__.py @@ -0,0 +1,3 @@ + +from emp_mos_api.mos import MosAPI, Water, Watercounter, \ + AuthException, EmpServerException \ No newline at end of file diff --git a/appdaemon/docker/emp_mos_api/emp_mos_api/examples/__init__.py b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_car_fine.py b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_car_fine.py new file mode 100644 index 0000000..dcc6eff --- /dev/null +++ b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_car_fine.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from __future__ import print_function +import argparse +from emp_mos_api import MosAPI, AuthException, EmpServerException + +# Код написан общим для python2, python3 благодаря +# http://python-future.org/compatible_idioms.html + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='emp.mos.ru API') + parser.add_argument('--token', help='application token') + parser.add_argument('--user_agent', default='okhttp/3.8.1', help='User-Agent header') + parser.add_argument('--guid', help='some guid') + parser.add_argument('--dev_user_agent', default='Android', help='application user agent') + parser.add_argument('--dev_app_version', help='application version') + + parser.add_argument('--login', help='your login (phone number: 7xxxxxxxxxx)') + parser.add_argument('--pwd', help='your password') + + parser.add_argument('--sts', help='your car sts') + + args = parser.parse_args() + + # + try: + api = MosAPI(token=args.token, + user_agent=args.user_agent, + guid=args.guid, + dev_user_agent=args.dev_user_agent, + dev_app_version=args.dev_app_version, + timeout=6) + + api.login(args.login, args.pwd) + + car_fines = api.get_car_fines(args.sts) + unpaid = car_fines['unpaid'] + print("Неоплаченных штрафов: {}".format(len(unpaid))) + + for u in unpaid: + print("\nНомер: {}".format(u['seriesAndNumber'])) + print("Дата: {}".format(u['date'])) + print("Место: {}".format(u['offence_place'])) + print("Статья: {}".format(u['offenceType'])) + print("Сумма: {}".format(u['cost'])) + print("is_discount: {}".format(u['is_discount'])) + print("drive_license: {}".format(u['drive_license'])) + print("sts_number: {}".format(u['sts_number'])) + print("executionState: {}".format(u['executionState'])) + print("is_fssp: {}".format(u['is_fssp'])) + + + except AuthException as err: + print('Некорректный логин или пароль') + except EmpServerException as err: + print('Ошибка сервера: {}'.format(err)) + except Exception as err: + print('Ошибка: {}'.format(err)) + finally: + try: + api.logout() + except Exception as err: + print('Ошибка: {}'.format(err)) diff --git a/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_electro_epd.py b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_electro_epd.py new file mode 100644 index 0000000..65db4d7 --- /dev/null +++ b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_electro_epd.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +from __future__ import print_function +import argparse +import time +from emp_mos_api import MosAPI, AuthException, EmpServerException + +# Код написан общим для python2, python3 благодаря +# http://python-future.org/compatible_idioms.html + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='emp.mos.ru API') + parser.add_argument('--token', help='application token') + parser.add_argument('--user_agent', default='okhttp/3.8.1', help='User-Agent header') + parser.add_argument('--guid', help='some guid') + parser.add_argument('--dev_user_agent', default='Android', help='application user agent') + parser.add_argument('--dev_app_version', help='application version') + + parser.add_argument('--login', help='your login (phone number: 7xxxxxxxxxx)') + parser.add_argument('--pwd', help='your password') + + args = parser.parse_args() + + # + try: + api = MosAPI(token=args.token, + user_agent=args.user_agent, + guid=args.guid, + dev_user_agent=args.dev_user_agent, + dev_app_version=args.dev_app_version, + timeout=6) + + api.login(args.login, args.pwd) + p = api.get_profile() + + flats = api.get_flats() + + print('Кол-во квартир: ', len(flats)) + assert flats, 'Добавьте квартиру в приложении Госуслуги Москвы' + + for f in flats: + # print(f) + print('Квартира #', flats.index(f) + 1) + print('Название: ', f['name']) + print('Адрес: ', f['address']) + print('Номер кв: ', f['flat_number']) + print('Номер платежки: ', f['paycode']) + + print('Счет электро: ', f['electro_account']) + print('Счетчик электро: ', f['electro_device']) + + epds = api.get_epd(f['flat_id'], '01.01.2018', '31.12.2018') + for epd in epds: + period = epd['period'] + epd_total = epd['amount'] + epd_is_paid = epd['payment_status'] == 'PAID' + print(" - Дата: {}, сумма: {}, оплачен: {}.".format(period, epd_total, epd_is_paid)) + + if f['electro_account'] != "": + electro = api.get_electrocounters(f['flat_id']) + balance = electro['balance'] + last_zone1 = electro['zones'][0]['value'] + print("Баланс электро: {}, показания 1 зоны: {}".format(balance, last_zone1)) + # data = [{'counter_id': 'T1', 'indication': 6887, 'period': '2018-12-27'}] + # send = api.send_electrocounters(f['flat_id'], data) + + except AuthException as err: + print('Некорректный логин или пароль') + except EmpServerException as err: + print('Ошибка сервера: {}'.format(err)) + except Exception as err: + print('Ошибка: {}'.format(err)) + finally: + try: + api.logout() + except Exception as err: + print('Ошибка: {}'.format(err)) diff --git a/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_flat.py b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_flat.py new file mode 100644 index 0000000..bb8354c --- /dev/null +++ b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_flat.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +from __future__ import print_function +import argparse +from emp_mos_api import MosAPI, AuthException, EmpServerException + +# Код написан общим для python2, python3 благодаря +# http://python-future.org/compatible_idioms.html + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='emp.mos.ru API') + parser.add_argument('--token', help='application token') + parser.add_argument('--user_agent', default='okhttp/3.8.1', help='User-Agent header') + parser.add_argument('--guid', help='some guid') + parser.add_argument('--dev_user_agent', default='Android', help='application user agent') + parser.add_argument('--dev_app_version', help='3.5.0.195 (102)') + + parser.add_argument('--login', help='your login (phone number: 7xxxxxxxxxx)') + parser.add_argument('--pwd', help='your password') + + args = parser.parse_args() + + # + api = MosAPI(token=args.token, + user_agent=args.user_agent, + guid=args.guid, + dev_user_agent=args.dev_user_agent, + dev_app_version=args.dev_app_version, + timeout=6) + + try: + api.login(args.login, args.pwd) + + flats = api.get_flats() + + print('Кол-во квартир: ', len(flats)) + assert flats, 'Добавьте квартиру в приложении Госуслуги Москвы' + + for f in flats: + print('Квартира #', flats.index(f) + 1) + print('Название: ', f['name']) + print('Адрес: ', f['address']) + print('Номер кв: ', f['flat_number']) + print('Номер платежки: ', f['paycode']) + + except AuthException as err: + print('Некорректный логин или пароль') + except EmpServerException as err: + print('Ошибка сервера: {}'.format(err)) + except Exception as err: + print('Ошибка: {}'.format(err)) + finally: + try: + api.logout() + except Exception as err: + print('Ошибка: {}'.format(err)) diff --git a/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_profile.py b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_profile.py new file mode 100644 index 0000000..91fe929 --- /dev/null +++ b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_profile.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from __future__ import print_function +import argparse +from emp_mos_api import MosAPI, AuthException, EmpServerException + +# Код написан общим для python2, python3 благодаря +# http://python-future.org/compatible_idioms.html + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='emp.mos.ru API') + parser.add_argument('--token', help='application token') + parser.add_argument('--user_agent', default='okhttp/3.8.1', help='User-Agent header') + parser.add_argument('--guid', help='some guid') + parser.add_argument('--dev_user_agent', default='Android', help='application user agent') + parser.add_argument('--dev_app_version', help='application version') + + parser.add_argument('--login', help='your login (phone number: 7xxxxxxxxxx)') + parser.add_argument('--pwd', help='your password') + + args = parser.parse_args() + + # + api = MosAPI(token=args.token, + user_agent=args.user_agent, + guid=args.guid, + dev_user_agent=args.dev_user_agent, + dev_app_version=args.dev_app_version, + timeout=6) + + try: + api.login(args.login, args.pwd) + p = api.get_profile() + + print('ФИО: ', p['firstname'], + ' ', p['middlename'], + ' ', p['lastname']) + print('Дата рождения: ', p['birthdate']) + print('Телефон: ', p['msisdn']) + print('Эл. почта: ', p['email']) + + flats = api.get_flats() + + print('Кол-во квартир: ', len(flats)) + assert flats, 'Добавьте квартиру в приложении Госуслуги Москвы' + + for f in flats: + print('Квартира #', flats.index(f) + 1) + print('Название: ', f['name']) + print('Адрес: ', f['address']) + print('Номер кв: ', f['flat_number']) + print('Номер платежки: ', f['paycode']) + + except AuthException as err: + print('Некорректный логин или пароль') + except EmpServerException as err: + print('Ошибка сервера: {}'.format(err)) + except Exception as err: + print('Ошибка: {}'.format(err)) + finally: + try: + api.logout() + except Exception as err: + print('Ошибка: {}'.format(err)) diff --git a/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_watermeters.py b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_watermeters.py new file mode 100644 index 0000000..8307512 --- /dev/null +++ b/appdaemon/docker/emp_mos_api/emp_mos_api/examples/read_watermeters.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +from __future__ import print_function +import argparse +from emp_mos_api import MosAPI, Water, Watercounter + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='emp.mos.ru API') + parser.add_argument('--token', help='application token') + parser.add_argument('--user_agent', default='okhttp/3.8.1', help='User-Agent header') + parser.add_argument('--guid', help='some guid') + parser.add_argument('--dev_user_agent', default='Android', help='application user agent') + parser.add_argument('--dev_app_version', help='application version') + + parser.add_argument('--login', help='your login (phone number: 7xxxxxxxxxx)') + parser.add_argument('--pwd', help='your password') + + parser.add_argument('--flat_id', help='id квартиры на сервере. если не указывать, отправится в 1ю.') + + parser.add_argument('--hot', type=float, help='Hot water new value') + parser.add_argument('--cold', type=float, help='Cold water new value') + + args = parser.parse_args() + + # + api = MosAPI(token=args.token, + user_agent=args.user_agent, + guid=args.guid, + dev_user_agent=args.dev_user_agent, + dev_app_version=args.dev_app_version, + verify=True) + + try: + api.login(args.login, args.pwd) + + if not args.flat_id: + flats = api.get_flats() + assert flats, u'Добавьте квартиру в приложении Госуслуги Москвы' + f = flats[0] + print('Адрес: ', f['address']) + print('Номер кв: ', f['flat_number']) + print('Номер платежки: ', f['paycode']) + + flat_id = f['flat_id'] + else: + flat_id = args.flat_id + + counters = api.get_watercounters(flat_id)['counters'] + + new_values = [] + + hots_json = list(filter(lambda x: x['type'] == Water.HOT, counters)) + if hots_json: + hot_value = Watercounter.last_value(hots_json[0]) + if hot_value: + print('Текущее показание горячей воды: {:.2f} m3'.format(hot_value)) + else: + print('Показания горячей воды не передавались больше 3-х месяцев') + + if args.hot: + new_values.append(Watercounter.serialize_for_send(hots_json[0], args.hot)) + print('Новое показание горячей воды: {:.2f} m3'.format(args.hot)) + else: + print('Не найден счетчик горячей воды') + + # + colds_json = list(filter(lambda x: x['type'] == Water.COLD, counters)) + if colds_json: + cold_value = Watercounter.last_value(colds_json[0]) + if cold_value: + print('Текущее показание холодной воды: {:.2f} m3'.format(cold_value)) + else: + print('Показания холодной воды не передавались больше 3-х месяцев') + + if args.cold: + new_values.append(Watercounter.serialize_for_send(colds_json[0], args.cold)) + print('Новое показание холодной воды: {:.2f} m3'.format(args.cold)) + else: + print('Не найден счетчик холодной воды') + + if new_values: + api.send_watercounters(flat_id, new_values) + print('Показания отправлены на сервер') + + finally: + api.logout() #долго diff --git a/appdaemon/docker/emp_mos_api/emp_mos_api/mos.py b/appdaemon/docker/emp_mos_api/emp_mos_api/mos.py new file mode 100644 index 0000000..98083ee --- /dev/null +++ b/appdaemon/docker/emp_mos_api/emp_mos_api/mos.py @@ -0,0 +1,956 @@ +# -*- coding: utf-8 -*- + +from __future__ import print_function +import six +import time +from datetime import datetime, tzinfo +import requests +from copy import deepcopy + +API_V1_0 = 'https://emp.mos.ru/v1.0' +API_V1_1 = 'https://emp.mos.ru/v1.1' +API_V1_2 = 'https://emp.mos.ru/v1.2' + +class AuthException(Exception): + pass + + +class EmpServerException(Exception): + def __init__(self, message, code): + self.message = message + self.code = code + if six.PY2: + super(Exception, self).__init__('{0} (code:{1})'.format(self.message.encode('utf-8'), self.code)) + else: + super().__init__('{0} (code:{1})'.format(self.message, self.code)) + + +class EmpCounterNotVerifiedException(EmpServerException): + """ + 'errorCode' 'errorMessage' + 3454: 'Не удалось передать показания за сентябрь по счётчикам <серийный номер>: "Невозможно внести показание, поскольку не введены показания за три и более месяца, предшествующих текущему. Для возобновления + удалённой передачи показаний Вам следует обратиться в Центр госуслуг/ГКУ ИС района. + """ + pass + + +class EmpAlreadySendException(EmpServerException): + """Не удалось передать показания за X по счётчикам 12345678: + "Вы не можете изменить показания, переданные через портал mos.ru или введенные сотрудниками" \ + " Центров госуслуг/ГКУ ИС." 12345678: "Редактируемое показание принято к расчёту." ' + """ + pass + + +class EmpHugeValueException(EmpServerException): + """ + Не удалось передать показания за февраль по счётчикам 123456: + "Не допускается внесение данных, в несколько раз превышающих нормативы водопотребления, + установленные Правительством Москвы." (code:3454) + """ + pass + +class EmpValueLessException(EmpServerException): + """ + Не удалось передать показания за февраль по счётчикам 123456: + "Вносимое показание меньше предыдущего. Проверьте корректность вносимого показания. + В случае, если передаваемые данные корректны, Вам следует обратиться в + Центр госуслуг/ГКУ ИС района для уточнения причин ошибки." (code:3454) + """ + 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', 'okhttp/3.8.1') + self.dev_app_version = kwargs.get('dev_app_version') + self.dev_user_agent = kwargs.get('dev_user_agent') + + self.post_request_data = { + 'info': { + 'guid': self.guid, + 'user_agent': self.dev_user_agent, + 'app_version': self.dev_app_version + }, + 'auth': { + 'session_id': self.session_id + } + } + + + # 3.8.1.216(108) + #self.dev_mobile = kwargs.get('dev_mobile') + #self.info_field = { + # 'app_version': self.dev_app_version, + # 'guid': self.guid, + # 'mobile': self.dev_mobile, + # 'session_id': None, + # 'user_agent': self.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']) + + code = answer['errorCode'] + msg = answer['errorMessage'] if 'errorMessage' in answer and answer['errorMessage'] else '' + + if code == 401: + raise AuthException('Ошибка авторизации') + + if code == 3454: + if u'показание принято к расчёту' in msg: + raise EmpAlreadySendException(msg, code) + elif u'Не допускается внесение данных, в несколько раз превышающих нормативы водопотребления' in msg: + raise EmpHugeValueException(msg, code) + elif u'Вносимое показание меньше предыдущего' in msg: + raise EmpValueLessException(msg, code) + elif u'Истёк срок поверки прибора учёта' in msg: + raise EmpCounterNotVerifiedException(msg, code) + + if code != 0: + raise EmpServerException(msg, code) + + def is_active(self): + """ + :return: True если уже залогинился + """ + return self.session_id is not None + + def set_session(self, session_id): + self.session_id = session_id + self.post_request_data['auth']['session_id'] = self.session_id + self.post_request_data['info']['session_id'] = self.session_id + + 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, + #'mobile': self.dev_mobile # 3.8.1.216(108) + }, + 'auth': { + 'login': telephone, + 'password': pwd, + 'guid': self.guid + } + } + + ret = self.session.post(API_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'] + self.post_request_data['auth']['session_id'] = self.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(API_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(API_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 address_search(self, pattern, limit=100): + """ + :param pattern: строка шаблон для поиска + :param limit: сколько ответов выводить + :return: + { + "address": "", + "description": "", + "district": "", + "fullMatch": False, + "unad": 1, + "unum": 123456 + } + """ + assert self.session_id + wheaders = deepcopy(self.headers) + wheaders.update({'Content-Type': 'application/json; charset=UTF-8'}) + + wcrequest = deepcopy(self.post_request_data) + wcrequest.update({ + 'limit': limit, + 'pattern': pattern, + }) + + ret = self.session.post(API_V1_1 + '/flat/addressSearch', + 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 flat_delete(self, flat_id): + """ + Удалить квартиру + :param flat_id: unicode string from flat_response + response[0]['flat_id'] + :return: Null + """ + + assert self.session_id + wheaders = deepcopy(self.headers) + wheaders.update({ + 'X-Clears-tags': 'WIDGETS,EPD,ELECTRO_COUNTERS,WATER_COUNTERS,APARTMENT, EPD_WIDGET,ACCRUALS_WIDGET', + 'Content-Type': 'application/json; charset=UTF-8' }) + + wcrequest = deepcopy(self.post_request_data) + wcrequest.update({ + 'flat_id': flat_id + }) + + ret = self.session.post(API_V1_0 + '/flat/delete', + 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 flat_add(self, name, unom, unad, address, flat_number, paycode): + """ + Добавление квартиры. Перед добавлением надо узнать unom + :param name: имя квартиры. любое + :param unom: INT из запроса addressSearch + :param unad: INT из запроса addressSearch + :param address: Любой + :param flat_number: Номер квартиры. Любой. Но для отправки показаний нужен точный. + :param paycode: Код плательщика + :param electro_account: Номер счета Мосэнергосбыта + :param electro_device: Серийный номер счетчика. Если заполнен, должен и номер Мосэнергосбыта быть заполнен. + :return: + + { + "flat_id":"23611975", + "name":"", + "address":"", + "flat_number":"111", + "unom":"3802879", + "unad":"1", + "paycode":"1234567890", + "electro_account":"", + "electro_device":"", + "intercom":"", + "floor":"", + "entrance_number":"", + "alias":"", + "epd":"1234567890", + "flat":"111", + "building":"", + "street": равно address + } + """ + assert self.session_id + wheaders = deepcopy(self.headers) + wheaders.update({ + 'X-Clears-tags': 'WIDGETS,EPD,ELECTRO_COUNTERS,WATER_COUNTERS,APARTMENT, EPD_WIDGET,ACCRUALS_WIDGET', + 'Content-Type': 'application/json; charset=UTF-8' + }) + + wcrequest = deepcopy(self.post_request_data) + wcrequest.update({ + 'a': 0, + 'address': address, + 'can_update': False, + 'flat_number': flat_number, + 'name': name, + 'paycode': paycode, + 'unad': unad, + 'unom': unom + }) + + ret = self.session.post(API_V1_0 + '/flat/add', + 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_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 = deepcopy(self.post_request_data) + wcrequest.update({ + 'flat_id': flat_id, + 'is_widget': False + }) + + ret = self.session.post(API_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 = deepcopy(self.post_request_data) + wcrequest.update({ + 'flat_id': flat_id, + 'counters_data': counters_data + }) + + ret = self.session.post(API_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 = deepcopy(self.post_request_data) + wcrequest.update({ + 'flat_id': flat_id, + 'is_widget': False, + }) + + ret = self.session.post(API_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 = deepcopy(self.post_request_data) + wcrequest.update({ + 'flat_id': flat_id, + 'counters_data': counters_data + }) + + ret = self.session.post(API_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, begin_period, end_period): + """ + :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: array of + [{ + "uin": "234234234234234234234234", + "insurance_amount": null, + "period": "01.12.2021", + "epd_type": "REGULAR", + "payment_amount": "3316,73", + "payment_date": "01.12.2021", + "payment_status": "PAID", + "initiator": "MFC", + "create_date": "11.12.2021 19:34", + "penalty_amount": "0,00", + "amount": "3316,73", + "amount_with_insurance": null + }] + """ + 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 = deepcopy(self.post_request_data) + wcrequest.update({ + 'flat_id': flat_id, + 'begin_period': begin_period, + 'end_period': end_period + }) + + ret = self.session.post(API_V1_2 + '/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_eepd(self, flat_id, period, epd_type='current', rid=None): + """ + Запросить электронный ЕПД (pdf). Первый запрос возвращает rid и документ начинает готовиться. + Последующие запросы будут либо пустые, либо содержать ссылку на PDF + расшифровку полей. + + :param flat_id: unicode string from flat_response + response[0]['flat_id'] + :param period: unicode string represents date in 27.09.2018 format + :param epd_type: current + :param rid: Если None, то это первый запрос. В ответе будет rid, который нужно добавить в последующие запросы, + пока документ готовится. + :return: + Первый ответ и последующие , пока не готов документ + { + "rid": GUID, + } + + { + "pdf": Ссылка на pdf, + "title": "Платежный документ", + "sections": [ + { + "title": "Начисления", + "elements": { + "address": "xxxx", + "epd": 123455, + "hint": "text", + "payment_elements": [ + ], + "period": "DD-месяц-YYYY", + "total_elements": [ + ] + } + }, + { + "title": "Информация", + "elements": [ + { + "title": "text", + "body": "text" + }, + { + } + ] + } + ] + } + + """ + assert self.session_id + wheaders = deepcopy(self.headers) + wheaders.update({ + 'Content-Type': 'application/json; charset=UTF-8'}) + + wcrequest = deepcopy(self.post_request_data) + wcrequest.update({ + 'flat_id': flat_id, + 'period': period, + 'type': epd_type, + }) + + if rid: + wcrequest.update({'rid': rid}) + + ret = self.session.post(API_V1_0 + '/eepd/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_eepd_wait_result(self, flat_id, period, timeout=10.0): + """ + Запросить электронный ЕПД (pdf) и дождаться результата + + :param flat_id: unicode string from flat_response + response[0]['flat_id'] + :param period: unicode string represents date in 27.09.2018 format + :param timeout: сколько ждем в секундах результата + """ + start = time.time() + + rid = None + result = None + while not result and time.time() - start < timeout: + time.sleep(2.0) + + ret = self.get_eepd(flat_id, period, 'current', rid) + if 'rid' in ret: + rid = ret['rid'] + + if 'pdf' in ret: + result = deepcopy(ret) + + return 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 = deepcopy(self.post_request_data) + wcrequest.update({ + 'sts_number': sts_number + }) + + ret = self.session.post(API_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 = deepcopy(self.post_request_data) + + ret = self.session.post(API_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 set_session(self, *args): + return self.client().set_session(*args) + + 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 flat_delete(self, *args): + return self.client().flat_delete(*args) + + def flat_add(self, *args): + return self.client().flat_add(*args) + + def address_search(self, *args): + return self.client().address_search(*args) + + # Счетчики воды + 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) + + # Сформировать pdf Единый платежный документ + def get_eepd_wait_result(self, *args): + return self.client().get_eepd_wait_result(*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 + None - когда показания не сданы более 3-х месяцев + """ + 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'].split('+')[0] # python 3.6 support https://stackoverflow.com/questions/53291250/python-3-6-datetime-strptime-returns-error-while-python-3-7-works-well + # ValueError: time data '2023-08-08+03:00' does not match format '%Y-%m-%d%z'↵" + d = datetime.strptime(checkup, '%Y-%m-%d') #'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/docker/emp_mos_api/read_watermeters.py b/appdaemon/docker/emp_mos_api/read_watermeters.py new file mode 100644 index 0000000..c805769 --- /dev/null +++ b/appdaemon/docker/emp_mos_api/read_watermeters.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +from __future__ import print_function +import argparse +from emp_mos_api import MosAPI, Water, Watercounter + + +if __name__ == "__main__": + +# mosru_token: '887033d0649e62a84f80433e823526a1' #new: 887033d0649e62a84f80433e823526a1 #old +# mosru_session: 'a6727b620c79d9b2d24953de734e641e' #new: a3d3fdb831f1a88138abc5a096a06fce #old 3d88ede29eb74bd49fbaa54050fe8b5b + + + parser = argparse.ArgumentParser(description='emp.mos.ru API') + parser.add_argument('--token', default='705408acae4c705e9f66c9837288b3dd') + parser.add_argument('--user_agent', default='okhttp/3.8.1', help='User-Agent header') + parser.add_argument('--guid', default='80C3E12-A56F-C7A5-4174-14612ED63CF2') + parser.add_argument('--dev_user_agent', default='ios', help='application user agent') + parser.add_argument('--dev_app_version', help='application version', default='4.00.3 (245)') + + parser.add_argument('--login', help='your login (phone number: 7xxxxxxxxxx)') + parser.add_argument('--pwd', help='your password') + + parser.add_argument('--flat_id', help='id квартиры на сервере. если не указывать, отправится в 1ю.', default='16384751') + + parser.add_argument('--hot', type=float, help='Hot water new value') + parser.add_argument('--cold', type=float, help='Cold water new value') + + args = parser.parse_args() + + # + api = MosAPI(token=args.token, + user_agent=args.user_agent, + guid=args.guid, + dev_user_agent=args.dev_user_agent, + dev_app_version=args.dev_app_version, + verify=True) + + try: + api.set_session('a6727b620c79d9b2d24953de734e641e') + + counters = api.get_watercounters('16384751')['counters'] + print(counters) + + new_values = [] + + hots_json = list(filter(lambda x: x['type'] == Water.HOT, counters)) + if hots_json: + hot_value = Watercounter.last_value(hots_json[0]) + if hot_value: + print('Текущее показание горячей воды: {:.2f} m3'.format(hot_value)) + else: + print('Показания горячей воды не передавались больше 3-х месяцев') + + if args.hot: + new_values.append(Watercounter.serialize_for_send(hots_json[0], args.hot)) + print('Новое показание горячей воды: {:.2f} m3'.format(args.hot)) + else: + print('Не найден счетчик горячей воды') + + # + colds_json = list(filter(lambda x: x['type'] == Water.COLD, counters)) + if colds_json: + cold_value = Watercounter.last_value(colds_json[0]) + if cold_value: + print('Текущее показание холодной воды: {:.2f} m3'.format(cold_value)) + else: + print('Показания холодной воды не передавались больше 3-х месяцев') + + if args.cold: + new_values.append(Watercounter.serialize_for_send(colds_json[0], args.cold)) + print('Новое показание холодной воды: {:.2f} m3'.format(args.cold)) + else: + print('Не найден счетчик холодной воды') + + if new_values: + api.send_watercounters(flat_id, new_values) + print('Показания отправлены на сервер') + + finally: + print('done') + #api.logout() #долго diff --git a/appdaemon/docker/emp_mos_api/requirements.txt b/appdaemon/docker/emp_mos_api/requirements.txt new file mode 100644 index 0000000..7e78f02 --- /dev/null +++ b/appdaemon/docker/emp_mos_api/requirements.txt @@ -0,0 +1,2 @@ +distro>=1.3.0 +requests>=2.19.1 \ No newline at end of file diff --git a/appdaemon/docker/emp_mos_api/setup.py b/appdaemon/docker/emp_mos_api/setup.py new file mode 100644 index 0000000..576ffc9 --- /dev/null +++ b/appdaemon/docker/emp_mos_api/setup.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Неофициальный Python клиент Единой мобильной платформы города Москвы. +""" +from __future__ import print_function +from setuptools import setup + +VERSION_MAJOR = 0 +VERSION_MINOR = 12 + +ver = '%d.%d' % (VERSION_MAJOR, VERSION_MINOR) + +if __name__ == '__main__': + """ + Создание пакета + + python setup.py sdist --formats=zip bdist_wheel # или --formats=gztar + twine upload dist/* + """ + with open('README.md', 'r') as fh: + long_description = fh.read() + + setup( + name='emp_mos_api', + version=ver, + description=__doc__.replace('\n', '').strip(), + long_description=long_description, + long_description_content_type='text/markdown', + author='Dontsov E.', + author_email='dontsovcmc@gmail.com', + url='https://github.com/dontsovcmc/emp_mos_api', + py_modules=['emp_mos_api'], + include_package_data=True, + packages=[ + 'emp_mos_api', + 'emp_mos_api.examples' + ], + classifiers=( + "Programming Language :: Python :: 2.7", + 'Programming Language :: Python :: 3', + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ), + license='MIT', + platforms=['linux2', 'win32'], + install_requires=[ + 'requests>=2.19.1' + ], + ) diff --git a/appdaemon/settings/appdaemon.yaml b/appdaemon/settings/appdaemon.yaml new file mode 100644 index 0000000..7248f7b --- /dev/null +++ b/appdaemon/settings/appdaemon.yaml @@ -0,0 +1,36 @@ +logs: + main_log: + filename: STDOUT + +appdaemon: + # threads: 10 + latitude: !secret home_latitude + longitude: !secret home_longitude + elevation: 0 + time_zone: !secret timezone + ascii_encode: False + + plugins: + HASS: + type: hass + namespace: default + ha_url: !secret ha_url + token: !secret ha_token + + MQTT: + type: mqtt + namespace: mqtt + verbose: True + client_host: !secret mqtt_ip + client_port: 1883 + client_user: !secret mqtt_user + client_password: !secret mqtt_password + client_topics: + - home/presence/# + +#http: +# url: !secret ha_url +admin: +api: +hadashboard: +mqtt: diff --git a/appdaemon/settings/apps/alice/alice_intents.py b/appdaemon/settings/apps/alice/alice_intents.py new file mode 100644 index 0000000..cbcfb93 --- /dev/null +++ b/appdaemon/settings/apps/alice/alice_intents.py @@ -0,0 +1,44 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import time + +# +# Home and away report +# Send report about home state after presence_actions was done. +# +# Args: +# entity - group of entities to send state report +# notify - notify entity to send message +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class AliceIntents(hass.Hass): + entity_id = None + + def initialize(self): + self.listen_event(self.yandex_intent, "yandex_intent") + + def yandex_intent(self, event_id, event_args, kwargs): + self.log("{} {}".format(event_id, event_args)) + text = event_args['text'] + room = event_args['room'] + alice = event_args['entity_id'] + if text == 'Как дела': + self.entity_id = alice + self.run_in(self.run_in_say, 10, command="Кто здесь в {}?".format(room)) + + def run_in_say(self, kwargs): + if 'command' not in kwargs: + return + text = kwargs['command'] + self.say(text) + + def command(self, command): + self.call_service('yandex_station/send_command', command='sendText', text=command) + + def say(self, command): + self.log('command = {}'.format(command)) + self.call_service('media_player/play_media', entity_id=self.entity_id, media_content_type='text', media_content_id=command) \ No newline at end of file diff --git a/appdaemon/settings/apps/alice/alice_intents.yaml b/appdaemon/settings/apps/alice/alice_intents.yaml new file mode 100644 index 0000000..0061c0d --- /dev/null +++ b/appdaemon/settings/apps/alice/alice_intents.yaml @@ -0,0 +1,8 @@ +################################# +# HOME PRESENSE # +################################# +alice_intents: + module: alice_intents + class: AliceIntents + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/alice/presence_action.py b/appdaemon/settings/apps/alice/presence_action.py new file mode 100644 index 0000000..ac89643 --- /dev/null +++ b/appdaemon/settings/apps/alice/presence_action.py @@ -0,0 +1,49 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import time + +# +# Home and away report +# Send report about home state after presence_actions was done. +# +# Args: +# entity - group of entities to send state report +# notify - notify entity to send message +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class PresenceAction(hass.Hass): + report_delay = 15 + def initialize(self): + if 'alice' not in self.args: + self.error("Please provide alice in config!") + return + self.listen_event(self.away_mode, "away_mode") + self.listen_event(self.return_home_mode, "return_home_mode") + + def away_mode(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.command('стоп') + + def return_home_mode(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + command = 'Добро пожаловать домой.' + self.run_in(self.run_in_say, 59, command=command) + + def run_in_say(self, kwargs): + if 'command' not in kwargs: + return + text = kwargs['command'] + self.say(text) + + def command(self, command): + self.call_service('yandex_station/send_command', command='sendText', text=command) + + def say(self, command): + self.log('command = {}'.format(command)) + self.call_service('media_player/play_media', entity_id=self.args['alice'], media_content_type='text', media_content_id=command) \ No newline at end of file diff --git a/appdaemon/settings/apps/alice/presence_action.yaml b/appdaemon/settings/apps/alice/presence_action.yaml new file mode 100644 index 0000000..20e4551 --- /dev/null +++ b/appdaemon/settings/apps/alice/presence_action.yaml @@ -0,0 +1,9 @@ +################################# +# HOME PRESENSE # +################################# +presence_action: + module: presence_action + class: PresenceAction + alice: media_player.yandex_station_m00r46000m53db + 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..ca7962f --- /dev/null +++ b/appdaemon/settings/apps/apps.yaml @@ -0,0 +1,29 @@ +################################################################# +## Global +################################################################# +global_modules: + - globals + - automation + - notify + +automation: + class: Automation + module: automation +################################# +# CUBE # +################################# +# cube: +# module: cube +# class: Cube + +################################# +# NARODMON # +################################# +# narodmon_sensor: +# module: narodmon_sensor +# class: NarodmonSensor +# entity: sensor.lux_outside +# friendly_name: 'Освещенность (Улица)' +# user: 1089 +# sensorid: 7574 +# interval: 120 \ 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/breaser_control.py b/appdaemon/settings/apps/climate/breaser_control.py new file mode 100644 index 0000000..7718a5f --- /dev/null +++ b/appdaemon/settings/apps/climate/breaser_control.py @@ -0,0 +1,277 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import datetime + +# +# Purifier controller +# Turn on and off purifier by light sensor and timers +# +# Args: +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class BreaserControl(hass.Hass): + away_fan_speed = 8 #1-14 + listen_event_handle_list = [] + mode = 'auto' + heat_start_at = '23:00:00' + heat_stop_at = '07:00:00' + + def initialize(self): + if not globals.check_args(self, ["on_time", "off_time"]): + return + on_time = self.parse_time(self.args["on_time"]) + off_time = self.parse_time(self.args["off_time"]) + + self.on_timer = self.run_daily(self.day_mode_timer_event, on_time) + self.off_timer = self.run_daily(self.night_mode_timer_event, off_time) + + if 'heat_switch' in self.args: + self.heat_on_timer = self.run_daily(self.heat_mode_on_timer_event, self.heat_start_at) + self.heat_off_timer = self.run_daily(self.heat_mode_off_timer_event, self.heat_stop_at) + self.heat_mode_by_time() + + self.listen_event_handle_list.append(self.listen_event(self.away_mode_event, "away_mode")) + self.listen_event_handle_list.append(self.listen_event(self.return_home_mode_event, "return_home_mode")) + if 'co2_sensor' in self.args: + self.listen_event_handle_list.append(self.listen_state(self.state_change, self.args['co2_sensor'])) + + def state_change(self, entity, attribute, old, new, kwargs): + self.heat_mode_by_time() + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + presence_state = self.get_state(self.args['ha_panel']) + if presence_state != "disarmed": + self.log('Nobody home, ignoring co2 changes.') + return + if new == 'unknown': + return + self.log("{} changed from {} to {}".format(attribute, old, new)) + co2_level = int(new) + #day + if self.time_is_between(self.datetime(), self.args["on_time"], self.args["off_time"]): + self.set_mode_by_co2(co2_level, False) + #night + elif not self.time_is_between(self.datetime(), self.args["on_time"], self.args["off_time"]): + self.set_mode_by_co2(co2_level, True) + +def set_mode_by_co2(self, co2_level, is_night): + self.heat_mode_by_time() + + # === Настраиваемые коэффициенты === + CFG = { + 'co2_auto_threshold': 600, # Ниже этого значения — auto_mode + 'co2_min_control': 700, # Начало плавного роста скорости + 'co2_max_control': 1200, # Максимальная скорость достигается здесь + 'speed_min': 30, # Мин. скорость вентиляции + 'speed_max': 100, # Макс. скорость вентиляции + 'night_speed_factor': 0.75, # Коэффициент снижения скорости ночью + 'transition_smoothness': 0.3, # Плавность перехода (0-1) + } + + # === Плавная функция перехода от auto к ручной скорости === + def calculate_speed(co2, cfg, is_night): + # Нормализация CO2 в диапазон [0, 1] + t = (co2 - cfg['co2_min_control']) / (cfg['co2_max_control'] - cfg['co2_min_control']) + t = max(0.0, min(1.0, t)) + + # Плавная интерполяция (smoothstep для более мягкого старта) + if cfg['transition_smoothness'] > 0: + t = t * t * (3 - 2 * t) # Cubic smoothstep + + # Базовая скорость + base_speed = cfg['speed_min'] + t * (cfg['speed_max'] - cfg['speed_min']) + + # Ночной коэффициент + day_factor = 1.0 if not is_night else cfg['night_speed_factor'] + final_speed = base_speed * day_factor + + return int(round(max(cfg['speed_min'], min(cfg['speed_max'], final_speed)))) + + # === Автоматический режим при низком CO2 === + if co2_level <= CFG['co2_auto_threshold']: + self.log('CO2 is excellent: {}. Set auto mode.'.format(co2_level)) + self.auto_mode() + return + + # === Плавный расчёт скорости === + speed = calculate_speed(co2_level, CFG, is_night) + + # Определение уровня для лога + if co2_level < CFG['co2_min_control']: + level_desc = 'good' + elif co2_level < CFG['co2_max_control'] * 0.7: + level_desc = 'moderate' + elif co2_level < CFG['co2_max_control']: + level_desc = 'high' + else: + level_desc = 'extremely high' + + period = 'Night' if is_night else 'Day' + self.log('{}, CO2 is {}: {}. Set speed to {}.'.format( + period, level_desc, co2_level, speed)) + + self.set_speed(speed) + + # def set_mode_by_co2(self, co2_level, is_night): + # self.heat_mode_by_time() + # if co2_level < 500: + # self.log('CO2 is fine: {}. Set auto mode.'.format(co2_level)) + # self.auto_mode() + # if co2_level > 500 and co2_level < 800: + # if is_night: + # self.log('CO2 is normal: {}. Set night mode'.format(co2_level)) + # self.night_mode() + # else: + # self.log('CO2 is normal: {}. Set auto mode.'.format(co2_level)) + # self.auto_mode() + # return + # if co2_level > 800 and co2_level < 900: + # if is_night: + # self.log('Night, CO2 is high: {}. Set speed to 45.'.format(co2_level)) + # self.set_speed(45) + # else: + # self.log('Day, CO2 is high: {}. Set speed to 65.'.format(co2_level)) + # self.set_speed(65) + # return + # if co2_level > 900 and co2_level < 1000: + # if is_night: + # self.log('Night, CO2 is high: {}. Set speed to 65.'.format(co2_level)) + # self.set_speed(65) + # else: + # self.log('Day, CO2 is high: {}. Set speed to 80.'.format(co2_level)) + # self.set_speed(80) + # return + # if co2_level > 1000 and co2_level < 1200: + # if is_night: + # self.log('Night, CO2 is extremely high: {}. Set speed to 80.'.format(co2_level)) + # self.set_speed(80) + # else: + # self.log('Day, CO2 is extremely high: {}. Set speed to 100.'.format(co2_level)) + # self.set_speed(100) + # return + # if co2_level > 1200: + # if is_night: + # self.log('Night, CO2 is extremely high: {}. Set speed to 100.'.format(co2_level)) + # self.set_speed(100) + # else: + # self.log('Day, CO2 is extremely high: {}. Set speed to 100.'.format(co2_level)) + # self.set_speed(100) + # return + + def set_speed(self, speed): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.log('Set purifier mode to favorite, speed: {}'.format(speed)) + self.call_service("fan/set_preset_mode", entity_id = self.args['entity'], preset_mode='Favorite') + self.call_service("fan/set_percentage", entity_id = self.args['entity'], percentage=speed) + + def return_home_mode_event(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.auto_mode() + + def away_mode_event(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.turn_on(self.args['entity']) + self.away_mode() + + def night_mode_timer_event(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + presence_state = self.get_state(self.args['ha_panel']) + if presence_state != "disarmed": + self.log('Nobody home, ignoring automations.') + return + self.night_mode() + + def day_mode_timer_event(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + presence_state = self.get_state(self.args['ha_panel']) + if presence_state != "disarmed": + self.log('Nobody home, ignoring automations.') + return + self.auto_mode() + + def away_mode(self): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.mode='away' + self.log('Set purifier mode to favorite, speed: {}'.format(self.away_fan_speed)) + if 'entity' in self.args: + self.call_service("fan/set_preset_mode", entity_id = self.args['entity'], preset_mode='Favorite') + self.call_service("fan/set_percentage", entity_id = self.args['entity'], percentage=100) + + def auto_mode(self): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.mode='auto' + self.log('Set purifier mode to auto.') + if 'entity' in self.args: + self.call_service("fan/set_preset_mode", entity_id = self.args['entity'], preset_mode='Auto') + + def night_mode(self): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.mode='night' + self.log('Set purifier mode to silent.') + if 'entity' in self.args: + self.call_service("fan/set_preset_mode", entity_id = self.args['entity'], preset_mode='Sleep') + + #heating control + def heat_mode_by_time(self): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint_heat']): + return + if self.time_is_between(self.datetime(), self.heat_stop_at, self.heat_start_at): + self.log('Expensive tariff. Set heat off.') + self.heat_mode_off_timer_event(None) + else: + presence_state = self.get_state(self.args['ha_panel']) + if presence_state != "disarmed": + self.log('Nobody home, ignoring automations.') + return + self.log('Cheap tariff. Set heat on.') + self.heat_mode_on_timer_event(None) + + def heat_mode_on_timer_event(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint_heat']): + return + presence_state = self.get_state(self.args['ha_panel']) + if presence_state != "disarmed": + self.log('Nobody home, ignoring automations.') + return + self.turn_on(self.args['heat_switch']) + + def heat_mode_off_timer_event(self, kwargs): + self.turn_off(self.args['heat_switch']) + + + def time_is_between( + hass, target_dt: datetime, start_time: str, + end_time: str) -> bool: + """Generalization of AppDaemon's now_is_between method.""" + start_time_dt = hass.parse_time(start_time) # type: datetime + end_time_dt = hass.parse_time(end_time) # type: datetime + start_dt = target_dt.replace( + hour=start_time_dt.hour, + minute=start_time_dt.minute, + second=start_time_dt.second) + end_dt = target_dt.replace( + hour=end_time_dt.hour, + minute=end_time_dt.minute, + second=end_time_dt.second) + + if end_dt < start_dt: + # Spans midnight + if target_dt < start_dt and target_dt < end_dt: + target_dt = target_dt + timedelta(days=1) + end_dt = end_dt + timedelta(days=1) + return start_dt <= target_dt <= end_dt \ No newline at end of file diff --git a/appdaemon/settings/apps/climate/breaser_control.yaml b/appdaemon/settings/apps/climate/breaser_control.yaml new file mode 100644 index 0000000..c180cbe --- /dev/null +++ b/appdaemon/settings/apps/climate/breaser_control.yaml @@ -0,0 +1,14 @@ +breaser_control: + module: breaser_control + class: BreaserControl + on_time: "09:00:00" + off_time: "22:00:00" + entity: fan.ventiliatsionnaia_sistema + ha_panel: alarm_control_panel.ha_alarm + co2_sensor: sensor.co2_bedroom_co2 + heat_switch: switch.ventiliatsionnaia_sistema_auxiliary_heat + constraint: input_boolean.vent_control + constraint_heat: input_boolean.vent_control_heat + global_dependencies: + - globals + \ No newline at end of file diff --git a/appdaemon/settings/apps/climate/climate_control.py_ b/appdaemon/settings/apps/climate/climate_control.py_ new file mode 100644 index 0000000..5cc61f2 --- /dev/null +++ b/appdaemon/settings/apps/climate/climate_control.py_ @@ -0,0 +1,212 @@ +import appdaemon.plugins.hass.hassapi as hass +from datetime import time, datetime, timedelta + +class ClimateControl(hass.Hass): + def initialize(self): + # Настройки из YAML + self.thermostat = self.args.get("thermostat", "climate.virtual_thermostat") + self.ac = self.args.get("ac", "climate.ac_bedroom") + self.radiators = self.args.get("radiators", ["climate.radiator_bedroom", "climate.radiator_bedroom_balcony"]) + self.radiator_enabled = self.args.get("radiator_enabled", "input_boolean.radiator_enabled") + self.presence_sensors = self.args.get("presence_sensors", None) + self.no_presence_delay = int(self.args.get("no_presence_delay", 1800)) # секунд + self.window_sensors = self.args.get("window_sensors", None) + self.window_open_delay = int(self.args.get("window_open_delay", 300)) # секунд + self.alarm = self.args.get("alarm", "alarm_control_panel.ha_alarm") + + self.listen_state(self.thermostat_changed, self.thermostat) + self.listen_state(self.thermostat_temp_changed, self.thermostat, attribute="temperature") + self.listen_state(self.thermostat_mode_changed, self.thermostat, attribute="hvac_mode") + if self.presence_sensors is not None: + self.listen_state(self.presence_changed, self.presence_sensors) + if self.window_sensors is not None: + self.listen_state(self.window_changed, self.window_sensors) + self.listen_state(self.radiator_enabled_changed, self.radiator_enabled) + self.listen_state(self.alarm_changed, self.alarm) + self.run_every(self.periodic_check, self.datetime() + timedelta(seconds=5), 60) + + self.no_presence_handle = None + self.window_open_handle = None + self.last_presence = self.datetime() + self.last_window_open = None + self.updating_thermostat = False + self.listen_state(self.ac_changed, self.ac) + self.listen_state(self.ac_temp_changed, self.ac, attribute="temperature") + self.auto_off_reason = None + + def thermostat_changed(self, entity, attribute, old, new, kwargs): + self.log(f"Термостат изменён: {new}") + self.control_climate() + + def thermostat_temp_changed(self, entity, attribute, old, new, kwargs): + self.log(f"Изменилась температура термостата: {old} → {new}") + self.control_climate() + + def thermostat_mode_changed(self, entity, attribute, old, new, kwargs): + self.log(f"Изменился режим термостата: {old} → {new}") + self.control_climate() + + def presence_changed(self, entity, attribute, old, new, kwargs): + if new == "on": + self.last_presence = self.datetime() + if self.no_presence_handle: + self.cancel_timer(self.no_presence_handle) + self.no_presence_handle = None + self.control_climate() + else: + if not self.no_presence_handle: + self.no_presence_handle = self.run_in(self.no_presence_timeout, self.no_presence_delay) + + def no_presence_timeout(self, kwargs): + self.log("В комнате долго нет людей — отключаю климат.") + self.turn_off_all(reason="no_presence") + + def window_changed(self, entity, attribute, old, new, kwargs): + if new == "on": + if not self.window_open_handle: + self.window_open_handle = self.run_in(self.window_open_timeout, self.window_open_delay) + else: + if self.window_open_handle: + self.cancel_timer(self.window_open_handle) + self.window_open_handle = None + self.auto_off_reason = None # сбросить флаг после закрытия окна + self.control_climate() + + def window_open_timeout(self, kwargs): + self.log("Окно открыто более 5 минут — отключаю климат.") + self.turn_off_all(reason="window") + + def radiator_enabled_changed(self, entity, attribute, old, new, kwargs): + self.control_climate() + + def alarm_changed(self, entity, attribute, old, new, kwargs): + self.control_climate() + + def periodic_check(self, kwargs): + self.control_climate() + + def control_climate(self): + self.log(f"Проверка климата: alarm={self.get_state(self.alarm)}, окна={self.window_sensors}, присутствие={self.presence_sensors}") + mode = self.get_state(self.thermostat, attribute="hvac_mode") + if mode is None: + mode = self.get_state(self.thermostat) + target_temp = self.get_state(self.thermostat, attribute="temperature") + self.log(f"Режим термостата (hvac_mode/state): {mode}, температура: {target_temp}") + # Проверка сигнализации + if self.get_state(self.alarm) != "disarmed": + self.log("Сигнализация активна — автоматика отключена.") + self.turn_off_all() + return + + # Проверка окон + if self.window_sensors: + if any(self.get_state(s) == "on" for s in self.window_sensors): + self.log("Окно открыто — автоматика отключена.") + self.turn_off_all() + return + + # Проверка присутствия + if self.presence_sensors: + if all(self.get_state(s) == "off" for s in self.presence_sensors): + if (self.datetime() - self.last_presence).total_seconds() > self.no_presence_delay: + self.log("Нет людей — автоматика отключена.") + self.turn_off_all() + return + + # Проверка режима термостата + if mode == "off": + self.log("Термостат в off — автоматика отключена.") + self.turn_off_all() + return + elif mode == "cool": + self.log("Режим охлаждения.") + self.set_ac_mode("cool", target_temp) + self.set_radiators_off() + return + elif mode == "heat": + # Определяем сезон + radiator_on = self.get_state(self.radiator_enabled) == "on" + now = datetime.now().time() + self.log(f"radiator_enabled: {radiator_on}, время: {now}") + if not radiator_on: + self.log("Лето — только сплит-система.") + self.set_ac_mode("heat", target_temp) + self.set_radiators_off() + else: + if time(23,0) <= now or now < time(7,0): + self.log("Зима, ночь — только сплит-система в режиме обогрева.") + self.set_ac_mode("heat", target_temp) + self.set_radiators_off() + else: + self.log("Зима, день — только радиаторы.") + self.set_ac_off() + self.set_radiators_on(target_temp) + + def set_ac_mode(self, mode, temp): + current_mode = self.get_state(self.ac) + current_temp = self.get_state(self.ac, attribute="temperature") + + current_temp_f = int(current_temp) + temp_f = int(temp) + + self.log(f"Текущий режим AC: {current_mode}, температура: {current_temp_f} (требуется: {temp_f})") + if current_mode == mode and isinstance(current_temp_f, int) and isinstance(temp_f, int) and abs(current_temp_f - temp_f) < 0.1: + self.log(f"AC {self.ac}: режим {mode}, температура {temp} — уже установлены, не отправляю команду.") + return + self.updating_thermostat = True + self.call_service("climate/set_temperature", entity_id=self.ac, temperature=temp, hvac_mode=mode) + self.log(f"AC {self.ac}: режим {mode}, температура {temp}") + self.updating_thermostat = False + + def set_ac_off(self): + current_mode = self.get_state(self.ac) + if current_mode == "off": + self.log(f"AC {self.ac}: уже выключен, не отправляю команду.") + return + self.updating_thermostat = True + self.call_service("climate/set_hvac_mode", entity_id=self.ac, hvac_mode="off") + self.log(f"AC {self.ac}: выключен") + self.updating_thermostat = False + + def set_radiators_on(self, temp): + for r in self.radiators: + self.updating_thermostat = True + self.call_service("climate/set_hvac_mode", entity_id=r, hvac_mode="heat") + self.call_service("climate/set_temperature", entity_id=r, temperature=temp) + self.log(f"Радиатор {r}: режим heat, температура {temp}") + self.updating_thermostat = False + + def set_radiators_off(self): + for r in self.radiators: + self.updating_thermostat = True + self.call_service("climate/set_hvac_mode", entity_id=r, hvac_mode="off") + self.log(f"Радиатор {r}: выключен") + self.updating_thermostat = False + + def turn_off_all(self, reason=None): + self.auto_off_reason = reason + self.set_ac_off() + self.set_radiators_off() + + def ac_changed(self, entity, attribute, old, new, kwargs): + if self.updating_thermostat: + return + if self.auto_off_reason: + self.log(f"AC выключен автоматикой ({self.auto_off_reason}), не синхронизирую термостат в off") + return + mode = self.get_state(self.ac) + temp = self.get_state(self.ac, attribute="temperature") + self.log(f"AC изменён вручную: режим {mode}, температура {temp}") + # Синхронизируем виртуальный термостат + self.updating_thermostat = True + self.call_service("climate/set_hvac_mode", entity_id=self.thermostat, hvac_mode=mode) + self.call_service("climate/set_temperature", entity_id=self.thermostat, temperature=temp) + self.updating_thermostat = False + + def ac_temp_changed(self, entity, attribute, old, new, kwargs): + if self.updating_thermostat: + return + self.log(f"AC температура изменена вручную: {old} → {new}") + self.updating_thermostat = True + self.call_service("climate/set_temperature", entity_id=self.thermostat, temperature=new) + self.updating_thermostat = False diff --git a/appdaemon/settings/apps/climate/climate_control.yaml_ b/appdaemon/settings/apps/climate/climate_control.yaml_ new file mode 100644 index 0000000..b63665b --- /dev/null +++ b/appdaemon/settings/apps/climate/climate_control.yaml_ @@ -0,0 +1,17 @@ +climate_control: + module: climate_control + class: ClimateControl + thermostat: climate.bedroom_climate + ac: climate.ac_bedroom + radiators: + - climate.radiator_bedroom + - climate.radiator_bedroom_balcony + radiator_enabled: input_boolean.radiator_enabled + #presence_sensors: + # - binary_sensor.presence_bedroom + no_presence_delay: 1800 # секунд без присутствия для отключения + window_sensors: + - binary_sensor.door_bedroom_big_window_contact + - binary_sensor.door_balcony_contact + window_open_delay: 300 # секунд открытого окна для отключения + alarm: alarm_control_panel.ha_alarm \ No newline at end of file diff --git a/appdaemon/settings/apps/climate/cover.py b/appdaemon/settings/apps/climate/cover.py new file mode 100644 index 0000000..b86f9d6 --- /dev/null +++ b/appdaemon/settings/apps/climate/cover.py @@ -0,0 +1,83 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# Cover control by presence events +# Listen for presence events and change light state +# +# Args: +# entities - entities to turn on and off when presence is changed. +# Release Notes +# +# Version 1.0: +# Initial Version + +class CoverByPresence(hass.Hass): + def initialize(self): + if 'covers' not in self.args: + self.error("Please provide covers in config!") + return + self.listen_event(self.away_mode, "away_mode") + self.listen_event(self.return_home_mode, "return_home_mode") + + sunrise_offset = int(self.args.get('sunrise_offset', '0')) + self.run_at_sunrise(self.run_at_sunrise_func, offset=sunrise_offset) + sunset_offset = int(self.args.get('sunset_offset', '0')) + self.run_at_sunset(self.run_at_sunset_func, offset=sunset_offset) + + def run_at_sunrise_func(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + self.log('execution is disabled.') + return + + if 'guest_mode' in self.args and self.constrain_input_boolean(self.args['guest_mode']): + self.log('guest mode, no run at sunrise.') + return + + if 'open_at_sunrise' not in self.args: + self.log('no covers to open at sunrise.') + return + + for entity_id in self.args["open_at_sunrise"]: + self.log('opening cover {} at sunrise'.format(entity_id)) + self.turn_on_custom(entity_id) + + def run_at_sunset_func(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + self.log('execution is disabled.') + return + if 'close_at_sunset' not in self.args: + self.log('no covers to close at sunset.') + return + for entity_id in self.args["close_at_sunset"]: + self.log('closing cover {} at sunset'.format(entity_id)) + self.turn_off_custom(entity_id) + + def away_mode(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + self.log('execution is disabled.') + return + for entity_id in self.args["covers"]: + self.turn_off_custom(entity_id) + + def return_home_mode(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + self.log('execution is disabled.') + return + if self.sun_down(): + self.log('Sun is down, skip opening covers.') + return + entities = self.args["covers"] + for entity_id in entities: + self.turn_on_custom(entity_id) + + def turn_on_custom(self, entity_id): + if 'cover.' in entity_id: + self.call_service("cover/open_cover", entity_id=entity_id) + return + self.turn_on(entity_id) + + def turn_off_custom(self, entity_id): + if 'cover.' in entity_id: + self.call_service("cover/close_cover", entity_id=entity_id) + return + self.turn_off(entity_id) diff --git a/appdaemon/settings/apps/climate/cover.yaml b/appdaemon/settings/apps/climate/cover.yaml new file mode 100644 index 0000000..fb8377e --- /dev/null +++ b/appdaemon/settings/apps/climate/cover.yaml @@ -0,0 +1,29 @@ +################################# +# HOME PRESENSE # +################################# +cover_action: + module: cover + class: CoverByPresence + sunrise_offset: 0 + sunset_offset: 0 + guest_mode: input_boolean.guest_mode + covers: + - cover.curtain_bedroom + - cover.curtain_bedroom_balcony + #- cover.curtain_bedroom_bobbinet_balcony + - cover.curtain_kitchen + - cover.curtain_mainroom + #- cover.curtain_mainroom_bobbinet + close_at_sunset: + - cover.curtain_bedroom + - cover.curtain_bedroom_balcony + - cover.curtain_bedroom_bobbinet_balcony + - cover.curtain_kitchen + - cover.curtain_mainroom + - cover.curtain_mainroom_bobbinet + open_at_sunrise: + - cover.curtain_kitchen + - cover.curtain_mainroom + global_dependencies: + - globals + diff --git a/appdaemon/settings/apps/climate/mirror_heater_control.py b/appdaemon/settings/apps/climate/mirror_heater_control.py new file mode 100644 index 0000000..7046d9a --- /dev/null +++ b/appdaemon/settings/apps/climate/mirror_heater_control.py @@ -0,0 +1,73 @@ +import hassapi as hass +import datetime +import adbase as ad + + +# +# entity: sensor.temperature_bath_humidity +# control_entity: switch.switch_mirror_bath_heater +# constraint: input_boolean.bath_mirror_heat +# threshold: 75 + + +class MirrorHeater(hass.Hass): + timer = None + entity = None + threshold = 75 + blocked = False + + def initialize(self): + self.log('starting initialize') + + sensor = self.args['entity'] + self.log('listen [{0}] state'.format(sensor)) + self.listen_state(self.humidity_changed, sensor, duration=10) + + if 'threshold' in self.args: + self.threshold = self.args['threshold'] + self.log('setting threshold to: {}'.format(self.threshold)) + + def humidity_changed(self, entity, attribute, old_state, new_state, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + self.log('[{0}] [humidity changed] : {1}, isnumeric: {2}, type: {3}'.format(entity, new_state, new_state.isnumeric(), type(new_state))) + if float(new_state) > self.threshold: + self.log('Control Entity On and running timer...') + self.control_entity_on() + self.run_timer() + else: + self.log('Control Entity Off and stopping timer...') + self.control_entity_off(None) + +############ TIMER ######################## + def run_timer(self): + if self.timer != None: + self.stop_timer() + self.timer = self.run_in(self.control_entity_off, self.args['timeout']) + + def control_entity_on(self): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + for entity in self.args['control_entities']: + state = self.get_state(entity) + if state == 'off': + self.turn_on(entity) + self.log("Turn on ({}).".format(entity)) + + def control_entity_off(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + for entity in self.args['control_entities']: + state = self.get_state(entity) + if state == 'on': + self.turn_off(entity) + self.log("Turn off ({}).".format(entity)) + self.stop_timer() + + def stop_timer(self): + if (self.timer == None): + return + self.log('stopping timer.') + self.cancel_timer(self.timer) + self.timer = None \ No newline at end of file diff --git a/appdaemon/settings/apps/climate/mirror_heater_control.yaml b/appdaemon/settings/apps/climate/mirror_heater_control.yaml new file mode 100644 index 0000000..7053407 --- /dev/null +++ b/appdaemon/settings/apps/climate/mirror_heater_control.yaml @@ -0,0 +1,39 @@ +################################# +# Контроль подогрева зеркал # +################################# +mirror_heater_control: + # Название класса + class: MirrorHeater + module: mirror_heater_control + + entity: sensor.temperature_bath_humidity + control_entities: + - switch.switch_mirror_bath_heater + + constraint: input_boolean.bath_mirror_heat + threshold: 75 + timeout: 1800 #3600=1hr + + global_dependencies: + - globals + + +# ################################# +# # Контроль подогрева зеркал # +# ################################# +# mirror_heater_control_shower: +# # Название класса +# class: MirrorHeater +# module: mirror_heater_control + +# entity: sensor.temperature_shower_humidity +# control_entities: +# - switch.shower_mirror +# - switch.switch_mirror_shower_heater + +# constraint: input_boolean.shower_mirror_heat +# threshold: 75 +# timeout: 3600 #3600=1hr + +# global_dependencies: +# - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/climate/trv_updater.py b/appdaemon/settings/apps/climate/trv_updater.py new file mode 100644 index 0000000..5a844f1 --- /dev/null +++ b/appdaemon/settings/apps/climate/trv_updater.py @@ -0,0 +1,46 @@ +import hassapi as hass +import datetime +import adbase as ad + + +class TRVUpdater(hass.Hass): + entities = [] + + def initialize(self): + self.log('starting initialize') + self.entities = self.args['entities'] + + for sensor in self.entities.keys(): + self.log('listen [{0}] state'.format(sensor)) + self.listen_state(self.temperature_changed, sensor, duration=10) + current_temperature = self.get_state(sensor) + self.temperature_changed(sensor, 'temperature', '20.0', current_temperature, None) + + def temperature_changed(self, entity, attribute, old_state, new_state, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + self.log('Отправка температуры отключена в связи с отключением отопления.') + return + + self.log('[{0}] [temperature changed] : {1}, isnumeric: {2}, type: {3}'.format(entity, new_state, new_state.isnumeric(), type(new_state))) + topic = self.entities[entity].get('topic_prefix', None) + if topic is not None: + sensor_topic = topic.replace('/sensor_temp', '') + sensor_payload = '{"sensor":"external"}' + self.log('[mqtt publish] topic: "{0}" value: {1}'.format(sensor_topic, sensor_payload)) + self.call_service("mqtt/publish", topic=sensor_topic, payload=sensor_payload) + + temperature_payload = '{"external_temperature_input":"' + new_state + '"}' + self.log('[mqtt publish] topic: "{0}" value: {1}'.format(sensor_topic, temperature_payload)) + self.call_service("mqtt/publish", topic=sensor_topic, payload=temperature_payload) + + topics = self.entities[entity].get('topic_prefixes', None) + if topics is not None: + for topic in topics: + sensor_topic = topic.replace('/sensor_temp', '') + sensor_payload = '{"sensor":"external"}' + self.log('[mqtt publish] topic: "{0}" value: {1}'.format(sensor_topic, sensor_payload)) + self.call_service("mqtt/publish", topic=sensor_topic, payload=sensor_payload) + + temperature_payload = '{"external_temperature_input":' + new_state + '}' + self.log('[mqtt publish] topic: "{0}" value: {1}'.format(sensor_topic, temperature_payload)) + self.call_service("mqtt/publish", topic=sensor_topic, payload=temperature_payload) diff --git a/appdaemon/settings/apps/climate/trv_updater.yaml b/appdaemon/settings/apps/climate/trv_updater.yaml new file mode 100644 index 0000000..7298830 --- /dev/null +++ b/appdaemon/settings/apps/climate/trv_updater.yaml @@ -0,0 +1,22 @@ +################################# +# Обновление температуры на термоголовках # +################################# +trv_updater: + # Название класса + class: TRVUpdater + module: trv_updater + constraint: input_boolean.radiator_enabled + + entities: + 'sensor.temperature_mainroom_temperature': + topic_prefix: z2m-tcp/radiator_mainroom/set/sensor_temp + 'sensor.temperature_bedroom_temperature': + topic_prefixes: + - z2m-tcp/radiator_bedroom/set/sensor_temp + - z2m-tcp/radiator_bedroom_balcony/set/sensor_temp + 'sensor.temperature_kitchen_temperature': + topic_prefix: z2m-tcp/radiator_kitchen/set/sensor_temp + 'sensor.temperature_childroom_temperature': + topic_prefix: z2m-tcp/radiator_childroom/set/sensor_temp + global_dependencies: + - globals \ 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..8854896 --- /dev/null +++ b/appdaemon/settings/apps/doorbells/doorbell.py @@ -0,0 +1,43 @@ +import appdaemon.plugins.hass.hassapi as hass +import os + +# +# App to send notification when doorbell is ringing +# +# Args: +# +# notify = notification platform to send notifications to +# ringtone (optional) = ringtone to play +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Doorbell(hass.Hass): + def initialize(self): + if "notify" not in self.args: + self.error("Please provide notify, sensor, camera in config!") + return + self.listen_event(self.ha_event, "doorbell_call") + + def ha_event(self, event_name, data, kwargs): + self.log('{}:{}'.format(event_name, data)) + + self.notify("Звонок в дверь!!!", name = self.args['notify']) + if 'doorbell_pic' in data: + extra_data = {'photo': {'url':data['doorbell_pic']}} + self.notify("Фото звонившего", name = self.args['notify'], data=extra_data) + + self.say('Звонок в дверь') + + def command(self, command): + self.call_service('yandex_station/send_command', command='sendText', text=command) + + def say(self, command): + if 'alices' in self.args: + for alice in self.args['alices']: + self.log('command = {}'.format(command)) + self.call_service('media_player/play_media', entity_id=alice, media_content_type='text', media_content_id=command, extra={'volume_level': 0.8}) \ No newline at end of file diff --git a/appdaemon/settings/apps/doorbells/doorbell.yaml b/appdaemon/settings/apps/doorbells/doorbell.yaml new file mode 100644 index 0000000..a74d240 --- /dev/null +++ b/appdaemon/settings/apps/doorbells/doorbell.yaml @@ -0,0 +1,17 @@ +################################# +# INTERCOM # +################################# +doorbell: + module: doorbell + class: Doorbell + sensor: binary_sensor.doorbell_ring + camera: camera.domofon + notify: telegram + alices: + - media_player.yandex_station_m00r46000m53db + - media_player.yandex_station_m00fhe2003seqr + - media_player.yandex_station_ly00000000000008674600007209d3f5 + - media_player.yandex_station_ff98f029ab48468d28c557be + - media_player.yandex_station_u003jc100phn9k + global_dependencies: + - globals \ 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..ebc8299 --- /dev/null +++ b/appdaemon/settings/apps/globals.py @@ -0,0 +1,97 @@ +import importlib +import subprocess +from typing import Any, Tuple, Union + +# +# Global module +# contain global functions +# +# Args: +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +"""Define various constants.""" +CONF_PEOPLE = 'people' + +def most_common(the_list: list) -> Any: + """Return the most common element in a list.""" + return max(set(the_list), key=the_list.count) + +def check_properties(self, args): + missing = [] + for arg in args: + if arg not in self.properties: + missing.append(arg) + if len(missing) > 0: + self.error("Please provide {} in properties!".format(missing)) + return False + return True + +def check_args(self, args): + missing = [] + for arg in args: + if arg not in self.args: + missing.append(arg) + if len(missing) > 0: + self.error("Please provide {} in config!".format(missing)) + return False + return True + +def check_constaint(self, name='constraint'): + self.log('checking constaint: {}'.format(name)) + if name not in self.args: + self.log('script is not constrained') + return True; + constaint_input = self.args[name] + constaint_state = self.get_state(constaint_input) + +def get_group_entities(self, group): + attributes = self.get_state(group, attribute = "all") + if (attributes == None): + self.log('Warning: Possible invalid group: {}'.format(group)) + return [] + if 'attributes' in attributes: + attributes = attributes['attributes'] + + #group + if "entity_id" in attributes: + return attributes["entity_id"] + + #simple item + return [group] + +def turn_on(self, entity_id, state): + if entity_id.startswith("light"): + self.call_service("light/turn_off", entity_id = entity_id, transition = 1, brightness = 255) + else: + self.turn_off(entity_id, state) + +def turn_off(self, entity_id, state): + if entity_id.startswith("light"): + self.call_service("light/turn_off", entity_id = entity_id, transition = 1) + else: + self.turn_off(entity_id, state) + +def notification(self, to, message): + self.notify(message, name = "telegram") + + +def get_arg(args, key): + key = args[key] + return key + + +def get_arg_list(args, key): + arg_list = [] + if isinstance(args[key], list): + arg = args[key] + else: + arg = (args[key]).split(",") + for key in arg: + arg_list.append(key) + return arg_list \ No newline at end of file diff --git a/appdaemon/settings/apps/groups/groups.py b/appdaemon/settings/apps/groups/groups.py new file mode 100644 index 0000000..0522d57 --- /dev/null +++ b/appdaemon/settings/apps/groups/groups.py @@ -0,0 +1,79 @@ +import appdaemon.plugins.hass.hassapi as hass +import re +# +# This app will create groups in runtime by user defined params +# Based on Rene Tode groups plugin ( hass@reot.org ) +# +# Args: +# device_type: sensor # or any devicetype +# entity_part: "any_part" +# entities: # list of entities +# - sensor.any_entity +# hidden: False # or True +# view: True # or False +# assumed_state: False # or True +# friendly_name: Your Friendly Name +# nested_view: True # or False +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Groups(hass.Hass): + def initialize(self): + self.run_in(self.create_groups_timer, 10) + + def create_groups_timer(self, kwargs): + entitylist = [] + + expand_group = self.args.get("expand_group", False) + device_type = self.args.get("device_type", None) + all_entities = self.get_state(device_type) + + if "entities" in self.args: + for entity in self.args["entities"]: + found = False + for real_entity in all_entities: + if real_entity.lower() == entity.lower(): + self.append_entity(entitylist, entity, expand_group) + found = True + if not found: + for real_entity in all_entities: + if re.match(entity.lower(), real_entity.lower()): + self.append_entity(entitylist, real_entity, expand_group) + + if 'entity_part' in self.args: + for entity in all_entities: + if self.args["entity_part"] in entity and entity.lower() not in entitylist: + self.append_entity(entitylist, entity, expand_group) + + hidden = self.args.get("hidden", False) + view = self.args.get("view", False) + assumed_state = self.args.get("assumed_state",False) + friendly_name = self.args.get("friendly_name","") + name = "group." + self.name + + if self.args.get('orderby', None) == 'friendly_name': + entitylist.sort(key=self.sortByFriendlyName) + + self.log("Creating group {} with entities: {}".format(name, entitylist)) + self.set_state(name,state="on",attributes={"view": view,"hidden": hidden,"assumed_state": assumed_state,"friendly_name": friendly_name,"entity_id": entitylist, 'description': 'Created and updated from appdaemon ({})'.format(__name__)}) + + def sortByFriendlyName(self, entity): + return self.get_state(entity, attribute='friendly_name') + + def append_entity(self, entitylist, entity, expand_group): + entity = entity.lower() + if (('group.' not in entity and expand_group) or not expand_group) and entity not in entitylist: + entitylist.append(entity) + return + if expand_group and 'group.' in entity: + group_entities = self.get_group_entities(entity) + for group_entity in group_entities: + if group_entity not in entitylist: + entitylist.append(group_entity) + + def get_group_entities(self, group): + group = self.get_state(group, attribute = "all") + return group["attributes"]["entity_id"] \ No newline at end of file diff --git a/appdaemon/settings/apps/groups/groups.yaml b/appdaemon/settings/apps/groups/groups.yaml new file mode 100644 index 0000000..adc8eb6 --- /dev/null +++ b/appdaemon/settings/apps/groups/groups.yaml @@ -0,0 +1,8 @@ +groups: + module: groups + class: Groups + friendly_name: Автоматические группы + entities: # list of entities + - media_player.lg_webos_smart_tv + # device_type: light + # entity_part: "_" \ No newline at end of file diff --git a/appdaemon/settings/apps/light/child_door_notifier.py b/appdaemon/settings/apps/light/child_door_notifier.py new file mode 100644 index 0000000..17e298c --- /dev/null +++ b/appdaemon/settings/apps/light/child_door_notifier.py @@ -0,0 +1,24 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import datetime + +class ChildDoorNotifier(hass.Hass): + isLocked = False + + def initialize(self): + if not globals.check_args(self,['entities', 'sensor']): + return + + self.listen_event_handle_list = [] + self.listen_event_handle_list.append(self.listen_state(self.state_change, self.args['sensor'])) #, attribute='action' + + def state_change(self, entity, attribute, old, new, kwargs): + self.log('state = {}'.format(new)) + if new == "on": + self.log('open') + for i in range(1, 7): + self.run_in(self.switch_light, i) + + def switch_light(self, kwargs): + for entity in self.args['entities']: + self.toggle(entity) \ No newline at end of file diff --git a/appdaemon/settings/apps/light/child_door_notifier.yaml b/appdaemon/settings/apps/light/child_door_notifier.yaml new file mode 100644 index 0000000..bd0524f --- /dev/null +++ b/appdaemon/settings/apps/light/child_door_notifier.yaml @@ -0,0 +1,14 @@ +################################# +# Door Notifier # +################################# +child_door_notifier: + module: child_door_notifier + class: ChildDoorNotifier + sensor: binary_sensor.door_childroom_contact + entities: + - light.svetlana_bedside_lamp_rgbww_light + - light.danil_bedside_lamp_rgbww_light + global_dependencies: + - globals + + diff --git a/appdaemon/settings/apps/light/light_balcony.py b/appdaemon/settings/apps/light/light_balcony.py new file mode 100644 index 0000000..2ab06a2 --- /dev/null +++ b/appdaemon/settings/apps/light/light_balcony.py @@ -0,0 +1,46 @@ +import appdaemon.plugins.hass.hassapi as hass +import datetime + +# +# App to control balcony room light based on lux and door sensor +# +# Args: +# door_sensor: door contact sensor +# lux_sensor: xiaomi lux sensor +# light: light entity_id +# lux_min: min lux value to turn light on +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class BalconyLightControl(hass.Hass): + def initialize(self): + self.door_sensor = self.args.get("door_sensor", "binary_sensor.door_balcony_contact") + self.light = self.args.get("light", "light.light_balcony_spotlight") + self.lux_sensor = self.args.get("lux_sensor", "sensor.brightness_balcony_illuminance") + self.lux_min = self.args.get("lux_min", 100) + + self.log("Initializing balcony light control application") + self.log(f"Door sensor: {self.door_sensor}") + self.log(f"Lux sensor: {self.lux_sensor}") + self.log(f"Lux min: {self.lux_min}") + self.log(f"Light: {self.light}") + + # Listen for presence events + self.listen_state(self.state_change, self.args['door_sensor']) + self.log("Presence event handler registered") + + def state_change(self, entity, attribute, old, new, kwargs): + self.log(f"door entity: {entity}, attribute: {attribute}, old: {old}, new: {new}") + lux = int(self.get_state(self.lux_sensor)) + if new == "on": + self.log("Door is open, lux: {}, lux_min: {}".format(lux, self.lux_min)) + if lux < self.lux_min: + self.turn_on(self.light) + else: + self.log("high lux value, skip turn light on") + elif new == "off": + self.log("Door is closed, lux: {}, lux_min: {}".format(lux, self.lux_min)) + self.turn_off(self.light) \ No newline at end of file diff --git a/appdaemon/settings/apps/light/light_balcony.yaml b/appdaemon/settings/apps/light/light_balcony.yaml new file mode 100644 index 0000000..704f70d --- /dev/null +++ b/appdaemon/settings/apps/light/light_balcony.yaml @@ -0,0 +1,7 @@ +light_balcony: + module: light_balcony + class: BalconyLightControl + light: light.light_balcony_spotlight + door_sensor: binary_sensor.door_balcony_contact + lux_sensor: sensor.brightness_balcony_illuminance + lux_min: 150 \ No newline at end of file diff --git a/appdaemon/settings/apps/light/light_mainroom.py b/appdaemon/settings/apps/light/light_mainroom.py new file mode 100644 index 0000000..eb75dc4 --- /dev/null +++ b/appdaemon/settings/apps/light/light_mainroom.py @@ -0,0 +1,36 @@ +import appdaemon.plugins.hass.hassapi as hass +import datetime + +# +# App to control main room light based on Aqara FP1 presence sensor +# +# Args: +# presence_sensor: presence sensor entity_id (default: sensor.presence_aqara_mainroom_presence_event) +# light: light entity_id (default: light.light_mainroom_pc_spotlight) +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class MainRoomLightControl(hass.Hass): + def initialize(self): + self.presence_sensor = self.args.get("presence_sensor", "sensor.presence_aqara_mainroom_presence_event") + self.light = self.args.get("light", "light.light_mainroom_pc_spotlight") + + self.log("Initializing light control application") + self.log(f"Presence sensor: {self.presence_sensor}") + self.log(f"Light: {self.light}") + + # Listen for presence events + self.listen_state(self.state_change, self.args['presence_sensor']) + self.log("Presence event handler registered") + + def state_change(self, entity, attribute, old, new, kwargs): + self.log(f"entity: {entity}, attribute: {attribute}, old: {old}, new: {new}") + if new == "away": + self.log("Status 'away' - turning off light") + self.turn_off(self.light) + else: + self.log("Presence detected - turning on light") + self.turn_on(self.light) \ No newline at end of file diff --git a/appdaemon/settings/apps/light/light_mainroom.yaml b/appdaemon/settings/apps/light/light_mainroom.yaml new file mode 100644 index 0000000..fd8962c --- /dev/null +++ b/appdaemon/settings/apps/light/light_mainroom.yaml @@ -0,0 +1,5 @@ +light_mainroom: + module: light_mainroom + class: MainRoomLightControl + presence_sensor: sensor.presence_aqara_mainroom_presence_event11 + light: light.light_mainroom_pc_spotlight diff --git a/appdaemon/settings/apps/light/lightbutton._yaml b/appdaemon/settings/apps/light/lightbutton._yaml new file mode 100644 index 0000000..c2d2396 --- /dev/null +++ b/appdaemon/settings/apps/light/lightbutton._yaml @@ -0,0 +1,29 @@ +################################# +# LIGHT BUTTONS # +################################# +# light_button_bath: +# module: lightbutton +# class: LightButton +# sensor: binary_sensor.wall_switch_bath +# entities: light.bath +# # control_input: input_boolean.bath_light_control +# global_dependencies: +# - globals + +light_button_main: + module: lightbutton + class: LightButton + sensor: sensor.bed_button + entities: light.yeelight_color1_34ce008fe328,light.yeelight_color1_286c071065c7,light.yeelight_mono1_34ce0087ff33,light.yeelight_mono1_f0b4290e5444,light.yeelight_mono1_f0b429a8f6df,light.yeelight_mono1_286c07f12bed,light.yeelight_mono1_f0b429a8d936,light.lamp1 + global_dependencies: + - globals + +light_button_hall: + module: lightbutton + class: LightButton + sensor: sensor.0x00158d000183a5fe_action + entities: switch.plug_light_hall + global_dependencies: + - globals + + diff --git a/appdaemon/settings/apps/light/lightbutton.py b/appdaemon/settings/apps/light/lightbutton.py new file mode 100644 index 0000000..e800353 --- /dev/null +++ b/appdaemon/settings/apps/light/lightbutton.py @@ -0,0 +1,133 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import datetime + +class LightButton(hass.Hass): + isLocked = False + + def initialize(self): + if not globals.check_args(self,['entities', 'sensor']): + return + + self.listen_event_handle_list = [] + self.listen_event_handle_list.append(self.listen_event(self.event_detected, "xiaomi_aqara.click")) + self.listen_event_handle_list.append(self.listen_state(self.state_change, self.args['sensor'])) #, attribute='action' + + def state_change(self, entity, attribute, old, new, kwargs): + if new == "single": + self.log('button_click') + self.make_action_by_mode("off") + if new == "double": + self.log('button_dblclick') + self.make_action_by_mode("on") + + def event_detected(self, event_name, data, kwargs): + if data["entity_id"] != self.args["sensor"] or self.isLocked: + return + self.isLocked = True + if data["click_type"] == "single": + self.log('button_click') + self.make_action_by_mode("off") + if data["click_type"] == "double": + self.log('button_dblclick') + self.make_action_by_mode("on") + + self.run_in(self.clear_lock, 1) + + def clear_lock(self, kwargs): + self.isLocked = False + + def make_action_by_mode(self, state): + states = self.fill_states() + control_entity = self.args.get('control_input', None) + if (control_entity != None): + entity_id = next(iter(states)) + state = self.get_state(entity_id) + self.set_state(entity_id, self.invert_state(state)) + self.set_state(control_entity, state) + return + + if len(self.args["entities"]) > 1: + self.toggle_next_with_state(state, states) + + def toggle_next_with_state(self, state, states): + for entity_id in states: + entity_state = states[entity_id] + if (entity_state == state): + self.set_state(entity_id, self.invert_state(state)) + self.log('toggling {}'.format(entity_id)) + return + + for entity_id in states: + self.log('toggling {}'.format(entity_id)) + self.toggle(entity_id) + + def set_state(self, entity_id, state): + if (state == "off"): + self.log('turn_off {}'.format(entity_id)) + self.set_off(entity_id) + if (state == "on"): + self.log('turn_on {}'.format(entity_id)) + self.set_on(entity_id) + + #light + def set_on(self, entity_id): + brightness = 1 #night mode + if self.time_is_between(self.datetime(), '07:00:00', '23:00:00'): + brightness=255 #day mode + + if entity_id.startswith("light") and 'color' in entity_id: + self.call_service("homeassistant/turn_on", entity_id = entity_id, transition = 1, brightness = brightness, color_temp=204) + elif entity_id.startswith("light"): + self.call_service("homeassistant/turn_on", entity_id = entity_id, transition = 1, brightness = brightness) + else: + self.turn_on(entity_id) + + def set_off(self, entity_id): + if entity_id.startswith("light"): + self.call_service("homeassistant/turn_off", entity_id = entity_id, transition = 1) + else: + self.turn_off(entity_id) + + + def invert_state(self, state): + if (state == "off"): + return "on" + return "off" + + def fill_states(self): + states = {} + for entity_id in self.split_device_list(self.args["entities"]): + state = self.get_state(entity_id) + if (state == None): + self.log("Warning! Device {} not found (state: {}).".format(entity_id, state)) + continue + states[entity_id] = state + return states + + def terminate(self): + if self.listen_event_handle_list != None: + for listen_event_handle in self.listen_event_handle_list: + self.cancel_listen_event(listen_event_handle) + + def time_is_between( + hass, target_dt: datetime, start_time: str, + end_time: str) -> bool: + """Generalization of AppDaemon's now_is_between method.""" + start_time_dt = hass.parse_time(start_time) # type: datetime + end_time_dt = hass.parse_time(end_time) # type: datetime + start_dt = target_dt.replace( + hour=start_time_dt.hour, + minute=start_time_dt.minute, + second=start_time_dt.second) + end_dt = target_dt.replace( + hour=end_time_dt.hour, + minute=end_time_dt.minute, + second=end_time_dt.second) + + if end_dt < start_dt: + # Spans midnight + if target_dt < start_dt and target_dt < end_dt: + target_dt = target_dt + timedelta(days=1) + end_dt = end_dt + timedelta(days=1) + return start_dt <= target_dt <= end_dt \ 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..4601a11 --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol._yaml @@ -0,0 +1,25 @@ +light_control_hall: + module: lightcontrol + class: LightControl + sensor: binary_sensor.motion_sensor_hall + control_entity: switch.plug_light_hall + control_night_entity: light.gateway + timeout: 240 + constraint: input_boolean.hall_light_control + ha_panel: alarm_control_panel.ha_alarm + vacuum: vacuum.xiaomi_vacuum_cleaner + global_dependencies: + - globals + +light_control_kitchen: + module: lightcontrol + class: LightControl + sensor: group.kitchen_motion_sensors + control_entity: switch.light_kitchen + timeout: 600 + constraint: input_boolean.kitchen_light_control + ha_panel: alarm_control_panel.ha_alarm + vacuum: vacuum.xiaomi_vacuum_cleaner + global_dependencies: + - globals + \ 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..2ab490d --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol.py @@ -0,0 +1,123 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import datetime + +# +# This app will use motion sensors to turn on and off control_entity for 'timeout' time. +# It work with xiaomi motion sensors. +# main algorithm: when sensor turns on - turn on the light; when sensor is off - set timer for 'timeout'; +# if sensor goes on - turn off the timer; when timer was hit - turn off the light. +# +# Args: +# +# sensor = motion sensor to use (will work with other sensor types too) +# timeout = timeout after sensor will turned off (keep in mind that xiaomi motion sensor has own timeout!) +# control_entity = entity, that will be controlled (example: group of light bulbs) +# constraint = you can define constraint (input_boolean), that will turn on and off this app. +# after_sundown (optionally) - whether to only trigger after sundown. example: True +# Release Notes +# +# Version 1.0: +# Initial Version + +class LightControl(hass.Hass): + timer = None + def initialize(self): + if not globals.check_args(self, ["sensor", "timeout", "control_entity"]): + return + self.listen_event_handle_list = [] + self.listen_event_handle_list.append(self.listen_state(self.sensor_trigger, self.args['sensor'])) + + def sensor_trigger(self, entity, attribute, old, new, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + if 'after_sundown' in self.args and self.args['after_sundown'] and not self.sun_down(): + # self.log('Sun is up, and we will turn it only after sundown.') + return + + vacuum_state = "docked" + ha_panel_state = "" + if "vacuum" in self.args: + vacuum_state = self.get_state(self.args["vacuum"]) + if "ha_panel" in self.args: + ha_panel_state = self.get_state(self.args["ha_panel"]) + #Если работает пылесос и сработал датчик движения + if ha_panel_state == "armed_away" and vacuum_state == 'cleaning': + self.log('Motion sensor is triggered, but vacuum is cleaning.') + return + + #sensor is off + if new == 'off' and old == 'on': + self.log('Sensor is off - running timer for {}s.'.format(self.args['timeout'])) + self.run_timer() + #sensor is on + if new == 'on' and old == 'off': + #turnin on control entity and wait while sensor will be off, than run timer. + self.control_entity_on() + self.stop_timer() + + + def terminate(self): + if self.listen_event_handle_list != None: + for listen_event_handle in self.listen_event_handle_list: + self.cancel_listen_event(listen_event_handle) + +############ TIMER ######################## + def run_timer(self): + if self.timer != None: + self.stop_timer() + self.timer = self.run_in(self.control_entity_off, self.args['timeout']) + + def control_entity_on(self): + #if 'constraint' is off - we dont need to do anything + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + entity = self.args['control_entity'] + if not self.time_is_between(self.datetime(), '07:00:00', '23:00:00') and 'control_night_entity' in self.args: + entity=self.args['control_night_entity'] #day mode + + if self.get_state(entity) == 'off': + self.turn_on(entity) + self.log("Light on ({}).".format(entity)) + + def control_entity_off(self, kwargs): + #if 'constraint' is off - we dont need to do anything + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + entity = self.args['control_entity'] + if not self.time_is_between(self.datetime(), '07:00:00', '23:00:00') and 'control_night_entity' in self.args: + entity=self.args['control_night_entity'] #day mode + + if self.get_state(entity) == 'on': + self.turn_off(entity) + self.log("Light off.") + self.stop_timer() + + def stop_timer(self): + if (self.timer == None): + return + self.log('stopping timer.') + self.cancel_timer(self.timer) + self.timer = None + + def time_is_between( + hass, target_dt: datetime, start_time: str, + end_time: str) -> bool: + """Generalization of AppDaemon's now_is_between method.""" + start_time_dt = hass.parse_time(start_time) # type: datetime + end_time_dt = hass.parse_time(end_time) # type: datetime + start_dt = target_dt.replace( + hour=start_time_dt.hour, + minute=start_time_dt.minute, + second=start_time_dt.second) + end_dt = target_dt.replace( + hour=end_time_dt.hour, + minute=end_time_dt.minute, + second=end_time_dt.second) + + if end_dt < start_dt: + # Spans midnight + if target_dt < start_dt and target_dt < end_dt: + target_dt = target_dt + timedelta(days=1) + end_dt = end_dt + timedelta(days=1) + return start_dt <= target_dt <= end_dt \ 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..d3f15ed --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol_bath.py @@ -0,0 +1,136 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import datetime +import time + +# +# 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 + entity = self.args['control_entity'] + if not self.time_is_between(self.datetime(), '07:00:00', '23:00:00') and 'control_night_entity' in self.args: + entity=self.args['control_night_entity'] #day mode + + if self.get_state(entity) == 'off': + self.turn_on(entity) + self.log("Light on.") + if 'control_mini_light_entity' in self.args: + self.turn_on(self.args['control_mini_light_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.door_state_changed == False: + self.log('Motion is detected inside and door is still closed - dont turn off the light') + return + + is_night = False + entity = self.args['control_entity'] + if not self.time_is_between(self.datetime(), '07:00:00', '23:00:00') and 'control_night_entity' in self.args: + entity=self.args['control_night_entity'] #day mode + is_night = True + + if self.get_state(entity) == 'on': + self.turn_off(entity) + self.log("Light off.") + if 'control_mini_light_entity' in self.args: + self.turn_off(self.args['control_mini_light_entity']) + if not is_night and 'control_night_entity' in self.args: + self.turn_off(self.args['control_night_entity']) + 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 + + def time_is_between( + hass, target_dt: datetime, start_time: str, + end_time: str) -> bool: + """Generalization of AppDaemon's now_is_between method.""" + start_time_dt = hass.parse_time(start_time) # type: datetime + end_time_dt = hass.parse_time(end_time) # type: datetime + start_dt = target_dt.replace( + hour=start_time_dt.hour, + minute=start_time_dt.minute, + second=start_time_dt.second) + end_dt = target_dt.replace( + hour=end_time_dt.hour, + minute=end_time_dt.minute, + second=end_time_dt.second) + + if end_dt < start_dt: + # Spans midnight + if target_dt < start_dt and target_dt < end_dt: + target_dt = target_dt + timedelta(days=1) + end_dt = end_dt + timedelta(days=1) + return start_dt <= target_dt <= end_dt \ 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..062fd46 --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol_bath.yaml @@ -0,0 +1,39 @@ +################################# +# LIGHT CONTROL # +################################# +light_control_bath: + module: lightcontrol_bath + class: LightControlBath + sensor: binary_sensor.motion_bath + door_sensor: binary_sensor.door_bath_contact + control_entity: switch.light_bath + control_night_entity: switch.light_bath_mirror + control_mini_light_entity: light.light_bath_shelf + timeout: 240 + constraint: input_boolean.bath_light_control + global_dependencies: + - globals + +light_control_shower: + module: lightcontrol_bath + class: LightControlBath + sensor: binary_sensor.motion_shower_center_occupancy + door_sensor: binary_sensor.door_shower_contact + control_entity: switch.light_shower_main_spotlights + control_night_entity: switch.shower_mirror + control_mini_light_entity: light.light_shower_shelf_light + timeout: 240 + constraint: input_boolean.shower_light_control + global_dependencies: + - globals + +light_control_shower_shower: + module: lightcontrol_bath + class: LightControlBath + sensor: binary_sensor.motion_shower_shower_occupancy + door_sensor: binary_sensor.door_shower_contact + control_entity: switch.light_shower_shower_spotlights + timeout: 360 + constraint: input_boolean.shower_light_control + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/light/lightcontrol_hall.py b/appdaemon/settings/apps/light/lightcontrol_hall.py new file mode 100644 index 0000000..d38ac41 --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol_hall.py @@ -0,0 +1,131 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import datetime +import time + +# +# 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 LightControlHall(hass.Hass): + timer = None + turn_on_by_door = None + def initialize(self): + if not globals.check_args(self, ["motion_sensors", "door_sensors", "timeout", "control_entity"]): + return + self.listen_event_handle_list = [] + for sensor in self.args['door_sensors']: + self.listen_event_handle_list.append(self.listen_state(self.sensor_trigger, sensor)) + + for sensor in self.args['motion_sensors']: + self.listen_event_handle_list.append(self.listen_state(self.motion_trigger, sensor)) + + for event in self.args['events']: + self.listen_event(self.event_trigger, event) + + + def motion_trigger(self, entity, attribute, old, new, kwargs): + #sensor is off + if new == 'off' and old == 'on' and self.turn_on_by_door != None: + 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': + self.stop_timer() + + def sensor_trigger(self, entity, attribute, old, new, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + #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.turn_on_by_door = True + self.stop_timer() + + def event_trigger(self, event_name, data, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + #turnin on control entity and wait while sensor will be off, than run timer. + self.control_entity_on() + self.turn_on_by_door = True + self.stop_timer() + self.run_timer() + + def terminate(self): + if self.listen_event_handle_list != None: + for listen_event_handle in self.listen_event_handle_list: + self.cancel_listen_event(listen_event_handle) + +############ TIMER ######################## + def run_timer(self): + if self.timer != None: + self.stop_timer() + self.timer = self.run_in(self.control_entity_off, self.args['timeout']) + + def control_entity_on(self): + #if 'constraint' is off - we dont need to do anything + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + entity = self.args['control_entity'] + state = self.get_state(entity) + + self.turn_on(entity) + self.log("Light on ({}).".format(entity)) + + def control_entity_off(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.turn_on_by_door = None + + entity = self.args['control_entity'] + state = self.get_state(entity) + self.turn_off(entity) + self.log("Light off.") + self.stop_timer() + + def stop_timer(self): + if (self.timer == None): + return + self.log('stopping timer.') + self.cancel_timer(self.timer) + self.timer = None + + def time_is_between( + hass, target_dt: datetime, start_time: str, + end_time: str) -> bool: + """Generalization of AppDaemon's now_is_between method.""" + start_time_dt = hass.parse_time(start_time) # type: datetime + end_time_dt = hass.parse_time(end_time) # type: datetime + start_dt = target_dt.replace( + hour=start_time_dt.hour, + minute=start_time_dt.minute, + second=start_time_dt.second) + end_dt = target_dt.replace( + hour=end_time_dt.hour, + minute=end_time_dt.minute, + second=end_time_dt.second) + + if end_dt < start_dt: + # Spans midnight + if target_dt < start_dt and target_dt < end_dt: + target_dt = target_dt + timedelta(days=1) + end_dt = end_dt + timedelta(days=1) + return start_dt <= target_dt <= end_dt \ No newline at end of file diff --git a/appdaemon/settings/apps/light/lightcontrol_hall.yaml b/appdaemon/settings/apps/light/lightcontrol_hall.yaml new file mode 100644 index 0000000..b70ba15 --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol_hall.yaml @@ -0,0 +1,18 @@ +light_control_hall: + module: lightcontrol_hall + class: LightControlHall + motion_sensors: + - binary_sensor.motion_hall_1_occupancy + - binary_sensor.motion_hall_2_occupancy + door_sensors: + - binary_sensor.door_locker_hall_right_contact + - binary_sensor.door_locker_hall_left_contact + - binary_sensor.door_locker2_hall_left_contact + - binary_sensor.door_locker2_hall_right_contact + events: + - door_opened + control_entity: light.light_hall_long + timeout: 120 + constraint: input_boolean.locker_light_control + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/light/lightcontrol_kitchenset.py b/appdaemon/settings/apps/light/lightcontrol_kitchenset.py new file mode 100644 index 0000000..aaaf95b --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol_kitchenset.py @@ -0,0 +1,149 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import datetime +import time + +# +# 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 LightControlKitchenset(hass.Hass): + timer = None + def initialize(self): + if "sensors" not in self.args or "timeout" not in self.args or "control_entity" not in self.args: + self.error("Please provide sensor, control_entity and timeout in config!") + return + for sensor in self.args['sensors']: + self.listen_state(self.sensor_trigger, sensor) + if 'night_sensors' in self.args: + for sensor in self.args['night_sensors']: + self.listen_state(self.sensor_night_trigger, sensor) + + def sensor_trigger(self, entity, attribute, old, new, kwargs): + #sensor is on + if new == 'on' and old == 'off': + #turnin on control entity and wait while sensor will be off, than run timer. + self.log('motion detected: {}'.format(entity)) + self.control_entity_on() + self.stop_timer() + + #sensor is off + if new == 'off' and old == 'on': + self.log('motion stop detected:{}'.format(entity)) + state = self.get_motion_state() + if state == 'off': + self.run_timer() + + def sensor_night_trigger(self, entity, attribute, old, new, kwargs): + door_state = 'on' + if 'door_sensors' in self.args: + for door in self.args['door_sensors']: + door_state = self.get_state(door) + self.log('door_state: {}'.format(door_state)) + if door_state == 'off': + door_state = 'off' + + if new == 'on' and old == 'off' and door_state == 'on': + if self.is_night(): + self.log('night motion detected: {}'.format(entity)) + self.control_entity_on() + self.stop_timer() + + if new == 'off' and old == 'on': + self.log('night motion stop detected:{}'.format(entity)) + state = self.get_motion_state() + if state == 'off': + self.run_timer() + +############ SENSOR GROUP ######################## + def get_motion_state(self): + sensors = self.args['sensors'] + night_sensors = [] + if 'night_sensors' in self.args and self.is_night(): + night_sensors = self.args['night_sensors'] + all_sensors = sensors + night_sensors + + self.log('all sensors: {}'.format(all_sensors)) + for sensor in all_sensors: + state = self.get_state(sensor) + if state == 'on': + return 'on' + return 'off' + + def is_night(self): + return not self.time_is_between(self.datetime(), '07:00:00', '23:00:00') + +############ 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 + entity = self.args['control_entity'] + if self.get_state(entity) == 'off': + self.turn_on(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 + + entity = self.args['control_entity'] + + if self.get_state(entity) == 'on': + self.turn_off(entity) + self.log("Light off.") + self.stop_timer() + + def run_timer(self): + if self.timer != None: + self.stop_timer() + + timeout = self.args['timeout'] + if 'night_timeout' in self.args and self.is_night(): + timeout = self.args['night_timeout'] + self.log('Sensor is off - running timer for {}s'.format(timeout)) + self.timer = self.run_in(self.control_entity_off, timeout) + + def stop_timer(self): + if (self.timer == None): + return + self.log('stopping timer.') + self.cancel_timer(self.timer) + self.timer = None + + def time_is_between( + hass, target_dt: datetime, start_time: str, + end_time: str) -> bool: + """Generalization of AppDaemon's now_is_between method.""" + start_time_dt = hass.parse_time(start_time) # type: datetime + end_time_dt = hass.parse_time(end_time) # type: datetime + start_dt = target_dt.replace( + hour=start_time_dt.hour, + minute=start_time_dt.minute, + second=start_time_dt.second) + end_dt = target_dt.replace( + hour=end_time_dt.hour, + minute=end_time_dt.minute, + second=end_time_dt.second) + + if end_dt < start_dt: + # Spans midnight + if target_dt < start_dt and target_dt < end_dt: + target_dt = target_dt + timedelta(days=1) + end_dt = end_dt + timedelta(days=1) + return start_dt <= target_dt <= end_dt \ No newline at end of file diff --git a/appdaemon/settings/apps/light/lightcontrol_kitchenset.yaml b/appdaemon/settings/apps/light/lightcontrol_kitchenset.yaml new file mode 100644 index 0000000..32582ab --- /dev/null +++ b/appdaemon/settings/apps/light/lightcontrol_kitchenset.yaml @@ -0,0 +1,20 @@ +################################# +# LIGHT CONTROL # +################################# +light_control_kitchenset: + module: lightcontrol_kitchenset + class: LightControlKitchenset + constraint: input_boolean.kitchenset_light_control + sensors: + - binary_sensor.motion_kitchenset_left_occupancy + - binary_sensor.motion_kitchenset_right_occupancy + night_sensors: + - binary_sensor.motion_hall_1_occupancy + - binary_sensor.motion_hall_2_occupancy + door_sensors: + - binary_sensor.door_kitchen_contact + control_entity: switch.light_kitchen_set + timeout: 900 #1200 - 20 mins + night_timeout: 300 #300 - 5 min + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/locks/aqara_lock.py b/appdaemon/settings/apps/locks/aqara_lock.py new file mode 100644 index 0000000..93fbb03 --- /dev/null +++ b/appdaemon/settings/apps/locks/aqara_lock.py @@ -0,0 +1,55 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# Aqara lock notifications +# Send notification to telegram of lock state +# +# Args: +# lock_state_entity - entity of aqara lock state +# notify - notify entity to send message +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class AqaraLock(hass.Hass): + def initialize(self): + if 'lock_state_entity' not in self.args or 'notify' not in self.args: + self.error("Please provide lock_state_entity and notify in config!") + return + + lock_state_entity = self.args['lock_state_entity'] + + self.log(' [+] listening for lock_state_entity sensor {}'.format(lock_state_entity)) + self.listen_state(self.sensor_trigger, lock_state_entity) + + def sensor_trigger(self, entity, attribute, old, new, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + #(old != "" and old != "unknown") or + if new == "unknown" or new == "" or new == "unavailable": + self.log('skiping event. {} changed from {} to {}'.format(entity, old, new)) + return + self.log('{} changed from {} to {}'.format(entity, old, new)) + + action = new + self.process_event(action) + + def process_event(self, eventname): + if eventname == "ring_bell": + self.fire_event('doorbell_call') + elif eventname == 'lock_opened_outside': + self.log("Дверь открыта снаружи, событие: {}.".format(eventname)) + elif 'finger_open_' in eventname: + self.fire_event('door_opened') + self.log("Дверь открыта отпечатком пальца, событие: {}.".format(eventname)) + self.notify_tg("Дверь открыта отпечатком пальца, событие: {}.".format(eventname)) + else: + self.log("Неизвестное событие замка: {}.".format(eventname)) + self.notify_tg("Неизвестное событие замка: {}".format(eventname)) + + def notify_tg(self, msg): + extra_data = {'parse_mode': 'html'} + self.notify(msg, name = self.args['notify'], data=extra_data) diff --git a/appdaemon/settings/apps/locks/aqara_lock.yaml b/appdaemon/settings/apps/locks/aqara_lock.yaml new file mode 100644 index 0000000..32d95ca --- /dev/null +++ b/appdaemon/settings/apps/locks/aqara_lock.yaml @@ -0,0 +1,8 @@ +aqara_lock: + module: aqara_lock + class: AqaraLock + lock_state_entity: sensor.door_lock_mqtt_action + constraint: input_boolean.aqara_lock_report + notify: telegram_monster + global_dependencies: + - globals diff --git a/appdaemon/settings/apps/notifiers/balance.py b/appdaemon/settings/apps/notifiers/balance.py new file mode 100644 index 0000000..77fcb38 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/balance.py @@ -0,0 +1,65 @@ +import datetime +from automation import Automation, Base # type: ignore + +# +# App to send email report for low balance +# +# Args: +# +# threshold = value below which battery levels are reported and email is sent +# always_send = set to 1 to override threshold and force send +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Balance(Base): + def initialize(self): + super().initialize() + self.check_balance({"force": 1}) + self.run_daily(self.check_balance, datetime.time(10, 0, 0)) + + def check_balance(self, kwargs): + # if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + # return + + values = {} + low = [] + for key in self.entity_ids: + device = self.entity_ids[key] + balance = self.get_state(device) + if balance != None and self.is_number(balance): + if float(balance) < self.args["threshold"]: + low.append(device) + values[device] = balance + + message="" + name = "Алтуфьево" + if 'name' in self.args: + name = self.args['name'] + if low: + message += "Пожалуйста пополните баланс ({}): (< {}) \n".format(name, self.args["threshold"]) + for device in low: + message += " - {}: {}\n".format(self.friendly_name(device), values[device]) + message += "\n\n" + + if low or ("always_send" in self.args and self.args["always_send"] == 1) or ("force" in kwargs and kwargs["force"] == 1): + if message != "": + self.log(message) + self.notify_tg(message) #name = "telegram_monster" + + def is_number(self, s): + try: + float(s) + return True + except ValueError: + return False + + def notify_tg(self, msg): + if not 'notify' in self.args: + return + extra_data = {'parse_mode': 'html'} + self.notify(msg, name = self.args['notify'], data=extra_data) diff --git a/appdaemon/settings/apps/notifiers/balance.yaml b/appdaemon/settings/apps/notifiers/balance.yaml new file mode 100644 index 0000000..6992357 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/balance.yaml @@ -0,0 +1,61 @@ +################################# +# SENSOR BATTERY LOW REMINDER # +################################# +balance_low_report: + class: Balance + module: balance + constraint: input_boolean.balance_report + name: Алтуфьево + entity_ids: + mosenergosbyt_balance: sensor.power_mosenergosbyt_balance + epd_balance: sensor.epd_balance + always_send: 0 + threshold: -100 + notify: telegram + global_dependencies: + - globals + +balance_low_report_belorechenskaya: + class: Balance + module: balance + constraint: input_boolean.balance_report + name: Белореченская + entity_ids: + mosenergosbyt_balance: sensor.power_mosenergosbyt_belorechenskaya_balance + epd_balance: sensor.epd_balance_belorechenskaya + always_send: 0 + threshold: -100 + notify: telegram + global_dependencies: + - globals + +balance_low_report_fonvizina: + class: Balance + module: balance + constraint: input_boolean.balance_report + name: Фонвизина + entity_ids: + fonvizina_epd_balance: sensor.fonvizina_epd_balance + fonvizina_trash_balance: sensor.fonvizina_trash_balance + fonvizina_kladovka_balance: sensor.fonvizina_kladovka_balance + fonvizina_epd_mosru_balance: sensor.epd_balance_mosru_fonvizina + always_send: 0 + threshold: -100 + inverted: true + notify: telegram + global_dependencies: + - globals + +balance_low_report_milashenkova: + class: Balance + module: balance + constraint: input_boolean.balance_report + name: Милашенкова + entity_ids: + mosenergosbyt_balance: sensor.power_mosenergosbyt_milashenkova_balance + epd_balance: sensor.epd_balance_milashenkova + always_send: 0 + threshold: -100 + notify: telegram + global_dependencies: + - globals diff --git a/appdaemon/settings/apps/notifiers/battery.py b/appdaemon/settings/apps/notifiers/battery.py new file mode 100644 index 0000000..d0a49fa --- /dev/null +++ b/appdaemon/settings/apps/notifiers/battery.py @@ -0,0 +1,65 @@ +import appdaemon.plugins.hass.hassapi as hass +import datetime + +# +# App to send email report for devices running low on battery +# +# Args: +# +# threshold = value below which battery levels are reported and email is sent +# always_send = set to 1 to override threshold and force send +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Battery(hass.Hass): + + def initialize(self): + #self.check_batteries({"force": 1}) + time = datetime.time(10, 0, 0) + self.run_daily(self.check_batteries, time) + self.check_batteries(None) + + def check_batteries(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + devices = self.get_state() + values = {} + low = [] + for deviceName in devices: + device = devices[deviceName] + if device == None: + continue + battery = None + if "attributes" in device: + if "battery" in device["attributes"]: + battery = device["attributes"]["battery"] + elif "battery_level" in device["attributes"]: + battery = device["attributes"]["battery_level"] + elif '_battery' in device['entity_id']: + battery = self.get_state(device['entity_id']) + if not battery.isdigit(): + continue + battery = int(battery) + print('{} = {}'.format(device['entity_id'], battery)) + if battery != None: + print('{} = {}'.format(device['entity_id'], battery)) + if battery < self.args["threshold"] and battery != 0: + low.append(deviceName) + values[deviceName] = battery + + message="" + if low: + message += "У следующих датчиков садится батарея: (< {}) \n".format(self.args["threshold"]) + for device in low: + message += "{}: {}%\n".format(self.friendly_name(device), values[device]) + message += "\n\n" + + if low: # or ("always_send" in self.args and self.args["always_send"] == 1) or ("force" in kwargs and kwargs["force"] == 1): + if message != "": + self.notify(message, name = "telegram") diff --git a/appdaemon/settings/apps/notifiers/battery.yaml b/appdaemon/settings/apps/notifiers/battery.yaml new file mode 100644 index 0000000..581087f --- /dev/null +++ b/appdaemon/settings/apps/notifiers/battery.yaml @@ -0,0 +1,12 @@ +################################# +# SENSOR BATTERY LOW REMINDER # +################################# +sensor_battery_low_report: + class: Battery + module: battery + constraint: input_boolean.battery_report + always_send: 0 + threshold: 10 + notify: telegram + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/bms_monitor.py b/appdaemon/settings/apps/notifiers/bms_monitor.py new file mode 100644 index 0000000..fff82e1 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/bms_monitor.py @@ -0,0 +1,165 @@ +import appdaemon.plugins.hass.hassapi as hass +import datetime + +class BMSMonitor(hass.Hass): + def initialize(self): + # Получаем настройки из apps.yaml + self.chat_id = self.args.get("chat_id") + self.power_entity = self.args.get("power_sensor") + self.soc_entity = self.args.get("soc_sensor") + self.current_entity = self.args.get("current_sensor") + self.voltage_entity = self.args.get("voltage_sensor") + self.threshold = float(self.args.get("power_threshold", 5)) + self.interval = int(self.args.get("discharge_interval", 300)) + self.notification_target = self.args.get("notification_target", 'telegram') + + # Внутренние переменные состояния + self.current_state = "IDLE" # IDLE, CHARGING, DISCHARGING + self.timer_handle = None + self.was_charging = False + + # Переменная для хранения времени начала отключения + self.outage_start_time = None + + # Слушаем изменения мощности + self.listen_state(self.on_power_change, self.power_entity) + # Слушаем SOC для детектирования 100% + self.listen_state(self.on_soc_change, self.soc_entity) + + self.log("BMS Monitor запущен. Ожидание изменений...") + + def on_power_change(self, entity, attribute, old, new, kwargs): + try: + power = float(new) + except (ValueError, TypeError): + return + + # Определяем новый режим + if power > self.threshold: + new_state = "CHARGING" + elif power < -self.threshold: + new_state = "DISCHARGING" + else: + new_state = "IDLE" + + # Логика переходов состояний + if self.current_state != new_state: + self.log(f"Смена состояния BMS: {self.current_state} -> {new_state}") + + # 1. Переход в разрядку (Отключили питание) + if new_state == "DISCHARGING" and self.current_state != "DISCHARGING": + # Фиксируем время начала отключения + self.outage_start_time = datetime.datetime.now() + + self.send_telegram("⚠️ ВНИМАНИЕ: Отключено питание!\nБатарея перешла в режим разрядки.", "html") + self.start_discharge_timer() + + # 2. Выход из разрядки (Питание восстановлено) + # Срабатывает при переходе DISCHARGING -> IDLE или DISCHARGING -> CHARGING + elif self.current_state == "DISCHARGING" and new_state in ("IDLE", "CHARGING"): + duration_msg = "" + if self.outage_start_time: + duration = datetime.datetime.now() - self.outage_start_time + duration_msg = f"\n⏱ Время без электричества: {self._format_timedelta(duration)}" + self.outage_start_time = None # Сбрасываем таймер + + self.send_telegram(f"✅ Питание восстановлено!\nБатарея вышла из режима разрядки.{duration_msg}", "html") + self.stop_discharge_timer() + + # 3. Переход в простой после зарядки (Зарядка окончена) + elif new_state == "IDLE" and self.current_state == "CHARGING": + self.send_telegram("🔋 Зарядка завершена!\nБатарея полностью заряжена или нагрузка отключена.", "html") + + # 4. Переход IDLE -> CHARGING (если не было разрядки перед этим) + elif new_state == "CHARGING" and self.current_state == "IDLE": + # Можно добавить отдельное уведомление, если нужно + pass + + self.current_state = new_state + self.was_charging = (new_state == "CHARGING") + + def on_soc_change(self, entity, attribute, old, new, kwargs): + # Дополнительная проверка на 100% SOC, если мощность еще не упала в ноль + try: + soc = float(new) + if soc >= 100 and self.current_state == "CHARGING" and not self.was_charging: + # Небольшая защита от повторных уведомлений + self.was_charging = True + except (ValueError, TypeError): + pass + + def start_discharge_timer(self): + if self.timer_handle is None: + # Запускаем таймер: сразу первое сообщение, потом каждые N секунд + self.send_status_report() + self.timer_handle = self.run_every(self.periodic_status, datetime.datetime.now(), self.interval) + self.log("Таймер статуса разрядки запущен") + + def stop_discharge_timer(self): + if self.timer_handle is not None: + self.cancel_timer(self.timer_handle) + self.timer_handle = None + self.log("Таймер статуса разрядки остановлен") + + def periodic_status(self, kwargs): + # Проверяем, все ли еще в режиме разрядки перед отправкой + if self.current_state == "DISCHARGING": + self.send_status_report() + else: + self.stop_discharge_timer() + + def _format_timedelta(self, td): + """Преобразует timedelta в строку вида 'X ч Y мин'""" + total_seconds = int(td.total_seconds()) + hours, remainder = divmod(total_seconds, 3600) + minutes, _ = divmod(remainder, 60) + + parts = [] + if hours > 0: + parts.append(f"{hours} ч") + parts.append(f"{minutes} мин") + + return " ".join(parts) + + def send_status_report(self): + try: + # Получаем значения напрямую + power_raw = self.get_state(self.power_entity) + soc_raw = self.get_state(self.soc_entity) + current_raw = self.get_state(self.current_entity) + voltage_raw = self.get_state(self.voltage_entity) + + # Преобразуем и округляем средствами Python + power = round(float(power_raw), 1) if power_raw not in (None, 'unknown', 'unavailable') else 0 + soc = round(float(soc_raw), 1) if soc_raw not in (None, 'unknown', 'unavailable') else 0 + current = round(float(current_raw), 2) if current_raw not in (None, 'unknown', 'unavailable') else 0 + voltage = round(float(voltage_raw), 2) if voltage_raw not in (None, 'unknown', 'unavailable') else 0 + + # Определяем цвет/иконку для направления тока + direction = "🔻 Разряд" if power < 0 else "🔺 Заряд" + + # Добавляем время без электричества, если известно + outage_info = "" + if self.outage_start_time: + duration = datetime.datetime.now() - self.outage_start_time + outage_info = f"\n⏳ Без света: {self._format_timedelta(duration)}" + + msg = ( + f"📊 Статус батареи (JK BMS)\n\n" + f"🔋 SOC: {soc}%\n" + f"⚡ Мощность: {power} Вт ({direction})\n" + f"🔌 Ток: {current} А\n" + f"🔋 Напряжение: {voltage} В" + f"{outage_info}\n" + f"🕒 Время: {datetime.datetime.now().strftime('%H:%M:%S')}" + ) + self.send_telegram(msg, "html") + except Exception as e: + self.log(f"Ошибка при отправке статуса: {e}") + + def send_telegram(self, message, parse_mode="html"): + try: + extra_data = {'parse_mode': parse_mode} + self.notify(message, name = self.notification_target, data=extra_data) + except Exception as e: + self.log(f"Ошибка отправки Telegram: {e}") \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/bms_monitor.yaml b/appdaemon/settings/apps/notifiers/bms_monitor.yaml new file mode 100644 index 0000000..8fdbc87 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/bms_monitor.yaml @@ -0,0 +1,14 @@ +bms_monitor: + module: bms_monitor + class: BMSMonitor + # Telegram настройки + notification_target: telegram_monster + # Сущности из вашей карточки + power_sensor: sensor.jk_bms_jk_bms_power + soc_sensor: sensor.jk_bms_jk_bms_state_of_charge + current_sensor: sensor.jk_bms_jk_bms_current + voltage_sensor: sensor.jk_bms_jk_bms_total_voltage + # Порог мощности (Вт) для определения режима (чтобы избежать шумов) + power_threshold: 5 + # Интервал оповещений при разрядке (секунды) + discharge_interval: 300 \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/dishwasher.py b/appdaemon/settings/apps/notifiers/dishwasher.py new file mode 100644 index 0000000..e44ec88 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/dishwasher.py @@ -0,0 +1,75 @@ +import appdaemon.plugins.hass.hassapi as hass +from datetime import timedelta +import datetime + +# +# App to show dishwasher state +# +# Args: +# +# door_sensor = dishwasher door sensor +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Dishwasher(hass.Hass): + listen_event_handle_list = [] + run_state = "" + + def initialize(self): + if 'run_state' not in self.args or 'door_sensor' not in self.args or 'counter' not in self.args: + self.log("Please define all args") + return + + self.listen_event_handle_list.append(self.listen_state(self.door_state_change, self.args['door_sensor'])) #, attribute='action' + self.listen_event_handle_list.append(self.listen_state(self.run_state_change, self.args['run_state'])) #, attribute='action' + + + def door_state_change(self, entity, attribute, old, new, kwargs): + if (new == "on"): + self.call_service("counter/increment", entity_id = self.args['counter']) + if (self.run_state == "finished"): + self.run_state = "collecting" + self.set_light_state_by_counter() + + def run_state_change(self, entity, attribute, old, new, kwargs): + self.log("event: {}: {}->{}".format(attribute,old,new)) + if (new == "Run"): + self.log('resetting counter') + self.call_service("counter/reset", entity_id = self.args['counter']) + self.run_state = "washing" + if (new == "Finished"): + self.log('resetting counter') + self.call_service("counter/reset", entity_id = self.args['counter']) + self.run_state = "finished" + + self.log('Current state is: {}'.format(self.run_state)) + self.set_light_state_by_counter() + + def set_light_state_by_counter(self): + state = self.get_state(self.args['counter']) + open_count = int(state) + self.log('counter state={}, machine state = {}'.format(state, self.run_state)) + + if (self.run_state == "finished"): + # Есть посуда, которую нужно разобрать + # red color + self.log('Time to take dishes out.') + if 'lamp' in self.args: + self.call_service("light/turn_on", entity_id = self.args['lamp'], rgb_color=[255, 0, 0]) #red + + elif (self.run_state == "washing"): + self.log('washing in progress') + self.call_service("light/turn_off", entity_id = self.args['lamp']) + + elif (open_count > 10): + self.log('Time to run dishwasher.') + if 'lamp' in self.args: + self.call_service("light/turn_on", entity_id = self.args['lamp'], rgb_color=[245, 236, 0]) #yellow + + else: + self.call_service("light/turn_off", entity_id = self.args['lamp']) \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/dishwasher.yaml b/appdaemon/settings/apps/notifiers/dishwasher.yaml new file mode 100644 index 0000000..9460a07 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/dishwasher.yaml @@ -0,0 +1,13 @@ +################################# +# TEETH BRUSHING REMINDER # +################################# +dishwasher: + class: Dishwasher + module: dishwasher + constraint: input_boolean.dishwasher_state_light + counter: counter.dishwasher_door_open_count + door_sensor: binary_sensor.hc_dishwasher_door + run_state: sensor.hc_dishwasher_state + lamp: light.espresense_kitchen_led_1 + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/security.py b/appdaemon/settings/apps/notifiers/security.py new file mode 100644 index 0000000..3bfc17a --- /dev/null +++ b/appdaemon/settings/apps/notifiers/security.py @@ -0,0 +1,28 @@ +import appdaemon.plugins.hass.hassapi as hass +import datetime + +# +# App to send reports about unsuccessfull auths +# +# Args: +# +# threshold = value below which battery levels are reported and email is sent +# always_send = set to 1 to override threshold and force send +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Security(hass.Hass): + def initialize(self): + self.log('---------------------') + self.listen_event_handle_list = [] + self.listen_event_handle_list.append(self.listen_state(self.state_change, 'persistent_notification.http_login', attribute='message')) + + def state_change(self, entity, attribute, old, new, kwargs): + notifier = self.args.get('notify', 'telegram') + self.notify(new, name = notifier) + self.log(new) \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/security.yaml b/appdaemon/settings/apps/notifiers/security.yaml new file mode 100644 index 0000000..e618198 --- /dev/null +++ b/appdaemon/settings/apps/notifiers/security.yaml @@ -0,0 +1,10 @@ +################################# +# INVALID AUTH REPORT # +################################# +invalid_auth_report: + class: Security + module: security + alice: media_player.yandex_station_m00r46000m53db + notify: telegram + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/notifiers/teeth_brushing.py b/appdaemon/settings/apps/notifiers/teeth_brushing.py new file mode 100644 index 0000000..dc3b6ed --- /dev/null +++ b/appdaemon/settings/apps/notifiers/teeth_brushing.py @@ -0,0 +1,82 @@ +import appdaemon.plugins.hass.hassapi as hass +from datetime import timedelta +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 Teeth(hass.Hass): + listen_event_handle_list = [] + + def initialize(self): + # Устанавливаем сброс в 10 утра + reset_time = datetime.time(10, 0, 0) + self.run_daily(self.reset_action, reset_time) + + # Запускаем проверку напоминаний + self.timer = self.run_every(self.remind_if_needed, self.datetime()+timedelta(seconds=1), self.args['remind_interval']) + + # Слушаем изменения состояния кнопки + self.listen_event_handle_list.append(self.listen_state(self.button_state_change, self.args['mute_button'])) + + def button_state_change(self, entity, attribute, old, new, kwargs): + self.log("event: {}={}".format(attribute,new)) + if attribute == 'state' and new == 'single': + self.log('Muting notifications') + self.set_state(self.args['mute_boolean'], state='on') + self.say("Принято") + + def say(self, command): + self.log('command = {}'.format(command)) + self.call_service('media_player/play_media', entity_id=self.args['alice'], media_content_type='text', media_content_id=command) + + def reset_action(self, kwargs): + self.log('Resetting mute state') + self.set_state(self.args['mute_boolean'], state='off') + + def remind_if_needed(self, kwargs): + # Проверяем состояние input_boolean + if self.get_state(self.args['mute_boolean']) == 'on': + return + + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + self.log('disabled by constraint') + return + + home_sensors = [] + if 'home_sensors' in self.args: + home_sensors = self.args['home_sensors'] + + at_home = False + is_any = False + for sensor in home_sensors: + is_any = True + state = self.get_state(sensor) + if state != 'not_home': + at_home = True + if not is_any: + at_home = True + + if not at_home: + self.log('User is away, skipping notification') + return + + notify = "telegram" + if "notify" in self.args: + notify = self.args["notify"] + now = datetime.datetime.now()+timedelta(hours=3) + message="Уже {}, а ты еще не почистил зубы. Самое время =)".format(now.strftime("%H:%M")) + self.notify(message, name = notify) + diff --git a/appdaemon/settings/apps/notifiers/teeth_brushing.yaml b/appdaemon/settings/apps/notifiers/teeth_brushing.yaml new file mode 100644 index 0000000..bb4123f --- /dev/null +++ b/appdaemon/settings/apps/notifiers/teeth_brushing.yaml @@ -0,0 +1,15 @@ +################################# +# TEETH BRUSHING REMINDER # +################################# +teeth_danil_brush_reminder: + class: Teeth + module: teeth_brushing + constraint: input_boolean.teeth_brushing_reminder + mute_boolean: input_boolean.teeth_muted + notify: telegram_monster + remind_interval: 1800 + mute_button: sensor.button_shower_sink_action + home_sensors: [person.danil] + alice: media_player.yandex_station_ff98f029ab48468d28c557be + 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..9e67c3b --- /dev/null +++ b/appdaemon/settings/apps/notifiers/water_counters.py @@ -0,0 +1,109 @@ +import appdaemon.plugins.hass.hassapi as hass +import datetime +import globals + +# +# App to send notifications when counters state needs attention +# +# Args: +# +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class WaterCountersAlarm(hass.Hass): + checkup_threasold = 31 + send_threasold = 31 + + def initialize(self): + if not globals.check_args(self, ['checkup_threasold', 'send_threasold']): + return + self.checkup_threasold = self.args['checkup_threasold'] + self.send_threasold = self.args['send_threasold'] + + self.check_send_dates({"force": 1}) + self.run_daily(self.check_checkup_dates, datetime.time(10, 0, 0)) + self.run_daily(self.check_send_dates, datetime.time(10, 1, 0)) + #self.check_checkup_dates([]) + + def check_send_dates(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + devices = self.get_state() + if devices == None: + return + values = {} + send_devices = [] + for deviceName in devices: + device = devices[deviceName] + if (device == None): + continue + send_date = None + if "attributes" in device: + if "date" in device["attributes"]: + send_date = device["attributes"]["date"] + if send_date != None: + self.log('found device {} with send date {}'.format(deviceName, send_date)) + if send_date == "": + send_devices.append(deviceName) + else: + send_date_date = None + if '-' in send_date: + send_date_date = datetime.datetime.strptime(send_date, '%Y-%m-%d') + else: + send_date_date = datetime.datetime.strptime(send_date, '%d.%m.%Y') + if send_date_date < (datetime.datetime.now() - datetime.timedelta(days=self.send_threasold)): + send_devices.append(deviceName) + values[deviceName] = send_date + + message="" + if send_devices: + message += "ВНИМАНИЕ! У вас есть счетчики, показания по которым давно не передавались:\n" + for device in send_devices: + message += "- {}: {}\n".format(self.friendly_name(device), values[device]) + message += "\n\n" + + if send_devices or ("always_send" in self.args and self.args["always_send"] == 1) or ("force" in kwargs and kwargs["force"] == 1): + if message != "": + self.notify(message, name = "telegram") + + def check_checkup_dates(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + entities = self.get_state() + if entities == None: + return + + values = {} + checkup_devices = [] + for entity_id in entities: + entity = entities[entity_id] + if entity == None: + # self.log('not found {} in entities list.'.format(entity_id)) + continue + checkup = None + if "attributes" in entity: + if "checkup" in entity["attributes"]: + checkup = entity["attributes"]["checkup"] + if checkup != None: + self.log('found device {} with checkup date {}'.format(entity_id, checkup)) + checkup_date = datetime.datetime.strptime(checkup, '%Y-%m-%d') + if (checkup_date - datetime.timedelta(days=self.checkup_threasold)) < datetime.datetime.now(): + checkup_devices.append(entity_id) + values[entity_id] = checkup + + message="" + if checkup_devices: + message += "ВНИМАНИЕ! У вас есть счетчик, нуждающийся в поверке:\n" + for device in checkup_devices: + message += "- {}: {}\n".format(self.friendly_name(device), values[device]) + message += "\n\n" + + if checkup_devices or ("always_send" in self.args and self.args["always_send"] == 1) or ("force" in kwargs and kwargs["force"] == 1): + if message != "": + self.notify(message, name = "telegram") \ 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..116929f --- /dev/null +++ b/appdaemon/settings/apps/notifiers/water_counters.yaml @@ -0,0 +1,12 @@ +################################# +# WATER COUNTERS ALARM # +################################# +water_counters: + class: WaterCountersAlarm + module: water_counters + constraint: input_boolean.water_counters_report + notify: telegram + checkup_threasold: 31 + send_threasold: 31 + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/notify.py b/appdaemon/settings/apps/notify.py new file mode 100644 index 0000000..631cc84 --- /dev/null +++ b/appdaemon/settings/apps/notify.py @@ -0,0 +1,63 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# Notification system +# +# Args: +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class BaseNotifyTarget(): + def notify(self, hass, message, person='*'): + hass.log('notifying person {} with message: {}'.format(person, message)) + +class AliceNotifyTarget(BaseNotifyTarget): + alice = 'media_player.yandex_station' + def notify(self, hass, message, person='*'): + msg = message + if person == 'danil': + msg = "Данил, " + msg + elif person == 'sveta': + msg = "Света, " + msg + hass.log('notifying over alice person {} with message: {}'.format(target, message)) + self.say(hass, msg) + + def say(self, hass, message): + hass.log('message = {}'.format(message)) + hass.call_service('media_player/play_media', entity_id=self.alice, media_content_type='text', media_content_id=message) + +class TelegramNotifyTarget(BaseNotifyTarget): + target_default = 'telegram' + target_sveta = 'telegram_sveta' + target_danil = 'telegram_monster' + + def notify(self, hass, message, person='*'): + target = self.target_default + if person == 'danil': + target = self.target_danil + elif person == 'sveta': + target = self.target_sveta + + hass.log('notifying over telegram person {} with message: {}'.format(target, message)) + hass.notify(message, name=target) + +class Notification(hass.Hass): + home_targets = [AliceNotifyTarget()] + away_targets = [TelegramNotifyTarget()] + + def initialize(self): + self.log('Init') + + def notify_person(self, message, target='*', repeat = 1, delay = 60, onEvent = '', at=None): + notifyTarget = None + away_mode = True + if (away_mode): + notifyTarget = self.away_targets[0] + else: + notifyTarget = self.home_targets[0] + + self.log('message: {}'.format(message)) + notifyTarget.notify(self, message, person=target) \ No newline at end of file diff --git a/appdaemon/settings/apps/notify.yaml b/appdaemon/settings/apps/notify.yaml new file mode 100644 index 0000000..a407e7d --- /dev/null +++ b/appdaemon/settings/apps/notify.yaml @@ -0,0 +1,3 @@ +notify: + module: notify + class: Notification \ No newline at end of file diff --git a/appdaemon/settings/apps/power/power_theory.py b/appdaemon/settings/apps/power/power_theory.py new file mode 100644 index 0000000..f68a43f --- /dev/null +++ b/appdaemon/settings/apps/power/power_theory.py @@ -0,0 +1,63 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import datetime +from datetime import timedelta +# +# POwer theory +# Power theory tariff switch +# +# Args: +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class PowerTheory(hass.Hass): + def initialize(self): + self.run_daily(self.timer_tick, self.parse_time("07:00:00")) + self.run_daily(self.timer_tick, self.parse_time("10:00:00")) + self.run_daily(self.timer_tick, self.parse_time("17:00:00")) + self.run_daily(self.timer_tick, self.parse_time("21:00:00")) + self.run_daily(self.timer_tick, self.parse_time("23:00:00")) + self.timer_tick(None) + + def timer_tick(self, kwargs): + t1 = self.time_is_between(self.datetime(), '07:00:00', '09:59:59') or self.time_is_between(self.datetime(), '17:00:00', '20:59:59') + t2 = self.time_is_between(self.datetime(), '23:00:00', '06:59:00') + t3 = self.time_is_between(self.datetime(), '10:00:00', '16:59:59') or self.time_is_between(self.datetime(), '21:00:00', '22:59:59') + if t1: + self.switch_to_tariff('t1') + if t2: + self.switch_to_tariff('t2') + if t3: + self.switch_to_tariff('t3') + + def switch_to_tariff(self, tariff): + self.log('switching to power tariff: {}'.format(tariff)) + self.call_service("select/select_option", entity_id='select.power_theory', option=tariff) + + + def time_is_between( + hass, target_dt: datetime, start_time: str, + end_time: str) -> bool: + """Generalization of AppDaemon's now_is_between method.""" + start_time_dt = hass.parse_time(start_time) # type: datetime + end_time_dt = hass.parse_time(end_time) # type: datetime + start_dt = target_dt.replace( + hour=start_time_dt.hour, + minute=start_time_dt.minute, + second=start_time_dt.second) + end_dt = target_dt.replace( + hour=end_time_dt.hour, + minute=end_time_dt.minute, + second=end_time_dt.second) + + if end_dt < start_dt: + # Spans midnight + if target_dt < start_dt and target_dt < end_dt: + target_dt = target_dt + timedelta(days=1) + end_dt = end_dt + timedelta(days=1) + return start_dt <= target_dt <= end_dt \ No newline at end of file diff --git a/appdaemon/settings/apps/power/power_theory.yaml b/appdaemon/settings/apps/power/power_theory.yaml new file mode 100644 index 0000000..c9c8d46 --- /dev/null +++ b/appdaemon/settings/apps/power/power_theory.yaml @@ -0,0 +1,6 @@ +power_theory: + module: power_theory + class: PowerTheory + global_dependencies: + - globals + \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/alarm_panel.py b/appdaemon/settings/apps/presence/alarm_panel.py new file mode 100644 index 0000000..031a0bb --- /dev/null +++ b/appdaemon/settings/apps/presence/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/alarm_panel.yaml b/appdaemon/settings/apps/presence/alarm_panel.yaml new file mode 100644 index 0000000..42c07b7 --- /dev/null +++ b/appdaemon/settings/apps/presence/alarm_panel.yaml @@ -0,0 +1,11 @@ +################################# +# HOME PRESENSE # +################################# +alarm_panel: + module: alarm_panel + class: AlarmPanelBySensor + sensor: sensor.presence_home + ha_panel: alarm_control_panel.ha_alarm + constraint: input_boolean.presence_control + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/alarm_panel_action.py b/appdaemon/settings/apps/presence/alarm_panel_action.py new file mode 100644 index 0000000..3825aa0 --- /dev/null +++ b/appdaemon/settings/apps/presence/alarm_panel_action.py @@ -0,0 +1,77 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# Listen for Alarm panel state change and fire an events ('away_mode' and 'return_home_mode') +# +# Args: +# ha_panel - alarm control panel entity. +# Release Notes +# +# Version 1.0: +# Initial Version + +class AlarmPanelAction(hass.Hass): + extended_timeout = 5*60 + timers = [] + + def initialize(self): + if "ha_panel" not in self.args: + self.error("Please provide ha_panel in config!") + return + self.listen_state(self.ha_panel_trigger, self.args['ha_panel']) + extended_timeout = self.args.get('extended_timeout', self.extended_timeout) + self.log('extended_timeout: {}'.format(self.extended_timeout)) + + def ha_panel_trigger(self, entity, attribute, old, new, kwargs): + # не срабатываем на triggered. + if new == "armed_away" and old != "triggered": + self.away_mode() + if new == "disarmed" and old == "armed_away": + self.return_home_mode() + + def away_mode(self): + self.log('away_mode') + self.fire_event('away_mode') + + if 'presence_extended' in self.args: + self.fire_event('presence_extended_just_left') + self.set_mode('Just Left') + self.cancel_current_timer() + self.timers.append(self.run_in(self.run_in_set_mode, self.extended_timeout, mode='Away')) + + + def return_home_mode(self): + self.log('return_home_mode') + self.fire_event('return_home_mode') + if 'presence_extended' in self.args: + self.set_mode('Just Arrived') + self.fire_event('presence_extended_just_arrived') + self.cancel_current_timer() + self.timers.append(self.run_in(self.run_in_set_mode, self.extended_timeout, mode='Home')) + + + def set_mode(self, mode): + if 'presence_extended' not in self.args: + return + entity_id = self.args['presence_extended'] + self.log('setting mode direct: {}={}'.format(entity_id, mode)) + self.call_service("input_select/select_option", entity_id = entity_id, option = mode) + + + def run_in_set_mode(self, kwargs): + if not 'mode' in kwargs: + return + mode = kwargs['mode'] + entity_id = self.args['presence_extended'] + event = 'presence_extended_{}'.format(mode.lower()) + self.log('firing event: {}'.format(event)) + self.fire_event(event) + + self.log('setting mode by timer: {}={}'.format(entity_id, mode)) + self.call_service("input_select/select_option", entity_id = entity_id, option = mode) + + def cancel_current_timer(self): + for timer in self.timers: + self.log('canceling timer') + self.cancel_timer(timer) + self.timers = [] diff --git a/appdaemon/settings/apps/presence/alarm_panel_action.yaml b/appdaemon/settings/apps/presence/alarm_panel_action.yaml new file mode 100644 index 0000000..dd2471b --- /dev/null +++ b/appdaemon/settings/apps/presence/alarm_panel_action.yaml @@ -0,0 +1,11 @@ +################################# +# HOME PRESENSE # +################################# +alarm_panel_action: + module: alarm_panel_action + class: AlarmPanelAction + ha_panel: alarm_control_panel.ha_alarm + presence_extended: input_select.presence_extended + extended_timeout: 300 + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/entities_action.py b/appdaemon/settings/apps/presence/entities_action.py new file mode 100644 index 0000000..17681d8 --- /dev/null +++ b/appdaemon/settings/apps/presence/entities_action.py @@ -0,0 +1,80 @@ +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): + mode = None + 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") + self.determine_current_mode() + self.set_thermostat_by_mode(None) + self.timer = self.run_every(self.set_thermostat_by_mode, 'now', 5*60) + + def determine_current_mode(self): + state = self.get_state('input_select.presence_extended') + if state == 'Home' or state == 'Just Arrived': + self.mode = 'home' + else: + self.mode = 'away' + + def away_mode(self, event_id, event_args, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.mode = 'away' + for entity_id in self.args["away_entities"]: + self.turn_off_custom(entity_id) + self.set_thermostat_by_mode(None) + + 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.mode = 'home' + entities = self.args["return_entities"] + if entities == None: + entities=[] + for entity_id in entities: + self.turn_on_custom(entity_id) + self.set_thermostat_by_mode(None) + + def set_thermostat_by_mode(self, kwargs): + if "thremostats" not in self.args: + return + preset = 'away' + if self.mode == 'home': + preset = 'manual' + for entity_id in self.args["thremostats"]: + state = self.get_state(entity_id, 'preset_mode') + if state != preset: + self.log('Set thermostat ({}), current state = {}, set mode to {}'.format(entity_id, state, preset)) + self.call_service("climate/set_preset_mode", entity_id=entity_id, preset_mode = preset) + + def turn_on_custom(self, entity_id): + if 'cover.' in entity_id: + self.call_service("cover/open_cover", entity_id=entity_id) + return + if 'climate.' in entity_id: + self.call_service("climate/turn_on", entity_id=entity_id) + return + self.turn_on(entity_id) + + def turn_off_custom(self, entity_id): + if 'cover.' in entity_id: + self.call_service("cover/close_cover", entity_id=entity_id) + return + if 'climate.' in entity_id: + self.call_service("climate/turn_off", entity_id=entity_id) + return + self.turn_off(entity_id) diff --git a/appdaemon/settings/apps/presence/entities_action.yaml b/appdaemon/settings/apps/presence/entities_action.yaml new file mode 100644 index 0000000..f944ed6 --- /dev/null +++ b/appdaemon/settings/apps/presence/entities_action.yaml @@ -0,0 +1,82 @@ +################################# +# HOME PRESENSE # +################################# +entities_action: + module: entities_action + class: EntitiesByPresence + away_entities: + # air conditioners + - climate.ac_bedroom + - climate.ac_childroom + - climate.ac_kitchen + - climate.ac_mainroom + + # din rail lights + - light.light_mainroom_mainlamp + - light.light_mainroom_spotlights + - light.light_mainroom_pc_spotlight + # balcony + - light.light_balcony_spotlight + # bedroom + - light.light_bedroom_spotlights + - light.light_bedroom_nightlight_left + - light.light_bedroom_nightlight_right + - light.light_bedroom_pc_spotlight + - light.light_bedroom_mainlight + - light.light_bedroom_pc_nightlight + # bath + - light.light_bath + - light.light_bath_shelf + # shower + - light.light_shower_main_spotlights + - light.light_shower_shower_spotlights + - light.light_shower_shelf_light + # hall + - light.light_hall_long + # childroom + - light.light_childroom_main + - light.light_childroom_spotlights + - light.light_childroom_bed + # kitchen + - light.light_kitchen_spotlights + - light.light_kitchen_table_lamp + - light.light_kitchen_bar_lamp + - light.light_kitchen_set + + # ventilation + - fan.ventilation_shower + - fan.ventilation_bath + + # bedside lamps + - light.danil_bedside_lamp_rgbww_light + - light.svetlana_bedside_lamp_rgbww_light + + # mirrors and heaters + - switch.shower_mirror + - switch.switch_mirror_shower_heater + - switch.light_bath_mirror + - switch.switch_mirror_bath_heater + + # media + - media_player.bedroom_tv + - media_player.kitchen_tv + - media_player.mainroom_tv + + # alice + - media_player.yandex_station_m00r46000m53db + - media_player.yandex_station_m00fhe2003seqr + - media_player.yandex_station_ly00000000000008674600007209d3f5 + - media_player.yandex_station_ff98f029ab48468d28c557be + - media_player.yandex_station_u003jc100phn9k + return_entities: + # - light.light_kitchen_set + + thremostats: + - climate.radiator_childroom + - climate.radiator_mainroom + - climate.radiator_kitchen + - climate.radiator_bedroom + - climate.radiator_bedroom_balcony + global_dependencies: + - globals + diff --git a/appdaemon/settings/apps/presence/purifier._yaml b/appdaemon/settings/apps/presence/purifier._yaml new file mode 100644 index 0000000..1bf70a6 --- /dev/null +++ b/appdaemon/settings/apps/presence/purifier._yaml @@ -0,0 +1,9 @@ +purifier_timer: + module: purifier + class: Purifier + on_time: "06:45:00" + off_time: "23:00:00" + entity: fan.xiaomi_miio_device + ha_panel: alarm_control_panel.ha_alarm + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/purifier.py b/appdaemon/settings/apps/presence/purifier.py new file mode 100644 index 0000000..9c752e6 --- /dev/null +++ b/appdaemon/settings/apps/presence/purifier.py @@ -0,0 +1,107 @@ +import appdaemon.plugins.hass.hassapi as hass +import globals +import datetime + +# +# Purifier controller +# Turn on and off purifier by light sensor and timers +# +# Args: +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class Purifier(hass.Hass): + away_fan_speed = 8 #1-14 + listen_event_handle_list = [] + mode = 'auto' + + def initialize(self): + if not globals.check_args(self, ["on_time", "off_time"]): + return + on_time = self.parse_time(self.args["on_time"]) + off_time = self.parse_time(self.args["off_time"]) + + self.on_timer = self.run_daily(self.day_mode_timer_event, on_time) + self.off_timer = self.run_daily(self.night_mode_timer_event, off_time) + + self.listen_event_handle_list.append(self.listen_event(self.away_mode_event, "away_mode")) + self.listen_event_handle_list.append(self.listen_event(self.return_home_mode_event, "return_home_mode")) + self.listen_event_handle_list.append(self.listen_state(self.state_change, self.args['entity'], attribute='illuminance')) + + def state_change(self, entity, attribute, old, new, kwargs): + presence_state = self.get_state(self.args['ha_panel']) + if presence_state != "disarmed": + # self.log('Nobody home, ignoring illuminance changes.') + return + self.log("{} changed from {} to {}".format(attribute, old, new)) + if old > 0 and new == 0 and self.time_is_between(self.datetime(), self.args["on_time"], self.args["off_time"]): + self.night_mode() + elif old == 0 and new > 0 and self.time_is_between(self.datetime(), self.args["on_time"], self.args["off_time"]): + self.auto_mode() + #self.auto_mode() + + def return_home_mode_event(self, event_id, event_args, kwargs): + self.auto_mode() + + def away_mode_event(self, event_id, event_args, kwargs): + self.away_mode() + + def night_mode_timer_event(self, kwargs): + self.night_mode() + + def day_mode_timer_event(self, kwargs): + self.auto_mode() + + + def away_mode(self): + self.mode='away' + self.log('Set purifier mode to favorite, speed: {}'.format(self.away_fan_speed)) + if 'entity' in self.args: + self.call_service("fan/set_speed", entity_id = self.args['entity'], speed='favorite') + self.call_service("fan/xiaomi_miio_set_favorite_level", entity_id = self.args['entity'], level=self.away_fan_speed) + self.call_service("fan/xiaomi_miio_set_led_on", entity_id = self.args['entity']) + # self.call_service("fan/xiaomi_miio_set_buzzer_on", entity_id = self.args['entity']) + + def auto_mode(self): + self.mode='auto' + self.log('Set purifier mode to auto.') + if 'entity' in self.args: + self.call_service("fan/set_speed", entity_id = self.args['entity'], speed='auto') + self.call_service("fan/xiaomi_miio_set_led_on", entity_id = self.args['entity']) + # self.call_service("fan/xiaomi_miio_set_buzzer_on", entity_id = self.args['entity']) + + def night_mode(self): + self.mode='night' + self.log('Set purifier mode to silent.') + if 'entity' in self.args: + self.call_service("fan/xiaomi_miio_set_buzzer_off", entity_id = self.args['entity']) + self.call_service("fan/set_speed", entity_id = self.args['entity'], speed='silent') + self.call_service("fan/xiaomi_miio_set_led_off", entity_id = self.args['entity']) + + + def time_is_between( + hass, target_dt: datetime, start_time: str, + end_time: str) -> bool: + """Generalization of AppDaemon's now_is_between method.""" + start_time_dt = hass.parse_time(start_time) # type: datetime + end_time_dt = hass.parse_time(end_time) # type: datetime + start_dt = target_dt.replace( + hour=start_time_dt.hour, + minute=start_time_dt.minute, + second=start_time_dt.second) + end_dt = target_dt.replace( + hour=end_time_dt.hour, + minute=end_time_dt.minute, + second=end_time_dt.second) + + if end_dt < start_dt: + # Spans midnight + if target_dt < start_dt and target_dt < end_dt: + target_dt = target_dt + timedelta(days=1) + end_dt = end_dt + timedelta(days=1) + return start_dt <= target_dt <= end_dt \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/report_action.py b/appdaemon/settings/apps/presence/report_action.py new file mode 100644 index 0000000..17f5918 --- /dev/null +++ b/appdaemon/settings/apps/presence/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/report_action.yaml b/appdaemon/settings/apps/presence/report_action.yaml new file mode 100644 index 0000000..3327852 --- /dev/null +++ b/appdaemon/settings/apps/presence/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/tags_presence.py b/appdaemon/settings/apps/presence/tags_presence.py new file mode 100644 index 0000000..0614872 --- /dev/null +++ b/appdaemon/settings/apps/presence/tags_presence.py @@ -0,0 +1,136 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# Tags counter +# This app count 'at home' BLE tags and set home presence state. +# tag names must start from 'tag_'. Room presence sensors will be generated. +# +# Args: +# tag_prefix = tag prefix to search (in my case 'sensor.tag_') +# sensor_prefix = sensor prefix for room presence sensor (ex: if prefix 'sensor.presence_', than sensor name will be 'sensor.presence_hall') +# rooms = rooms list, where tags will be discovered (sensor will be created for each room) +# constraint_prefix (input_boolean) = constraint for tag (ex: input_boolean.tag_monster) +# +# Release Notes +# +# Version 1.0: +# Initial Version +class TagsPresence(hass.Hass): + tags = [] + constraint_list = [] + tag_by_room_cache = {} + def initialize(self): + if 'tag_prefix' not in self.args or 'sensor_prefix' not in self.args or 'rooms' not in self.args or 'constraint_prefix' not in self.args: + self.error("Please provide tag_prefix, sensor_prefix, constraint_prefix and rooms in config!") + return + self.listen_tags() + # self.listen_event(self.ha_event, "plugin_started") + self.refresh_presence_state() + + # def ha_event(self, event_name, data, kwargs): + # self.run_in(self.refresh_presence_state_timer, 10) + + def refresh_presence_state_timer(self, kwargs): + self.refresh_presence_state() + + def sensor_trigger(self, entity, attribute, old, new, kwargs): + self.refresh_presence_state() + + def constraint_trigger(self, entity, attribute, old, new, kwargs): + self.refresh_presence_state() + + def listen_tags(self): + self.fill_tags_and_constraints() + for tag in self.tags: + self.listen_state(self.sensor_trigger, tag) + for constraint in self.constraint_list: + self.listen_state(self.constraint_trigger, constraint) + + def refresh_presence_state(self): + tags_by_room = self.get_tags_state_by_room(self.tags, self.constraint_list) + tags_by_person = self.get_tags_state_by_person(self.tags, self.constraint_list) + homePresence = 'off' + for room in self.args['rooms']: + sensor = '{}{}'.format(self.args['sensor_prefix'],room) + if room in tags_by_room and len(tags_by_room[room]) > 0: + self.create_sensor(sensor, 'on') + homePresence = 'on' + else: + self.create_sensor(sensor, 'off') + + for person in tags_by_person: + if len(tags_by_person[person])>0 and tags_by_person[person] != 'not home': + attr = self.get_state(person, attribute='all').get('attributes', None) + # self.log(attr) + # attr['state'] = tags_by_person[person][0] + + # disable set person state + # self.set_state(person, state = tags_by_person[person][0], attributes = attr) + + homeSensor = '{}{}'.format(self.args['sensor_prefix'],'home') + self.create_sensor(homeSensor, homePresence) + + def get_tags_state_by_room(self, tags, constraint_list): + tags_by_room = {} + for tag in tags: + constraint = '{}{}'.format(self.args['constraint_prefix'],tag.replace(self.args['tag_prefix'],'')) + if constraint in constraint_list and not self.constrain_input_boolean(constraint): + continue + room = self.get_state(tag) + if room not in tags_by_room: + tags_by_room[room] = [] + tags_by_room[room].append(tag) + return tags_by_room + + def get_tags_state_by_person(self, tags, constraint_list): + tags_by_person = {} + for tag in tags: + constraint = '{}{}'.format(self.args['constraint_prefix'],tag.replace(self.args['tag_prefix'],'')) + if constraint in constraint_list and not self.constrain_input_boolean(constraint): + continue + tagName = tag.replace(self.args['tag_prefix'],'') + if '_' in tagName: + tagName = tagName.split('_')[0] + person = '{}{}'.format(self.args['person_prefix'], tagName) + person_state = self.get_state(person) + if person_state != None: + # self.log('{}: {}'.format(person, person_state)) + if person not in tags_by_person: + tags_by_person[person] = [] + tags_by_person[person].append(self.get_state(tag)) + return tags_by_person + + # if person_state in self.get_state('person'): + # self.log('{} found'.format(person)) + # else: + # self.log('{} not found'.format(person)) + + # self.log('{}: {}'.format(person, person_state)) + # tag_state = self.get_state(tag) + # if room not in tags_by_room: + # tags_by_room[room] = [] + # tags_by_room[room].append(tag) + # return tags_by_room + + def fill_tags_and_constraints(self): + self.tags = [] + self.constraint_list = [] + devices = self.get_state() + for tag in devices: + if not tag.startswith(self.args['tag_prefix']): + continue + self.tags.append(tag) + + constraint = '{}{}'.format(self.args['constraint_prefix'],tag.replace(self.args['tag_prefix'],'')) + if constraint in devices and constraint not in self.constraint_list: + self.constraint_list.append(constraint) + + def create_sensor(self, entity_id, state, attributes = None): + current_state = self.get_state(entity_id) + if (current_state == state): + return + # self.log('Updating {} state ({}->{})'.format(entity_id, current_state, state)) + if attributes == None: + attributes = {} + attributes['description'] = 'Created and updated from appdaemon (tags_presence)' + self.set_state(entity_id, state = state, attributes = attributes) \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/tags_presence.yaml b/appdaemon/settings/apps/presence/tags_presence.yaml new file mode 100644 index 0000000..66bbd86 --- /dev/null +++ b/appdaemon/settings/apps/presence/tags_presence.yaml @@ -0,0 +1,16 @@ +################################# +# HOME PRESENSE # +################################# +tags_presence: + module: tags_presence + class: TagsPresence + tag_prefix: sensor.tag_ + sensor_prefix: sensor.presence_ + constraint_prefix: input_boolean.tag_ + person_prefix: person. + rooms: + - kladovka + - bedroom + - mainroom + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/presence/water_action.py b/appdaemon/settings/apps/presence/water_action.py new file mode 100644 index 0000000..83f022e --- /dev/null +++ b/appdaemon/settings/apps/presence/water_action.py @@ -0,0 +1,107 @@ +import appdaemon.plugins.hass.hassapi as hass +import datetime + +# +# Water control application. +# Controls water valve when all leave and no water needed devices is working. +# +# Args: +# water_valve = water valve entity +# notify - notify entity to send message +# constraint (optional) = input_boolean to enable\disable this automation +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class WaterValveControl(hass.Hass): + standby_power_limit = 15 + current_wait_cycle = 0 + wait_cycles = 2 + timer = None + listen_event_handle_list = [] + timers = [] + + def initialize(self): + if 'water_valve' not in self.args or 'water_devices' not in self.args or 'notify' not in self.args: + self.error("Please provide water_valve, water_devices and notify in config!") + return + + self.listen_event_handle_list.append(self.listen_event(self.away_mode, "away_mode")) + self.listen_event_handle_list.append(self.listen_event(self.return_home_mode, "return_home_mode")) + self.log(self.get_turned_on_devices()) + + def return_home_mode(self, event_id, event_args, kwargs): + self.timer = None + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.turn_on(self.args['water_valve']) + for device in self.args['water_devices']: + self.turn_on(device) + self.cancel_current_timer() + + def away_mode(self, event_id, event_args, kwargs): + self.timer = None + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + powered_on = self.get_turned_on_devices() + self.log('away entities: {}'.format(powered_on)) + if (len(powered_on) > 0): + device_names = "" + for device in powered_on: + if device_names != '': + device_names += ", " + name = self.friendly_name(device) + if name is None: + name = device + device_names += name + self.notify('В доме остались требующие воды устройства ({}), подача воды не будет отключена.'.format(device_names), name = self.args['notify']) + self.cancel_current_timer() + self.timers.append(self.run_every(self.wait_when_device_is_done, self.datetime()+datetime.timedelta(seconds=5), 5*60)) + else: + self.notify('Отключаю подачу воды.', name = self.args['notify']) + self.turn_off(self.args['water_valve']) + # for device in self.args['water_devices']: + # self.turn_off(device) + + def wait_when_device_is_done(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + powered_on = self.get_turned_on_devices() + self.log('still powered on: {}'.format(powered_on)) + if len(powered_on) == 1: + self.current_wait_cycle = 0 + elif len(powered_on) == 0 and self.current_wait_cycle=self.wait_cycles: + self.current_wait_cycle = 0 + self.notify('Устройства требующие воды закончили свою работу, отключаю подачу воды.', name = self.args['notify']) + self.turn_off_water_devices() + self.cancel_current_timer() + + def turn_off_water_devices(self): + self.log("Turning off water devices.") + self.turn_off(self.args['water_valve']) + for device in self.args['water_devices']: + if 'switch' in device: + self.log('switching off: {}'.format(device)) + self.turn_off(device) + + def get_turned_on_devices(self): + powered_on_devices = [] + devices = self.args['water_devices'] + for device in devices: + state = self.get_state(device) + self.log('{} state: {}'.format(device, state)) + if state == 'off' or state == None: + continue + powered_on_devices.append(device) + return powered_on_devices + + def cancel_current_timer(self): + for timer in self.timers: + self.log('canceling timer') + self.cancel_timer(timer) + self.timers = [] diff --git a/appdaemon/settings/apps/presence/water_action.yaml b/appdaemon/settings/apps/presence/water_action.yaml new file mode 100644 index 0000000..2a4cb67 --- /dev/null +++ b/appdaemon/settings/apps/presence/water_action.yaml @@ -0,0 +1,14 @@ +################################# +# HOME PRESENSE # +################################# +water_action: + module: water_action + class: WaterValveControl + water_valve: switch.watercontrol_valves + notify: telegram + constraint: input_boolean.water_control + water_devices: + - binary_sensor.washer_running # стиралка + - binary_sensor.hc_dishwasher_running # посудомойка + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/router/keenetic.py b/appdaemon/settings/apps/router/keenetic.py new file mode 100644 index 0000000..d6fb4c3 --- /dev/null +++ b/appdaemon/settings/apps/router/keenetic.py @@ -0,0 +1,265 @@ +import appdaemon.plugins.hass.hassapi as hass +import requests +from datetime import datetime + +class TelegramInternetControl(hass.Hass): + """ + Управление интернетом через Telegram команды. + Поддерживает множественные MAC-адреса. + """ + + def initialize(self): + """ + Инициализация приложения. + """ + self.log("Telegram Internet Control initialized") + + # Конфигурация из apps.yaml + self.api_url = self.args.get("api_url", "http://localhost:8000/run") + self.api_user = self.args.get("api_user", "api_user") + self.api_password = self.args.get("api_password", "strong_password") + + # Команды Telegram + self.block_command = self.args.get("block_command", "Коля наказан").lower() + self.unblock_command = self.args.get("unblock_command", "Включи Коле интернет").lower() + + # MAC адреса для управления (массив) + self.target_macs = self.args.get("target_macs", []) + if isinstance(self.target_macs, str): + self.target_macs = [self.target_macs] + + # Разрешенные user_id (безопасность) + self.allowed_users = self.args.get("allowed_users", []) + if isinstance(self.allowed_users, int): + self.allowed_users = [self.allowed_users] + + # Entity для прослушивания событий Telegram + self.telegram_event_entity = self.args.get( + "telegram_event_entity", + "event.home_assistant_fonvizina_update_event" + ) + + # Сервис для отправки ответов в Telegram + self.notify_service = self.args.get("notify_service", "notify/telegram") + + # Сохраняем последнее обработанное время (для пропуска дублей) + self.last_processed_time = None + + # Слушаем изменения состояния события Telegram + self.listen_state( + self.on_telegram_event, + self.telegram_event_entity + ) + + self.log(f"Listening to: {self.telegram_event_entity}") + self.log(f"Block command: '{self.block_command}'") + self.log(f"Unblock command: '{self.unblock_command}'") + self.log(f"Target MACs: {self.target_macs}") + self.log(f"Allowed users: {self.allowed_users}") + self.listen_event(self.yandex_intent, "yandex_intent") + + def yandex_intent(self, event_id, event_args, kwargs): + self.log("{} {}".format(event_id, event_args)) + text = event_args['text'] + room = event_args['room'] + alice = event_args['entity_id'] + if text == self.block_command: + self.handle_block_command('Monster1025') + + def say(self, entity_id, command): + self.log('command = {}'.format(command)) + self.call_service('media_player/play_media', entity_id=entity_id, media_content_type='text', media_content_id=command) + + def on_telegram_event(self, entity, attribute, old, new, kwargs): + """ + Обработка события Telegram. + """ + try: + # Получаем полный объект состояния с атрибутами + state_data = self.get_state(entity, attribute="all") + + if state_data is None: + self.log("State data is None") + return + + # Получаем атрибуты + attributes = state_data.get("attributes", {}) + + if not attributes: + self.log("No attributes in state data") + return + + # Проверяем тип события (должно быть telegram_text) + event_type = attributes.get("event_type", "") + if event_type != "telegram_text": + return + + # Получаем текст сообщения + text = attributes.get("text", "").strip() + if not text: + return + + # Получаем user_id отправителя + user_id = attributes.get("user_id") + from_first = attributes.get("from_first", "Unknown") + + # Получаем время события для защиты от дублей + event_time = attributes.get("date", "") + + # Пропускаем если уже обработали это событие + if event_time == self.last_processed_time: + self.log(f"Skipping duplicate event: {event_time}") + return + + self.last_processed_time = event_time + + self.log(f"Telegram message from {from_first} (user_id: {user_id}): '{text}'") + + # Проверяем, разрешен ли пользователь + if user_id not in self.allowed_users: + self.log(f"User {user_id} not in allowed list. Ignoring.") + return + + # Обрабатываем команды (регистронезависимо) + text_lower = text.lower() + + if text_lower == self.block_command: + self.handle_block_command(from_first) + elif text_lower == self.unblock_command: + self.handle_unblock_command(from_first) + + except Exception as e: + self.log(f"Error processing Telegram event: {str(e)}") + self.log(f"State data: {state_data if 'state_data' in locals() else 'N/A'}") + + def handle_block_command(self, user_name): + """ + Блокировка интернета для всех MAC-адресов. + """ + self.log(f"Blocking internet for {user_name}") + + if not self.target_macs: + self.send_telegram_message(f"❌ {user_name}, не настроены MAC-адреса для блокировки!") + self.log("No MAC addresses configured") + return + + success_count = 0 + failed_macs = [] + + for mac in self.target_macs: + # Формируем команду для роутера + command = f"ip hotspot host {mac} schedule schedule1" + self.log(f"Sending command: {command}") + + if self.send_to_router(command): + success_count += 1 + else: + failed_macs.append(mac) + command = "system configuration save" + self.log(f"Sending command: {command}") + cmd = self.send_to_router(command) + + # Формируем отчет + if success_count == len(self.target_macs): + self.send_telegram_message( + f"✅ {user_name}, интернет заблокирован для {success_count} устр-в!" + ) + self.log(f"Internet blocked successfully for {success_count} devices") + elif success_count > 0: + self.send_telegram_message( + f"⚠️ {user_name}, частично заблокировано: {success_count}/{len(self.target_macs)}. " + f"Не удалось: {', '.join(failed_macs)}" + ) + self.log(f"Partial block: {success_count}/{len(self.target_macs)}") + else: + self.send_telegram_message(f"❌ {user_name}, ошибка при блокировке интернета!") + self.log("Failed to block internet") + + def handle_unblock_command(self, user_name): + """ + Разблокировка интернета для всех MAC-адресов. + """ + self.log(f"Unblocking internet for {user_name}") + + if not self.target_macs: + self.send_telegram_message(f"❌ {user_name}, не настроены MAC-адреса для разблокировки!") + self.log("No MAC addresses configured") + return + + success_count = 0 + failed_macs = [] + + for mac in self.target_macs: + # Формируем команду для роутера + command = f"ip hotspot host {mac} schedule schedule0" + self.log(f"Sending command: {command}") + cmd = self.send_to_router(command) + + if cmd: + success_count += 1 + else: + failed_macs.append(mac) + command = "system configuration save" + self.log(f"Sending command: {command}") + cmd = self.send_to_router(command) + + # Формируем отчет + if success_count == len(self.target_macs): + self.send_telegram_message( + f"✅ {user_name}, интернет включен для {success_count} устр-в!" + ) + self.log(f"Internet unblocked successfully for {success_count} devices") + elif success_count > 0: + self.send_telegram_message( + f"⚠️ {user_name}, частично включено: {success_count}/{len(self.target_macs)}. " + f"Не удалось: {', '.join(failed_macs)}" + ) + self.log(f"Partial unblock: {success_count}/{len(self.target_macs)}") + else: + self.send_telegram_message(f"❌ {user_name}, ошибка при включении интернета!") + self.log("Failed to unblock internet") + + def send_to_router(self, command): + """ + Отправка команды на роутер через API. + """ + try: + response = requests.post( + self.api_url, + json={"command": command}, + auth=(self.api_user, self.api_password), + timeout=10 + ) + + if response.status_code == 200: + result = response.json() + exit_code = result.get("exit_code", -1) + + if exit_code == 0: + return True + else: + stderr = result.get('stderr', '') + self.log(f"Router command failed with exit code: {exit_code}") + self.log(f"Stderr: {stderr}") + return False + else: + self.log(f"API request failed with status: {response.status_code}") + self.log(f"Response: {response.text}") + return False + + except Exception as e: + self.log(f"Error sending command to router: {str(e)}") + return False + + def send_telegram_message(self, message): + """ + Отправка ответа в Telegram. + """ + try: + self.call_service( + self.notify_service, + message=message + ) + self.log(f"Telegram message sent: {message}") + except Exception as e: + self.log(f"Error sending Telegram message: {str(e)}") \ No newline at end of file diff --git a/appdaemon/settings/apps/router/keenetic.yaml b/appdaemon/settings/apps/router/keenetic.yaml new file mode 100644 index 0000000..bbf040e --- /dev/null +++ b/appdaemon/settings/apps/router/keenetic.yaml @@ -0,0 +1,27 @@ +keenetic: + module: keenetic + class: TelegramInternetControl + + # URL вашего API (адрес контейнера с API) + api_url: "http://192.168.1.6:8001/run" + api_user: !secret router_username + api_password: !secret router_password + + # Команды Telegram (регистронезависимые) + block_command: "Коля наказан" + unblock_command: "Включи Коле интернет" + + # MAC адреса для управления (массив) + target_macs: + - "cc:4b:73:04:ed:34" + - "b8:41:d9:d8:3f:58" + + # Разрешенные user_id из Telegram (безопасность!) + allowed_users: + - 123622180 + + # Entity для прослушивания событий + telegram_event_entity: "event.home_assistant_fonvizina_update_event" + + # Сервис для отправки уведомлений в Telegram + notify_service: "notify/telegram" \ No newline at end of file diff --git a/appdaemon/settings/apps/security/fire_sensors.py b/appdaemon/settings/apps/security/fire_sensors.py new file mode 100644 index 0000000..4661629 --- /dev/null +++ b/appdaemon/settings/apps/security/fire_sensors.py @@ -0,0 +1,48 @@ +import appdaemon.plugins.hass.hassapi as hass +import fnmatch + +# +# Fire 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 FireSensorsReport(hass.Hass): + def initialize(self): + if 'sensor_parts' not in self.args or 'notify' not in self.args: + self.error("Please provide sensor_parts, ha_panel and notify in config!") + return + + sensor_parts = self.args['sensor_parts'] + + all_sensors = self.get_state() + for entity_id in all_sensors: + for part in sensor_parts: + if fnmatch.fnmatch(entity_id, part): + self.log(' [+] listening for security sensor {}'.format(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": + self.log('Fire sensor is triggered!!!') + self.alarm_triggered(entity, old, new) + + def alarm_triggered(self, entity, old, new): + self.log('alarm triggered') + + 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/fire_sensors.yaml b/appdaemon/settings/apps/security/fire_sensors.yaml new file mode 100644 index 0000000..11adfdb --- /dev/null +++ b/appdaemon/settings/apps/security/fire_sensors.yaml @@ -0,0 +1,9 @@ +fire_sensors: + module: fire_sensors + class: FireSensorsReport + sensor_parts: + - binary_sensor.smoke_detector_*_smoke + constraint: input_boolean.fire_sensors_report + 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..06f606b --- /dev/null +++ b/appdaemon/settings/apps/security/security_sensors.py @@ -0,0 +1,59 @@ +import appdaemon.plugins.hass.hassapi as hass +import fnmatch + +# +# 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_parts' not in self.args or 'ha_panel' not in self.args or 'notify' not in self.args: + self.error("Please provide sensor_parts, ha_panel and notify in config!") + return + + sensor_parts = self.args['sensor_parts'] + + all_sensors = self.get_state() + for entity_id in all_sensors: + for part in sensor_parts: + if fnmatch.fnmatch(entity_id, part): + self.log(' [+] listening for security sensor {}'.format(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 + + vacuum_state = "docked" + if "vacuum" in self.args: + vacuum_state = self.get_state(self.args["vacuum"]) + #Если работает пылесос и сработал датчик движения + if vacuum_state == 'cleaning' and 'binary_sensor.motion' in entity: + self.log('Motion sensor is triggered, but vacuum is cleaning.') + 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..c2f1ac7 --- /dev/null +++ b/appdaemon/settings/apps/security/security_sensors.yaml @@ -0,0 +1,14 @@ +security_sensors: + module: security_sensors + class: SecuritySensorsReport + sensor_parts: + - binary_sensor.motion_*_occupancy + - binary_sensor.door_*_contact + - binary_sensor.smoke_detector_*_smoke + - binary_sensor.water_leak_*_leak + constraint: input_boolean.security_sensors_report + ha_panel: alarm_control_panel.ha_alarm + vacuum: vacuum.dreame_p2027_6b1e_robot_cleaner + notify: telegram + global_dependencies: + - globals \ No newline at end of file diff --git a/appdaemon/settings/apps/security/water_leak.py b/appdaemon/settings/apps/security/water_leak.py new file mode 100644 index 0000000..4c50988 --- /dev/null +++ b/appdaemon/settings/apps/security/water_leak.py @@ -0,0 +1,62 @@ +import appdaemon.plugins.hass.hassapi as hass + +# +# Water leak detector +# close water valve and send a report +# +# Args: +# +# sensor = water leak sensor group +# valve = water valve - app will close it, when leak is detected +# notify = notify name to send notification +# +# None +# +# Release Notes +# +# Version 1.0: +# Initial Version + +class WaterLeak(hass.Hass): + ringtone = 2 + gw_mac = '' + sensors = [] + def initialize(self): + if 'sensor' not in self.args or 'valve' not in self.args or 'notify' not in self.args: + self.error("Please provide sensor, valve and notify in config!") + return + self.sensors = self.get_water_leak_sensors() + self.log("leak sensors: {}".format(self.sensors)) + for sensor in self.sensors: + self.listen_state(self.water_leak, sensor) + + def get_water_leak_sensors(self): + sensors = [] + devices = self.get_state() + for sensor in devices: + if not sensor.startswith(self.args['sensor_prefix']): + continue + sensors.append(sensor) + return sensors + + def water_leak(self, entity, attribute, old, new, kwargs): + if new == "on": + self.send_alarm(entity) + + def send_alarm(self, entity_id): + self.log('water leak alarm triggered!') + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + + #close water valve + self.turn_off(self.args['valve']) + ## to ensure that water is closed. It is important!!! + self.call_service("mqtt/publish", topic = 'home/watercontrol/bath/valve/set', payload = 'close') + + message = "ВНИМАНИЕ! В ваше отстутствие сработал датчик протечки воды ({})!!! Выключаю подачу воды.".format(self.friendly_name(entity_id)) + self.notify(message, name = self.args['notify']) + + message = "Состояние датчиков протечки:\n" + for entity_id in self.sensors: + message += '{} ({}) = {}\n'.format(self.friendly_name(entity_id), entity_id, self.get_state(entity_id)) + self.notify(message, name = self.args['notify']) \ No newline at end of file diff --git a/appdaemon/settings/apps/security/water_leak.yaml b/appdaemon/settings/apps/security/water_leak.yaml new file mode 100644 index 0000000..5687b16 --- /dev/null +++ b/appdaemon/settings/apps/security/water_leak.yaml @@ -0,0 +1,14 @@ +################################# +# WATER LEAK # +################################# +water_leak: + module: water_leak + class: WaterLeak + constraint: input_boolean.water_leak_control + sensor: group.water_leak_sensors + valve: switch.water_valve + sensor_prefix: binary_sensor.water_leak_ + notify: telegram + gw_mac: !secret aqara1_mac + global_dependencies: + - globals \ 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..26ea821 --- /dev/null +++ b/appdaemon/settings/apps/sensors/mosru.py @@ -0,0 +1,370 @@ +import sys +sys.path.insert(0, '/emp_mos_api') + +import globals +import datetime as dt +from automation import Automation, Base +from datetime import datetime, tzinfo, timedelta +from emp_mos_api.mos import MosAPI, Watercounter, Water, EmpServerException +#from mos_lib import MosAPI, Watercounter, Water, EmpServerException + +class WaterCounterSender(Automation): + timezone = '+03:00' + def initialize(self): + super().initialize() + self.run_daily(self.send_mosru_counters, dt.time(12, 00, 0)) + + def send_mosru_counters(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + self.log('Starting sending counters value to mos.ru') + new_values = [] + self.app.update_watercounters() + water_hot = self.app.water_hot + water_cold = self.app.water_cold + counters = [water_hot, water_cold] + + for counter in counters: + counter_type = counter['type'] + + checkup = counter['checkup'].replace(self.timezone,'') + checkup_date = datetime.strptime(checkup, '%Y-%m-%d') + + # подходит дата поверки + if checkup_date < datetime.now(): + self.error('Counter {} checkup date is expired at {}!'.format(counter['counterId'], checkup)) + continue + + our_sensor = "" + if counter_type == 1: + our_sensor = self.entity_ids['cold'] + elif counter_type == 2: + our_sensor = self.entity_ids['hot'] + real_value = int(float(self.get_state(our_sensor))) + self.log("Updating counter {} ({}) to real value: {}".format(counter['counterId'], our_sensor, real_value)) + new_values.append(Watercounter.serialize_for_send(counter, real_value)) + + if new_values: + try: + self.app.send_watercounters(new_values) + self.log('Counters values was sent to mos.ru') + except EmpServerException as err: + self.log('error sending: {}'.format(err)) + + +class PowerCounterSender(Automation): + timezone = '+03:00' + def initialize(self): + super().initialize() + if 'power_total' not in self.entity_ids: + self.log('Please specify power_total for PowerCounterSender.') + return + self.run_daily(self.send_mosru_counters, dt.time(14, 00, 0)) + # self.send_mosru_counters("") + + def send_mosru_counters(self, kwargs): + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + today = datetime.today() + if today.day < 15 or today.day > 26: + self.log("We can send power values between days 15 and 26.") + return + + power_total = round(float(self.get_state(self.entity_ids['power_total']))) + period = datetime.now().strftime('%Y-%m-%d') + + if 'threshold' in self.args and 'power_mosenergosbyt_total' in self.args: + power_mosenergosbyt_total = float(self.get_state(self.args['power_mosenergosbyt_total'])) + threshold = self.args['threshold'] + diff = power_total-power_mosenergosbyt_total + if diff None: + self.log('Updating water counters') + self.app.update_watercounters() + if 'hot' in self.entity_ids: + water_hot = self.app.water_hot + self.create_water_entity(self.entity_ids['hot'], water_hot) + if 'cold' in self.entity_ids: + water_cold = self.app.water_cold + self.create_water_entity(self.entity_ids['cold'], water_cold) + + def create_water_entity(self, entity_id, counter): + indication_max = 0 + indication_date = "" + for indication in counter['indications']: + current = float(indication['indication']) + if (current > indication_max): + indication_max = current + indication_date = indication['period'] + indication_date = indication_date.replace(self.timezone,'') + + counter_type = counter['type'] + checkup = counter['checkup'] + type_desc = "" + type_desc_ru = "" + if counter_type == 1: + type_desc = "cold" + type_desc_ru = "Холодная" + elif counter_type == 2: + type_desc = "hot" + type_desc_ru = "Горячая" + + icon = "mdi:av-timer" + checkup = checkup.replace(self.timezone,'') + checkup_date = datetime.strptime(checkup, '%Y-%m-%d') + + # подходит дата поверки + if (checkup_date - timedelta(days=31)) < datetime.now(): + icon = "mdi:alert-octagon" + + attributes = {} + attributes['unit_of_measurement'] = 'm3' + attributes['date'] = indication_date + attributes['checkup'] = checkup + attributes['icon'] = icon + attributes['friendly_name'] = "{}(mos.ru)".format(type_desc_ru) + attributes['description'] = 'Created and updated from appdaemon ({})'.format(__name__) + + self.set_state(entity_id, state = indication_max, attributes = attributes) + +class PowerCounterSensor(Automation): + timezone = '+03:00' + def initialize(self): + super().initialize() + self.timer = self.run_every(self.update_sensors, self.datetime()+timedelta(seconds=30), 1*60*60) + + def update_sensors(self, args) -> None: + self.log('Updating power counters') + self.app.update_power() + self.log('power data: {}'.format(self.app._power_counters)) + if 'power_total' in self.entity_ids: + power_total = self.app.power_total + self.create_power_entity(self.entity_ids['power_total'], power_total, 'kWh', 'Счетчик') + if 'power_balance' in self.entity_ids: + power_balance = self.app.power_balance + self.create_power_entity(self.entity_ids['power_balance'], power_balance, '₽', 'Баланс') + + def create_power_entity(self, entity_id, value, unit, description): + icon = "mdi:av-timer" + attributes = {} + attributes['unit_of_measurement'] = unit + attributes['icon'] = icon + attributes['friendly_name'] = "{}(mos.ru)".format(description) + attributes['description'] = 'Created and updated from appdaemon ({})'.format(__name__) + + self.set_state(entity_id, state = value, attributes = attributes) + + +class EpdBalanceSensor(Automation): + def initialize(self): + super().initialize() + self.timer = self.run_every(self.update_sensors, self.datetime()+timedelta(seconds=5), 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'] + friendly_name = self.args.get('friendly_name', 'Баланс (ЕПД)') + attributes = { + 'unit_of_measurement': '₽', + 'icon': 'mdi:bank-transfer', + 'date': datetime.now().strftime('%d.%m.%Y'), + 'unpaid': epd_description, + 'friendly_name': friendly_name, + 'description': 'Created and updated from appdaemon ({})'.format(__name__) + } + self.log('setting state: {}, state = {}, attributes = {}'.format(entity_id, epd_balance, attributes)) + self.set_state(entity_id, state = str(epd_balance), attributes = attributes) + + +class MosruBase(Base): + _api = None + _flat_id = None + _guid='4421a3fb0567714b27dec84d3c3862dc' + _app_version = '3.5.0.195 (102)' + _user_agent = 'okhttp/3.8.1' + _dev_user_agent = 'Android' + _timezone = '+03:00' + + def initialize(self): + super().initialize() + if not globals.check_properties(self,['login', 'pwd', 'token', 'paycode', 'session_id']): + return + self._api = MosAPI(token=self.properties['token'], + user_agent=self._user_agent, + guid=self._guid, + dev_user_agent=self._dev_user_agent, + dev_app_version=self._app_version, + timeout=6) + self._set_flat_id() + + def _set_flat_id(self): + ''' Set flat id by properties or pay code ''' + if 'flat_id' in self.properties: + self._flat_id = self.properties['flat_id'] + return + if (self._flat_id != None): + return + self._check_login() + flats = self._api.get_flats() + for flat in flats: + if (int(flat['paycode']) != self.properties['paycode']): + continue + self._flat_id = flat['flat_id'] + + def _check_login(self): + self._api.set_session(self.properties['session_id']) + # if not self._api.is_active(): + # self._api.login(self.properties['login'], self.properties['pwd']) + +class MosruPower(MosruBase): + _power_counters = None + + @property + def power_total(self): + if (self._power_counters == None): + self.update_power() + return self._power_counters['zones'][0]['value'] + + @property + def power_balance(self): + if (self._power_counters == None): + self.update_power() + return float(self._power_counters['balance']) + + def update_power(self) -> None: + self._check_login() + self._power_counters = self._api.get_electrocounters(self._flat_id) + + def send_electrocounters(self, new_values) -> None: + self._check_login() + self._api.send_electrocounters(self._flat_id, new_values) + + +class MosruWater(MosruBase): + _water_counters = None + + @property + def water_hot(self): + """Return the current state of counter.""" + if (self._water_counters == None): + self.update_watercounters() + hots_json = list(filter(lambda x: x['type'] == Water.HOT, self._water_counters)) + return hots_json[0] + + @property + def water_cold(self): + """Return the current state of counter.""" + if (self._water_counters == None): + self.update_watercounters() + colds_json = list(filter(lambda x: x['type'] == Water.COLD, self._water_counters)) + return colds_json[0] + + @property + def water_hot_value(self): + hot_value = Watercounter.last_value(self.water_hot) + return hot_value + + @property + def water_cold_value(self): + cold_value = Watercounter.last_value(self.water_cold) + return cold_value + + + def send_watercounters(self, new_values) -> None: + self._check_login() + self._api.send_watercounters(self._flat_id, new_values) + + def update_watercounters(self) -> None: + self._check_login() + self._water_counters = self._api.get_watercounters(self._flat_id)['counters'] + self.log(self._water_counters) + +class MosruEpd(MosruBase): + _epd_data = None + + @property + def epd_description(self) -> str: + if (self._epd_data == None): + self.update_epd() + + unpaid = [] + for date in self._epd_data: + epd = self._epd_data[date] + if epd['payment_status'] == 'PAID': + continue + unpaid.append('{}: {}'.format(date, epd['amount'])) + return ", ".join(unpaid) + + + @property + def epd_balance(self) -> float: + """Return the current state of counter.""" + if (self._epd_data == None): + self.update_epd() + balance = 0 + for date in self._epd_data: + epd = self._epd_data[date] + epd_is_paid = epd['payment_status'] == 'PAID' + if epd_is_paid: + continue + balance = balance - float(epd['amount'].replace(',','.')) + return balance + + def update_epd(self) -> None: + self._check_login() + data = {} + dateEnd = datetime.now().date() + dateStart = dateEnd - timedelta(6*365/12) + + date_start = dateStart.strftime('%d.%m.%Y') + date_end = dateEnd.strftime('%d.%m.%Y') + + epds = self._api.get_epd(self._flat_id, date_start, date_end) + for i in range(0, 3): #retries + if epds != []: + break + epds = self._api.get_epd(self._flat_id, date_start, date_end) + if len(epds) == 0: + self.log('epd not found for date: {}'.format(date_str)) + return + for epd in epds: + if epd['epd_type'] == 'DEBT': + continue + date_str = epd['period'] + data[date_str] = epd + # epd_total = epd['amount'] + # epd_is_paid = epd['is_paid'] + # self.log(" - Дата: {}, данные: {}.".format(date_str, epd)) + 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..7a66ce9 --- /dev/null +++ b/appdaemon/settings/apps/sensors/mosru.yaml @@ -0,0 +1,205 @@ + +################################# +# MOS.RU DATA RETRIEVER # +################################# +mosru_client: + class: MosruClient + module: mosru + properties: + login: !secret mosru_login + pwd: !secret mosru_pwd + token: !secret mosru_token + paycode: !secret mosru_paycode + flat_id: !secret mosru_flat_id + session_id: !secret mosru_session + global_dependencies: + - globals + +epd_sensor: + class: EpdBalanceSensor + module: mosru + friendly_name: Баланс (ЕПД) Алтуфьево + entity_ids: + epd_balance: sensor.epd_balance + app: mosru_client + dependencies: + - mosru_client + global_dependencies: + - globals + + +power_sensor: + class: PowerCounterSensor + module: mosru + entity_ids: + power_balance: sensor.power_mosenergosbyt_balance + power_total: sensor.power_mosenergosbyt_total + app: mosru_client + dependencies: + - mosru_client + global_dependencies: + - globals + +water_sensor: + class: WaterCounterSensor + module: mosru + entity_ids: + hot: sensor.counter_mosru_hot + cold: sensor.counter_mosru_cold + app: mosru_client + dependencies: + - mosru_client + global_dependencies: + - globals + +# water_sender: +# class: WaterCounterSender +# module: mosru +# entity_ids: +# hot: sensor.counter_hot +# cold: sensor.counter_cold +# app: mosru_client +# dependencies: +# - mosru_client +# global_dependencies: +# - globals + +# power_sender: +# class: PowerCounterSender +# module: mosru +# power_mosenergosbyt_total: sensor.power_mosenergosbyt_total +# threshold: 100 +# entity_ids: +# power_total: sensor.power_main_total +# app: mosru_client +# dependencies: +# - mosru_client +# global_dependencies: +# - globals + +#--------------------------------------------------------- +mosru_client_belorechenskaya: + class: MosruClient + module: mosru + properties: + login: !secret mosru_login + pwd: !secret mosru_pwd + token: !secret mosru_token + paycode: !secret mosru_belorechenskaya_paycode + flat_id: !secret mosru_belorechenskaya_flat_id + session_id: !secret mosru_session + global_dependencies: + - globals + +epd_sensor_belorechenskaya: + class: EpdBalanceSensor + module: mosru + friendly_name: Баланс (ЕПД) Белореченская + entity_ids: + epd_balance: sensor.epd_balance_belorechenskaya + app: mosru_client_belorechenskaya + dependencies: + - mosru_client_belorechenskaya + global_dependencies: + - globals + + +power_sensor_belorechenskaya: + class: PowerCounterSensor + module: mosru + entity_ids: + power_balance: sensor.power_mosenergosbyt_belorechenskaya_balance + power_total: sensor.power_mosenergosbyt_belorechenskaya_total + app: mosru_client_belorechenskaya + dependencies: + - mosru_client_belorechenskaya + global_dependencies: + - globals + +water_sensor_belorechenskaya: + class: WaterCounterSensor + module: mosru + entity_ids: + hot: sensor.counter_mosru_belorechenskaya_hot + cold: sensor.counter_mosru_belorechenskaya_cold + app: mosru_client_belorechenskaya + dependencies: + - mosru_client_belorechenskaya + global_dependencies: + - globals +#--------------------------------------------------------- +mosru_client_fonvizina: + class: MosruClient + module: mosru + properties: + login: !secret mosru_login + pwd: !secret mosru_pwd + token: !secret mosru_token + paycode: !secret mosru_fonvizina_paycode + flat_id: !secret mosru_fonvizina_flat_id + session_id: !secret mosru_session + global_dependencies: + - globals + +epd_sensor_mosru_fonvizina: + class: EpdBalanceSensor + module: mosru + friendly_name: Баланс (ЕПД) Фонвизина + entity_ids: + epd_balance: sensor.epd_balance_mosru_fonvizina + app: mosru_client_fonvizina + dependencies: + - mosru_client_fonvizina + global_dependencies: + - globals + +#--------------------------------------------------------- +mosru_client_milashenkova: + class: MosruClient + module: mosru + properties: + login: !secret mosru_login + pwd: !secret mosru_pwd + token: !secret mosru_token + paycode: !secret mosru_milashenkova_paycode + flat_id: !secret mosru_milashenkova_flat_id + session_id: !secret mosru_session + global_dependencies: + - globals + +epd_sensor_milashenkova: + class: EpdBalanceSensor + module: mosru + friendly_name: Баланс (ЕПД) Милашенкова + entity_ids: + epd_balance: sensor.epd_balance_milashenkova + app: mosru_client_milashenkova + dependencies: + - mosru_client_milashenkova + global_dependencies: + - globals + +power_sensor_milashenkova: + class: PowerCounterSensor + module: mosru + entity_ids: + power_balance: sensor.power_mosenergosbyt_milashenkova_balance + power_total: sensor.power_mosenergosbyt_milashenkova_total + app: mosru_client_milashenkova + dependencies: + - mosru_client_milashenkova + global_dependencies: + - globals + +water_sensor_milashenkova: + class: WaterCounterSensor + module: mosru + entity_ids: + hot: sensor.counter_mosru_milashenkova_hot + cold: sensor.counter_mosru_milashenkova_cold + app: mosru_client_milashenkova + dependencies: + - mosru_client_milashenkova + global_dependencies: + - globals +#--------------------------------------------------------- diff --git a/appdaemon/settings/apps/sensors/uniservice.py b/appdaemon/settings/apps/sensors/uniservice.py new file mode 100644 index 0000000..e7c0366 --- /dev/null +++ b/appdaemon/settings/apps/sensors/uniservice.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +import os +import sys +import requests +import globals +import datetime as dt +import appdaemon.plugins.hass.hassapi as hass +from datetime import datetime, tzinfo, timedelta +from bs4 import BeautifulSoup + +class EpdBalanceSensor(hass.Hass): + login = '' + pwd = '' + accounts = [] + def initialize(self): + # if not globals.check_properties(self,['login', 'pwd', 'accounts']): + # self.log('not enought params') + # return + self.login = self.args.get('login', '') + self.pwd = self.args.get('pwd', '') + self.accounts = self.args.get('accounts', []) + self.timer = self.run_every(self.update_sensors, self.datetime()+timedelta(seconds=1), 1*60*60) + self.run_daily(self.send_counters_callback, "11:00:00", constrain_days = "sun") + self.send_counters_callback(None) + + def to_balance(self, value_str): + value_str = value_str.replace(" ", "").replace(",", ".").replace("₽", "") + return -1 * float(value_str) + + def update_sensors(self, args) -> None: + entity_ids = self.args.get('entity_ids',[]) + session = self.get_auth_session(self.login, self.pwd) + payment_amounts = self.get_payment_amounts(session, self.accounts) + self.log(payment_amounts) + + fonvizina_epd_balance = self.to_balance(payment_amounts[0]['service_to_pay']) + if 'fonvizina_epd_balance' in entity_ids: + entity_id = entity_ids['fonvizina_epd_balance'] + attributes = { + 'unit_of_measurement': '₽', + 'icon': 'mdi:bank-transfer', + 'date': datetime.now().strftime('%d.%m.%Y'), + 'address': payment_amounts[0]['address'], + 'friendly_name': 'Баланс (ЕПД)', + 'description': 'Created and updated from appdaemon ({})'.format(__name__) + } + self.set_state(entity_id, state = str(fonvizina_epd_balance), attributes = attributes) + + fonvizina_trash_balance = self.to_balance(payment_amounts[0]['trash_to_pay']) + if 'fonvizina_trash_balance' in entity_ids: + entity_id = entity_ids['fonvizina_trash_balance'] + attributes = { + 'unit_of_measurement': '₽', + 'icon': 'mdi:bank-transfer', + 'date': datetime.now().strftime('%d.%m.%Y'), + 'address': payment_amounts[0]['address'], + 'friendly_name': 'Баланс Выввоз мусора', + 'description': 'Created and updated from appdaemon ({})'.format(__name__) + } + self.set_state(entity_id, state = str(fonvizina_trash_balance), attributes = attributes) + + fonvizina_kladovka_balance = self.to_balance(payment_amounts[1]['service_to_pay']) + if 'fonvizina_kladovka_balance' in entity_ids: + entity_id = entity_ids['fonvizina_kladovka_balance'] + attributes = { + 'unit_of_measurement': '₽', + 'icon': 'mdi:bank-transfer', + 'date': datetime.now().strftime('%d.%m.%Y'), + 'address': payment_amounts[1]['address'], + 'friendly_name': 'Баланс Кладовка', + 'description': 'Created and updated from appdaemon ({})'.format(__name__) + } + self.set_state(entity_id, state = str(fonvizina_kladovka_balance), attributes = attributes) + + def get_auth_session(self, phone, password): + data={ + 'AUTH_FORM': 'Y', + 'TYPE':'AUTH', + 'backurl': '/', + 'USER_LOGIN': phone, + 'USER_PASSWORD': password + } + r = requests.post('https://lkcab.ru/?login=yes', data=data, verify=False, allow_redirects=False) + print(r.cookies) + return r.cookies['PHPSESSID'] + + def get_payment_amounts(self, session, accounts): + cookies = dict(PHPSESSID=session) + results = [] + for account in accounts: + url = 'https://lkcab.ru/?accountID={}'.format(account) + r = requests.get(url, cookies=cookies, verify=False) + + result = {} + result['account'] = account + + doc = BeautifulSoup(r.content, 'html.parser') + address = doc.find("div", string="Вы собственник по адресу:").parent.find_all('div')[1].text + result['address'] = address + + service_to_pay = doc.find('div', string="ЖКУ, к оплате:").parent.find_all('div')[1].text.replace(' Р.', '') + result['service_to_pay'] = service_to_pay + + trash_to_pay = '0' + trash_to_pay_item = doc.find('div', string="Обращение с ТКО, к оплате:") + self.log(trash_to_pay_item) + if not trash_to_pay_item is None: + trash_to_pay = trash_to_pay_item.parent.find_all('div')[3].text.replace(' Р.', '') + result['trash_to_pay'] = trash_to_pay + results.append(result) + return results + + def send_counters_callback(self, kwargs) -> None: + if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): + return + current_day = dt.datetime.now().strftime("%a").lower() + if current_day != "sun": + self.log(f'Today is {current_day}, but we run only on sunday.') + return + + self.log('sending counters....') + session = self.get_auth_session(self.login, self.pwd) + self.send_counters(session) + return + + def send_counters(self, session) -> None: + cookies = dict(PHPSESSID=session) + account = self.accounts[0] + url = 'https://lkcab.ru/?accountID={}'.format(account) + r = requests.get(url, cookies=cookies, verify=False) + + url = 'https://lkcab.ru/counters/' + r = requests.get(url, cookies=cookies, verify=False) + doc = BeautifulSoup(r.content, 'html.parser') + bitrix_session = doc.find("input", id="sessid").get('value') + self.log('session: {}'.format(bitrix_session)) + + data = { + 'action':'set_meters', + 'sessid': bitrix_session, + } + needToSend = False + if 'heat_total' in self.args: + needToSend = True + value = self.get_state(self.args['heat_total']) + if value != None: + data['indiccur1[294174]'] = value + + if 'counter_cold_outer' in self.args: + needToSend = True + value = self.get_state(self.args['counter_cold_outer']) + if value != None: + data['indiccur1[294175]'] = value + + if 'counter_hot_outer' in self.args: + needToSend = True + value = self.get_state(self.args['counter_hot_outer']) + if value != None: + data['indiccur1[294173]'] = value + + if needToSend: + self.log('sending: {}'.format(data)) + r = requests.post("https://lkcab.ru/counters/", cookies=cookies, data=data, verify=False) + self.notify_tg("Показания отправлены в юнисервис. {}".format(data)) + self.log('done.') + + def notify_tg(self, msg): + if not 'notify' in self.args: + return + extra_data = {'parse_mode': 'html'} + self.notify(msg, name = self.args['notify'], data=extra_data) diff --git a/appdaemon/settings/apps/sensors/uniservice.yaml b/appdaemon/settings/apps/sensors/uniservice.yaml new file mode 100644 index 0000000..d835053 --- /dev/null +++ b/appdaemon/settings/apps/sensors/uniservice.yaml @@ -0,0 +1,22 @@ + +################################# +# MOS.RU DATA RETRIEVER # +################################# +uniservice_epd_sensor: + class: EpdBalanceSensor + module: uniservice + login: !secret uniservice_login + pwd: !secret uniservice_pwd + accounts: !secret uniservice_accounts + entity_ids: + fonvizina_epd_balance: sensor.fonvizina_epd_balance + fonvizina_trash_balance: sensor.fonvizina_trash_balance + fonvizina_kladovka_balance: sensor.fonvizina_kladovka_balance + heat_total: sensor.heat_total + counter_cold_outer: sensor.counter_cold_outer + counter_hot_outer: sensor.counter_hot_outer + notify: telegram_monster + global_dependencies: + - globals + + \ 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/test/ir/zanussi_ac.json b/appdaemon/settings/apps/test/ir/zanussi_ac.json new file mode 100644 index 0000000..10eed1b --- /dev/null +++ b/appdaemon/settings/apps/test/ir/zanussi_ac.json @@ -0,0 +1,468 @@ +{ + "manufacturer":"ROYAL", + "supportedModels":[ + "RC-G25HN" + ], + "supportedController":"Broadlink", + "commandsEncoding":"Base64", + "minTemperature":16, + "maxTemperature":31, + "precision":1, + "operationModes":[ + "heat", + "cool", + "dry", + "fan_only" + ], + "fanModes":[ + "silent", + "low", + "mid", + "high", + "turbo", + "auto" + ], + "commands":{ + "off":"JgDMAWA0ESMRIxEKEAsQDA8kEAoRDA8kECQQChEkEAoRChEjESMRChEjESMRChEKECQRChAMDwoRJBAKEQoRChEKEQoRChALEAoRChEKEQoRChEKEAoRChEKEQoRChEKEAsQJBALEAoRChEKEQoRChEKEAsQJBAKEQoRChEKEQoRChEKEAsQJBAkEAoRChEKEQoRCxAKEAsQCxAKEQoRChEKEQoRChALEAoRChEKEQoRChEKEQoQCxAKEQoRChEKEQoRChALEAoRChEKEQoRChEKEQoQCxAKEQoRChEjEQoRIxEjESMRAAj9ZTQQJBAkEAsQCxAKESQQChEKESMRIxEKESMRChALECQQJBEKECQQJBALEAoRJBAKEQoRJBAKEQoRChALEAsQCxAKEQoRChEKEQoRChALEAoRChEKEQoRChEKEAsQJBAKEQoRJBAkEAoRChEKEQoRChEKECQQJBAkEAsQCxALEAoRChEkECQQChEKEAsQCxAKEQoRChEKEQoRChEKEAsQCxAKEQoRDA8KEQoQCxAKEQoRChEKEQoQCxALEAoRChEKEQoRChALEAsQCg8NEAoRChEjEQoRIxEKEQoQCxALECQQJBAADQU=", + "heat":{ + "silent":{ + "16":"JgDMAV02ECQPJQ8MDwwPDA4mDwwODQ4mDiYODQ4mDg0PDA4mDyUODQ4mDiYODQ8LDyUPDA8MDwwOJg4NDg0ODQ4NDwsPDA8MDwoQDQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDiYOJg4NDg0ODQ4MDwwPDA4NDiYODQ4NDgwPDA8MDwwPDA4NDg0OJg8lDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4KEQwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPChEMDwwPDA4NDg0ODQ4lEAsPDA8lDyUPAAj+ZDYPJQ4mDg0ODQ4NDiYODA8MDyUQJA8MDyUPDA4NDiYOJg4NDiYOJg4NDg0OJg4NDg0OJg4MDwwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDgoRDQ4NDgwPJQ8MDwwPJQ8MDwwOJg4NDg0PDA4NDgwPDA8MDyUPJQ8lDyUPDA8MDwwODQ4NDiYODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4mDiYPJQ4NDiYPDA4NDiYOJg4ADQU=", + "17":"JgDMAV42DiYPJQ8MDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA4mDyUPDA8lDyUPDA8JESYODQ4NDg0OJg4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA4NDwwODQ4NDg0ODQ8LDyUPJRAJEQkSDA4NDg0ODQ4NDiUPDA8MDwwODQ4NDg0ODQ4MDwwPJQ8lDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwODQ4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8lDwwPDA4mDyUPAAj+YzYPJQ8lEAsPDA8MDyUPDA4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4NDg0OJg4MDwwPJRALDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg8MDg0OJg4NDg0ODQ4MDwwPDA4NDg0OJg4mDiYODQ4NDg0ODA8MDyUPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8lEAsPJQ8MDyUPDA4NDiYOJg8ADQU=", + "18":"JgDMAV42DyUOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDg0ODQ4mDiYODQ4mDiYODQ4NDiUPDA8MDwwPJQ8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDiYOJg4NDg0ODA8MDwwPDA8MDiYODQ4NDg0ODQ4NDgwPDA8MDwwOJg4mDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODg0NDg0ODQ4NDwwODA8MDg0ODQ8MDgwPDA8MDg0ODQ4NDg0ODA8lDwwPDA8lDyUPAAj+YzYPJg4mDg0ODA8MDyUQCw8MDiYOJg4NDiYODQ4NDiYPJQ8MDiYOJg4NDg0PJQ4NDgwPJg4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPJQ8MDwwOJg8MDg0OJg4NDg0ODQ4MDwwPDA4NDiYODQ4mDyUODQ4NDgwPDA8MDyUPDA4NDgoRDQ4NDg0ODQ4MEAsPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8lDyUPDA8MDyUPDA4NDiYOJg4ADQU=", + "19":"JgDMAV02DyUPJQ8MDwwPDA8lDwwODQ4mDiYODQ4mDg0ODQ4mDiYODQ4mDiYODQ4MDyUPDA8MDwwPJQ8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDiYOJg4KEQ0ODA8MDwwPDA4NDyUPDA4NDg0ODQ4NDgwPDA8MDg0OJg4mDg0ODQ4MDwwPDA8MDg0ODQ4NDwwODA8MDwwPDA4NDg0ODQ4KEQoRDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4mDg0ODQ4mDiUPAAkAYjYOJg4mDwwODQ4KESYODA8MDyUPJQ8MECQTCBIJDiYOJg8MDiYOJg8MDg0OJg4NDg0OJg4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQoRJQ8MDwwPJQ8MDwwPJQ8MDg0ODQ4NDgwPDA8MDwwODQ4mDiYPDA4NDg0ODQ4NDiYODA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MEAsPDA4NDwwODQ4mDgwPDA8MDyUPDA8MDiYOJg4ADQU=", + "20":"JgDMAV82DiYOJg4NDg0OChElDwwPDA8lDyUPDA8lDwwODQ4mDiYODQ4mDiYODQ4MDyUPDA8MDwwPJQ8MDg0ODQ4NDg0ODA8MDwwPDA8MDgoRDQ4NDg0ODA8MDwwODQ4NDiYOJg4NDgwPDA8MDwwPDA4NDiYODQ4NDgwPDA8MDwwODQ4NDg0OJg4mDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8lDwwPDA4mDiYOAAj/YzYPJQ8lDwwPDA8MDyUPChANDiYOJg8MDiYODA8MDyUPJg4MDyYOJQ8MDwkSJQ8MDg0OJg4NDwwODQ4KEQwPDA8MDwwODQ4NDhALDA8MDwwODQ4NDg0OJg4NDwwOJg4KEQwPJQ8MDwwODQ4NDgoRDA8MDyUPJQ8MDyUPDA8MDg0ODQ4NDiYODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDwsPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4mDiYOJg4mDwwODQ4NDiUPJQ8ADQU=", + "21":"JgDMAV82DyUOJg4NDg0ODA8lDwwPDA8lDyUPDA8lDg0ODQ4mDiYODQ4mDiYODQ4MDyYODQ4MEAsPJRALDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8MDg0ODQ4NDiUPJg8LDwwPDA4NDg0ODQ4NDiYODQ4NDgwPDA8QCw4MDQ4NDg0OJg4mDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4mDg0ODQ4mDiYOAAkAYjYPJQ4mDg0ODQ4NDiYODQ4MDyUPJQ8MDyUQCw8MDyUPJQ4NDiYOJg4NDg0OJg4NDg0OJQ8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDwwOJg4NDg0ODQ4NDgwPDA8MDwwOJg4NDiYODA8MDwwPDA8MDiYODQ4NDg0ODA8MEAsPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDgoRDQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4mDg0OJg8lDwkRDQ4NDiYOJg8ADQU=", + "22":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYOCBQlDiYODQ4MDyUPDA8MDwwOJg4NDg0ODQ8MDgwPDA8MDwwOChENDg0ODA8MDwwPDA4NDg0ODQ4NDiUPJQ8MDwwPDA8MDg0ODQ4NDiYPBxMMDwwPDA8MDg0ODQ4NDg0OJg8lDwsPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4mDg0ODQ4mDiYOAAkAYzUOJg4mDwwODQ4NDiYODA8MDyUPJQ8MDyUPDA8MDiYOJg8MDiYOJg4NDg0OJg4NDg0OJg8LDwwPCRIMDg0ODQ4NDg0OChEMDwwPDA4NDg0ODQ4NDgwPJg4MDwwOJg8MDg0OJg4NDg0ODQ4KEQwPDA8MDyUPDA4NDiYODQ4NDgwPDA8MDiYPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDBAkDyUPDA8lDwwODQ4NDiYOJg8ADQU=", + "23":"JgDMAV82DyUOJg4NDg0ODQ4lDwwPDA8lDyUPDA8lDwwODQ4mDiYODQ4mDiYODQ4NDiYODQ4NDgwPJQ8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0OChEMDwwPDA8MDiYOJg4NDg0ODQ4NDgwPChEMDyUPDA4NDg0ODQ4MDwwPDA8MDwwPJQ4mDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4mDwwODA8lDyUPAAj+ZDYOJg8lDg0ODQ4NDiYODQ4MDyUPJQ8NDiYODA8MDiYOJg8MDiYOJg8MDg0PLwQWBQ0OJg8LDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPJg4MDwwPJQ8MDwkRJg4NDg0ODQ4NDgwPDA8MDwwPDA4NDiYODQ4NDgwPDA8MDyUPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODQ4mDgwPDA8lDwwPDQ4MDiYOJg4ADQU=", + "24":"JgDMAV42DyUOJg8MDg0ODQ4mDg0ODQ4mDiYODQ8lDg0ODQ4mDiYOFAclDyUPDA8MDyUPDA4NDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDyUPJQ8MDg0ODQ4NDg0ODQ4NDiUPDA8MEAsPDA4NDg0ODQ4MDwwPJQ8lDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODwwMDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4KEQ0ODQ4NDgwPDA8MDwwODQ4mDg0ODQ4lDyYOAAj/YzYOJg4mDg0ODQ4MDyUPDA8MDyUPJQ8MDyUPDA8MDyUPJQ8MDiYPJQ8MDg0OJg8MDg0OJQ8KEQwPDA4NDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPJQ8MDwkRJg8MDg0OJg4NDg0PCw8MDwwPDA4NDiYOJg4mDwwODQ4NDg0ODQ4MDyUPDA8MDwwODg0NDgwPDA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4KEQwPDA8lDyUPJg4NDgwPDA8MDiYOJg4ADQU=", + "25":"JgDMAWA0ECQQJBAKEQoRChEjEQoRChEjESMRCRIjEQkSCREkECQQChEjESQQChEKESMRChEKEQkSIxEJEQoRChEKEQoRCRIJEgkRChEKEQoRCRIJEgkSCREKEQoRChEJEiMRIxEJEgkRChEKEQoRChEKESMRCRIJEgkRChEKEQoRCRIJEgkSIxEjEQkRChEKEQoRCRIJEgkRChEKEQoRChEJEgkSCREKEQoRChEJEgkSCRIJEQoRChEKEQkSCRIJEQoRChEKEQoRCRIJEgkRChEKEQoRCRIJEgkSCREkEAoRChEjESMRAAj8ZTQQJBAjEQoRCRIJEiMRCREKFCEQJBAKEyIQChEKESMRIxEJEiMRIxEJEgkRJBAKEQoRJBAKEQoRCRIJEgkRChEKEQoRChEJEgkSCREKEQoRChEJEgkSIxEJEgkRJBAKEQoRJBAKEQoRChEJEgkSCREKEQoRIxEkEAoRChEJEgkSCREKESQQChEKEQkSCRIJEgkRChEKEQoRCRIJEQoRChEKEQoRCRIJEgkRChEKEQoRCRIJEgkSCREKEQoRChEJEgkSCREKEQoRChEKEQkSCRIjEQkSIxEJEQoRChEKESMRIxEADQU=", + "26":"JgDMAV42DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA8lDwwPDA4mDiYODQ4mDyUPDA8MDiYODQ4NDgwPJQ8MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4KEQwPDA8MDyUPJQ8MDg0ODQ4NDgwPDBALDyUPDA4NDg0ODQ4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4KEgkRDA8MDwwPDA4NDgoSDA4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDgoRDQ4mDg0PDA4mDiYOAAkAYzUOJg4mDg0ODQ4NDiYPCw8MDyUPJQ8MDyUPDA8MDiYPJQ8JESYPJQ8MDwwOJg4NDg0OJg4NDg0ODA8MDwwODQ4NDwwODA8MDwwPDA4NDg0ODQ4NDgwPJQ8MDwwPJQ8MDg0OJg4NDg0ODQ4NDwsPDA8MDyUPDA8lDwwODQ8MDg0ODA8MDyUPDA8MDg0ODQ4NDgwPDBALDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8lDyUPDA8MDwwPDA4NDiYOJg4ADQU=", + "27":"JgDMAV02DyYOJg8MDgwPDA8lDwwPDA4mDyUPDA8lDwwPDA4mDiYODQ4mDiYODQ4NDiYODQ4MDwwPJQ8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDiYOJg4NDgoRDA8MDwwPDA4NDyUODQ4NDg0ODQ4MDwwPDA8MDg0OJg4mDg0ODQ4NDgwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDwsPDA8MDwwODQ4NDg0ODA8lDwwPDA8lDyUPAAkAYjUPJQ8mDg0OChEJEiUQCw8MDiYOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJg4NDg0OJg4NDg0ODA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPJQ8MDwwPJQ8MDg0OJg4NDgoSDA4MDwwPDA4NDg0ODQ4mDg0ODA8MDwwPDA8MDiYPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8lDwwODQ8MDg0ODQ4MDyUPJQ8ADQU=", + "28":"JgDMAV42DyUPJQ8MDwwODQ4mDg0ODQ4lDyYODA8lDwwPDA8lDyUPDA8lDyUPDA4NDiYODQ4NDg0OJg4NDgwPDA8MDwwODQ8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwODQ4NDg0ODA8MDyUPDA8MDg0ODQ4NDgwPDA8MDwwOJg4mDwwODQ4NDwwODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDg0ODQ8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwoRDA8MDg0ODQ4NDgwPDA8MDg0ODQ4mDg0ODA8lDyUPAAj/YzUQJA8lDwwPDA8MDyUPDA8MDyUPJQ8MDiYODQ4NDiYOJg4NDiYOJg4NDwwOJg4MDwwPJQ8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0OJg4NDgwPJg4NDgwPDA8MDwwODQ4NDiYOJg8MDgwPDBALDwwODQ4NDiYPDA4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODBALDg0ODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4mDiYOJg4mDiYOJg4mDwwOJg4ADQU=", + "29":"JgDMAV82DiYOJg4NDgwPDA8lDwwPDA8lDiYPDA4mDwwODQ4mDiYODQ4mDiYODQ4NDyUPDA4MDwoRJQ8MDwwODQ4NDgoRChEMDwwPDA4NDg0ODQ4NDwsPDA8MDwwODQ4NDiYPJQ4NDg0ODQ4MDwwPDA4NDiYODQ4NDg0PCw8MDwwPDA8MDg0OJg4mDg0ODQ4NDgwPDA8MDwwODQ4NDg4NDQ4NDgwPDA8MDwwODQ8MDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ8MDg0ODA8MDwwPDA4NDg0ODQ4mDgwPDA8lDyYOAAj+YzYPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDgwPJQ8MDwwPJQ8MDg0OChENDg0ODA8MDwwPDA4NDg0ODQ4NDwwODA8MDwwOJg4NDg0OJg4NDg0OJg4KEQwPDA8MDwwODQ4KEQ0OJg4NDwsPDA8MDwwODQ8MDiYODQ4NDgwPDA8MDg0ODQ4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwQCw8MDwwODQ4NDg0ODQ4NDgwQDA4KEQwODQ4NDg0ODA8MDwwODQ4mDg0OJg4mDiYOJg4mDg0OJg4ADQU=", + "30":"JgDMAV42DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDg0OCxAmDiYODQ4mDiYODQ4NDiYODA8MDwwPJQ8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4IEwoRDA8MDwwPDA4NDiYOJg8MDg0PDA4MDwwPDA8MDiYODQ4NDg0ODQ4NDg0ODA8MDwwPJQ4mDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8JEQ0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4mDg0ODA8lDyUQAAj9ZDYOJg4mDg0ODQ4NDiYODQ4NDiYOJg4NDiUPDA8MDyUPJQ8MDyUPJQ8MDwwPJQ8MDg0OJg4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0OJg4NDgwPJQ8KEQwPJQ8MDwwPDA8MDg0ODQ4NDiUPDA8MDwwODQ4NDg0ODQ4NDiYODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0OChENDgwPDA8MDwkRDQ4NDg0ODA8MDwwPDA4NDg0ODQ4mDiYODQ4mDyUPJQ4mDg0OJg4ADQU=", + "31":"JgDMAV82DiYPJQ8MDwwPDA4mDwwODQ4mDiYODQ4mDg0ODQ8lDiUPDA8lDyUPChEKESUPDA4NDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4NDiYOJg4NDgwPDA8MDg0ODQ4NDiYODQ4MDwwPDA8MDg0PDA4NDg0OJQ8mDg0ODQ4MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ8LDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8lDwwPDA4mDiYPAAj/YzYOJg4mDg0OChEMDyUPDBALDyUOJg8MDyUPDA4NDiYOJg4NDiYOJg4NDg0OJg4NDg0OJg4MDwwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDgwPJQ8MDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA4NDyUODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOChENDg0ODQ4MDwwPDA8MDg0ODQ4NDgoRDA8MDwwPDA4NDg0ODQ4MDwwPChElDwwPDA4mDiYOJg8lDwwOJg8ADQU=" + }, + "low":{ + "16":"JgDMAV42DyUPJQ8MDwkSDA4mDwwPDA4mDiYODQ4mDg0ODQ4mDiYODA8lDyUPDQ4MDyUPDA8MDwwOJg4NDg0ODQ4NDgwPDBALDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0OJg4MEAwODA8MDwwPDA4NDiYODQ8MDgwPDA8MDg0ODQ4NDgwPJQ8lDwwPDA8MDwwODQ4NDg0ODQ4NDgoRDA8MDwwODQ4NDg0ODQ4MDwwPCRENDg0ODQ4MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwQCw8lDwwOJg8MDiYOAAj/YzYPJQ8lDwwPDA4NDiYODQ4NDiYOJg4NDiYODQ4MDyUPJQ8MDyUPJQ8MDwwPJQ8MDg0OJg4NDg0ODQ4MDwoRDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDg0OJg4MDwwPJQ8MDwkRDQ4NDg0ODA8MDyUPJQ8mDiYOChEMDwwPDA4NDiYODQ4NDgwPDA8MDwwODQ8MDg0ODQ4KEQwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0PDA4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4mDyUPJQ8MDyUODQ4NDiYOJg4ADQU=", + "17":"JgDMAV82DiYPJQ8MDg0ODQ4mDg0ODA8lDyUPDA8lDwwPDA8lDyUPCREmDiYODQ4NDiYODQ4NDgwPJQ8MDwwPDA4NDg0ODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDg0OJg4NDg0ODQ4MDwwPDA4NDiYODQ4NDg0ODQ4NDg0ODA8MDwwOJg4mDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0OChIlDgwPJQ8MDyUPAAj/YzYOJg4lDwwPDA8MDyUPDA8MDyUPJQ8MDiYODQ4NDiYOJg8MDiYOJg4NDgwPJQ8MDwwPJQ8MDg0ODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4KEQwPDA8MDwwPJQ4NDg0OJg4NDg0OJQ8MDwwPDA8MDg0ODQ4NDg0OJg4mDyUODA8MDwwPDA8MECQODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA4mDg0OJg4NDiYODQ4NDiYOJg4ADQU=", + "18":"JgDMAV82DiYOJg8MDg0ODQ4mDg0ODQ4lDyUPCxAlDwwPDA8lDyUPDA8lDyUPDA8MDiYODQ4NDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8IEg0OJg4NDwwODQ4MDwwPDA8MDyUPDA4NDg0ODQ4MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0OChENDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDg0ODQ4mDg0OJg4NDiYOAAj/YzYOJg8lDwwODQ4NDiYPDA4NDiUPJg4MDyUPDA8MDyUPJQ8MDiYPJQ8MDg0PJQ4NDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4NDwsPDA8MDg0ODQ4NDg0OJQ8KEQwPJQ8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA8lDyUPDA8MDg0ODQ4NDiYODQ4KEQwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4NDg0ODA8MDwwODQ4NDwwODA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4mDiYODQ4NDiYODQ4NDiYOJg4ADQU=", + "19":"JgDMAV82DiYOJg4NDgwPDA8lDwwODQ4mDiYODQ4mDwwODQ4mDiYODQ4mDiYODQ4MDyUPDA8MDwwOJg4NDg0ODQ4NDg0PCw8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0OJg8LDwwPDA8MDwwODQ4NDiYODQ4NDgoRDA8MDwwPDA4NDg0OJg8lDgoRDQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4lDwwPJQ8MDyUPAAj+YzYPJQ8lEAsPDA8MDyUPDA4NDiYOJg4NDiYPDA4KEiUOJg4NDiYOJg4NDg0OJg4NDgwPJRAMDgwODQ8JEQ0ODQ4MDwwPDA4NDg0ODQ4NDg0ODA8MDwwOJg4NDg0OJg4NDg0OJg4MDwwPDA8MDwwODQ4NDwwODQ4mDiYODQ4NDgwPDA4NDiYODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDwwODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4NDgwPDA8lDwwODQ4NDiYODQ4NDiYOJg4ADQU=", + "20":"JgDMAV82DiYOJg4NDg0ODQ4lDwwPDA8lDyUPDA8lDwwPDA4mDiYODQ4mDiYPDA8MDyUODQ4NDg0OJg4MDwwPDA8MDg0ODQ4MDwwPDA8MDwkRDQ4NDg0ODA8MDwwPDA4NDg0OJg4NDg0ODA8MDwwPDA8MDyUPDA4NDg0ODQ4MDwwPDA8MDwwOJg4mDg0ODQ4NDgwPDA8MDwkRDQ4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDwsPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4mDg0OJg4NDyUOAAj/YzYOJg4mDg0ODQ4KESUPDA8MDyUQJA8MDyUPDA8MDiYPJQ8MDiYPJQ8MDwwOJg4NDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0OJg4NDgwPJQ8NDgwPJQ8MDg0ODQ4NDgoRDBAMDiUPJQ8MDyUPDA4NDg0ODQ4NDiYODQ4NDgoSCw8MDwwODQ4NDgwPDBALDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4mDiYPJQ4mDg0OChENDiYOJg4ADQU=", + "21":"JgDMAV82DyUPJQ8MDg0ODQ4mDg0ODQ4mDiYODA8lDwwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPJQ8MDwwPDA8MDg0ODQ4NDiUPDA8MDwwODQ4NDg0PDA4NDgwPJQ8lDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDwsPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8lDwwOJg4NDiYOAAkDXzYPJQ8mDgwPDA8MDyUPDA8MDiYOJg4KESYODQ4NDiYOJg4NDiYOJg4NDg0OJg8MDg0OJg4MDwwPDA4NDg0PDA4NDgwPDA8MDwwODQ4NDg0ODRQHDg0OJQ8MDwwPJQ8MDwwOJg4NDg0ODQ4NDg0ODA8MDwwPJQ8KECYODQ4NDg0ODQ4MDyUPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8MDwwODQ4mDg0OJg4mDwwODQ4NDiYOJQ8ADQU=", + "22":"JgDMAWA1DiYOJg4NDg0ODA8lDwwPDA4mDyUPDA4mDgoRDQ4mDiYODQ4mDiYODQ4NDiYODQ4NDgwPJQ8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0OJg4NDg0PCREMDwwPDA4NDiYODQ4NDgwPDA8MDg0ODQ8MDg0OJg4mDg0ODQ4NDgwPDA8MDg0ODQ4NDwsPChEMDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8KEQwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDBAkDwwPJQ8MDyUPAAj/YzYOJg4mDg0ODQ4MDyUQCw8MDyUPJQ8MDyUPDA4NDiYOJg4NDiYOJg4NDg0OJg4NDwsPJQ8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPJQ8MDwwPJQ8MDg0OJg4NDg0ODQ4MDwwODQ4NDiYODQ4NDiYODQ4MDwwPDA4NDiYODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8lDyUPDA8lDg0OChENDiYOJg8ADQU=", + "23":"JgDMAV82DiYOJg4NDg0PCw8lDwwPDA8lDyUPDA8lDwkRDQ4mDiYODQ4mDiYODQ4NDiYODQ4MDwwPJQ8MDwwODQ4NDg0PDA4MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0OJg4NDg0ODA8MDwwODQ4NDiYODQ4NDwwODA8MDwwPDA4NDg0OJg4mDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODg0MDwwPCRIMDg0ODQ4NDgwPChEMDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4mDg0OJQ8KESUPAAj/YzYOJg4mDg0ODA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDgwPJQ8MDwwPJQ8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg4NDggTJg4NDg0OJg4NDg0OChEMDwwODQ4NDgoRDQ4NDiUPDA8MDwwODQ4NDiYODQ4NDgwPChEMDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4KEQ0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPCREmDgoRDQ4mDg0ODQ4NDiYOJg4ADQU=", + "24":"JgDMAV42DyUPJQ8MDwoRDA8lDw0ODA8lECQODQ4mDgoRDQ4mDiYODA8lDyYODQ4MDyYODA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0OJg4NDgwPDA8MDg0ODQ4NDiYODA8MDwwPDA4NDg0ODQ4NDg0OJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDgoRDQ4MEAsPDA8MDg0ODQ4NDg0ODA8MDwwPDA8lDwwOJg4NDiYOAAj/YzYPJQ8lDwwPDA4NDiYODQ4NDiYOJg4MDyYODA8MDyUPJQ8IEyUPJQ8MDwwPJQ8MDwwOJg4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0OJg4NDg0OJQ8MEAsPJQ8MDwwPDA4NDg0ODQ4NDiUPJQ8lDwwPDA8MDg0ODQ4NDiYODQ4KEQwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4mDiYOJg4NDg0ODQ4MDyUPJQ8ADQU=", + "25":"JgDMAV82DyUOJg4NDg0OChElDwwPDA8lDyUPDA8lDwwPDA4mDiYODQ4mDiYPDA4NDiYODQ4NDgwPJQ8MDwwPDA8MDg0ODQ4NDg0ODQ4MDwoRDA8MDg0ODQ4MDwwPDA8MDg0OJg4NDg0ODQ4MDwwPDA8MDiYODQ4KEQ0ODQ4MDwwPChEMDg0OJg4mDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODQ4KEQwPDA4NDg0ODQ4mDgwPJg4NDiYOAAj+ZDYPJQ8lDgoRDQ4MDyUPDA8MDyUPJg4MDyUPDA8MDyUPJQ8MDiYOJg4KEQ0OJg4NDg0OJg4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0OJg4MDwwPJRALDwwOJg4NDg0ODQ4MDwwPDA8MDwwOJg4mDg0ODQ4NDg0ODQ4MDyYODA8MDwwPDA8MDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgoRDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4mDg0OJg4KEQwPDA8MDyUOJg4ADQU=", + "26":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA8lDwwPDA8lDyUPDA8lDyUPDA8MDiYOChENDg0OJg4NDgwPDA8MDwwODQ8MDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwOJg4NDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODQ4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPCRENDg0ODQ4NDgwPDA8MDg0ODQ4mDg0OJg4NDiYPAAj+YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg4NDiYODQ4NDiYOJg4MDyUPJQ8KEQwPJQ8MDwwPJQ8MDg0ODQ4NDg0ODA8MDwwODQ4KEQ0ODQ4MDwwPDA8MDg0OJg4NDg0OJg8MDgwPJQ8MDwwPDA8MDg0ODQ4NDiYODA8mDgwPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwkRDQ4NDg0ODQ4NDg0ODA8MDwwPDA4NDgoRDQ4MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8lDyUPDA8MDg0ODQ4NDiYOJg4ADQU=", + "27":"JgDMAV81DyYOJg4NDgwPDA8lDwwPDA4mDiYOChEmDg0ODQ4lDyYODQ4mDiYODA8MDyUPDA8MDg0OJg4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPJQ8MDwwODQ8MDg0ODQ4MDyUPDA8MDwwODQ4NDg0ODQ4NDgwPJQ8lDwwPDA4NDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDg0ODA8MDg0ODQ4NDg0ODQ4MDwwPDA4NDg0OCRIMDwwPDA8MDwwODQ4NDg0ODA8MDwwODQ4mDg0OJg4NDiYOAAj/YzYOJg4mDwwODQ4NDiYODQ4MDyUPJQ8MDyYODA8MDyUPJQ8MDyUPJQ8MDwwOJg4NDg0OJg4NDgwPDA8MDwwODQ4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPJQ8MDwwPJQ8MDwwOJg4NDg0ODQ4NDgwPDA8MDwoRDA4mDwwODQ8MDg0ODA8MDyUPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8lDwwPDA4NDg0PDA4MDyUPJQ8ADQU=", + "28":"JgDMAV42DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODA8lDyUPDA8lDyYODQ4MDyUPDA8MDwwOJg4KEQ0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0ODA8KEQwPJQ8KEQwPDA4NDg0ODQ4NDiYODA8MDwwPDA4NDg0ODQ4MDwwPJQ8lDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4KEQwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA8lDwwOJg8MDiYOAAj+ZDYOJg8lDg0ODQ4NDiYODQ4MDyUPJQ8MDyYODA8MDyUPJQ8MDyUPJQ8MDwwPJQ8MDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0OJg4MDwwPJQ8MDg0OJg4NDgoRChENDgwPDA8MDyUPJQ8MDg0ODQ4NDg0ODA8MDyUQCw8MDg0ODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4NDg0ODA8MDwwODQ4NDgwPDA8MDwwODQ4KEQ0ODQ4mDiYOJg4mDiYOJg4mDg0OJg4ADQU=", + "29":"JgDMAV82DiYOJg8MDwwODA8lDwwPCRIlDyUQCw8lDwwODQ4mDyUPDA4mDiYODQ4NDiYODQ4MDwwPJQ8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4NDgwPDA8MDwwOJg4NDg0ODQ4NDgwPDA8MDiYOCBMNDg0ODA8MDwwPDA4NDg0OJg4mDg0ODQ4MDwwPDA8MDwwODQ4NDg0PDA4MDwwPDA8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4mDg0OJQ8MDyUPAAj/YzYOJg4mDg0ODQ4MDyUPDA8MDyUPJQ8MDyUPDA4NDiYOJg8MDiYPJQ4NDg0OJg4NDgwPJQ8MDwwPDA8MDwwOChENDg0ODA8MDwwPDA4NDg0ODQ4MDwwPJRALDwwPJRALDwwPJQ8MDg0ODQ4KEQwPDA8MDwkRJg4NDwwODQ4NDgwPDA8MDyUPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDwsPDA8MDgoRDQ4NDgwPDA8lDwwPJQ8mDiYPJQ4mDwsPJg4ADQU=", + "30":"JgDMAV42DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDwwODA8lDyUPDA8lDyYODQ4MDyUPDA8MDwwPJQ4NDg0ODQ4MDwwPChEMDg0ODQ4NDgwPDA8MDwwPDA4NDg0OChILDwwPJQ8MDg0OChENDg0OChEMDyUQCw8MDg0ODQ4NDg0OCBMMDwwPJQ8lDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8KEQwODQ4NDg0ODQ4KEQwPDA8MDwwODQ4KEQ0ODQ4KEQwQCw4NDg0ODQ4KEgsPDA4NDg0ODQ4mDg0OJg4KESYOAAj+YzYPJQ8mDg0ODA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwQJA8JEQ0ODQ4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0OJQ8MDwwPDA8MDg0ODQ4NDiYODA8MDwwPDA4NDg0ODQ4NDiYODQ4NDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4mDiYODQ4mDiYOJg4mDg0OJg4ADQU=", + "31":"JgDMAV81DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lDyUPDA8lDyUPDA8MDyUPDA4NDg0OJg4NDg0OChEMDwwPDA4NDg0OCBMNDgwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPDA4NDg0ODQ4NDiUPDA8MDwwODQ8MDg0ODA8MDwwPJQ8lDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwQCw8MDwwODQ4NDg0ODA8MDwwPCRENDg0ODQ4NDgwPDA8MDwwODQ4mDg0OJg4KESYOAAj+ZTUOJg4mDg0ODQ4NDiYODA8MDyUPJQ8MDyUPDA8MDiYOJg4NDiYOJg8JEQ0PJQ4NDg0OJg4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwQJA8MDwwPJQ8MDwwOJg4NDg0ODQ4NDgwPDA8MDwkRDQ4NDg0ODQ4MDwwPDA4NDiYODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4mDwwODQ4mDiYOJg4mDgoRJg4ADQU=" + }, + "mid":{ + "16":"JgDMAV42DyYOJg4NDgwPDA8lDwwPDA8lDyUPDA4mDg0ODQ4mDiYODQ4mDiYODQ4NDiYODA8MDwwPJQ8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0OJg4NDgwPDA8MDwwODQ4NDg0OJg4NDg0ODA8MDwwPDA4NDgoRJg4mDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODQ4mDg0OJg4mDiYOAAj/YzYOJg4mDwwODQ4NDiYODQ8LDyUPJQ8MDyUPDA8MDyUPJQ8MDiYPJQ8MDgoRJg4NDg0OJw0NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0OJRALDwwPJg4NFAYTIQ8MDwwPDA4NDg0ODBALDyUPJQ8lDyUPDA8MDwwODQ4mDiYODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4JEgwPDA8MDwwODQ4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0OChIkDwwPDA4mDyUODQ4NDiYOJg4ADQU=", + "17":"JgDMAV82DyUPJQ8IEwwPDA4mDwwODQ8lDiYODQ4mDg0ODA8lDyYODQ4mDiYODA8MDyUPDA8MDg0OJg8MDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPJQ8MDwwODQ4NDg0ODQ8LDwwPJQ8MDwwODQ4NDg0ODQ4NDgwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDg0ODA8MDwwODQ4mDg0OJg4mDiYOAAj/YzYPJQ8lDwwPDA4NDiYODQ4NDiYOJg4NDiYODA8KESUPJQ8MDyYOJRALDwwPJQ8MDwwOJw4MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwoRDA8MDwwOJg4NDg0OJg4NDgwPJQ8MDwwPDA4NDg0ODQ4NDg0OJQ8lDyUPDA8MDwwODQ4mDiYODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8lECQPJQ8MDyUPDA8MDyUPJQ8ADQU=", + "18":"JgDMAWA1ECQQJBAKEAwQChAkEQoRCRIjESMRCREkEAoRChEkECQQChAkESQQChEKESMRCRELEQkRJBAKEQoRChALEQkSCREKEQoRChEKEAsRChEJEgkRChEKEQoRChALEAsRIxEJEQsQChEKEQoRChALEAsQJBEJEQoRChEKEQoQCxEJEQsQJBAkEAoRChEKEQkRCxAKEQoRChEKEQoRCRIJEgkRChEKEQoRChEKEQkRCxEJEQsQChEKEQoRCREKEQoRChEKEQoRChEJEgkRChEKEAwQChEKEQoRCRIjEAoRJBAkECQQAAj8ZTQQJBAkEAoRChEKESQQChEKESMRIxEJEiMRCRIJESQQJBAKESQQJBAKEQoRJBAKEQoRIxEJEgkRChEKEQoRChEKEQkSCREKEQoRChALEQoRChEODAsQJBAKEQoRJBAKEQoRIxEKEQkRCxAKEQoRChEKESMRChEjESMRCRIJEQoRChEkECQQChEKEQoRCRIJEQoRChALEQoRChEJEgkRCxAKEQoRChALEQkRChEKEAsRChEKEQkSCREKEQoRChEKEQoRChEJEgkRChEKEQoRChEjEQkSIxEKESMRCRELDyUQJBAADQU=", + "19":"JgDMAV82DyUPJQ8MDwwPDA8lDwwODQ4mDiYODQ4mDg0ODA8mDiYODQ4lDyYODQ4MDyUQCw8MDwwOJg4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPJQ8MDwwPDA8MDwwODQ4NDg0OJQ8MDwwPDA8MDg0ODQ4NDg0OJg4mDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8lDwwPJQ8lDyUPAAj/YzYOJg4mDg0ODA8MDyUQCw8MDyUPJQ8MDyUPDA8MDyUPJQ8MDyUOJg4NDg0OJg4NDgwPJQ8MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPJQ8MDwwPJQ8MDg0OJg4NDg0ODQ4MDwwPDA8MDg0ODQ4mDiYODQ4NDg0ODQ4mDiYODQ4MDwkSDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ8lDiYODQ4NDiYODA8MDyUPJRAADQU=", + "20":"JgDMAV42DiYOJg8MDgwPDA8lDwwPDA8lECQPDA8lDwwPDA4mDiYODQ4mDiYODQ4NDiYODQ4NDgwPJQ8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0OJg4NDg0ODQ4MDwwODQ4NDg0OJg4NDg0ODQ4MDwwPDA4NDg0OJg4mDwwODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgoRDA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8lDwwPJQ8lDyUPAAj+YzYPJQ8mDg0ODA8MDyUPDA8MDyUPJQ4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJg4KEQwPJQ8NDgwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODQ4MDwwPJQ8MDwwPJQ8MDg0OJg4NDwsPDA8MDwwPDA8MDiYOJg4NDiYODQ4NDgwPDA8lDyYODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4mDg0ODQ4NDiYODA8MDyUPJQ8ADQU=", + "21":"JgDMAV82DyUOJg4NDg0ODQ4mDg0ODQ4lDyYODA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYODQ4KEQ0OJg4NDgwPDA8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwOJg4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0ODQ4MDwwPDA8MDwwOJg4mDg0ODQ4NDg0OChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDgoRDQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8lDwwPJQ8lDyUPAAj+ZDYOJg4mDwwODQ4MDyUPDA8MDiYPJQ8MDyUPDA8MDiYOJg4NDiYOJg4NDg0OJg8MDgwPJQ8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0OJg4NDwwODA8MDwwPDA4NDg0OJg4NDiYODQ4NDgwPDA8lDyUPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4mDiYPJQ4mDgwPDA8MDyUPJQ8ADQU=", + "22":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODA8lDyUPChElDwwPDA8lDyUPDA8lDyUPDA8MDyUPDA4NDg0OJg4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPJQ8MDwwPDA4NDg0ODQ4MDwkSJQ8MDwwPDA4NDg0ODQ4MDwwPJRAkDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPCRENDg0ODQ4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8lDwwPJQ8lDyUPAAj+ZDYOJg4mDg0ODQ4NDiYODQ4NDiUPJQ8MECQPDA8MDyUPJQ8MDyUPJQ8MDwwOJg4NDg0OJg8MDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwODA8NDg0OJg4NDgwPJRALDwwPJQ8MDwwPDA4NDg0ODQ4NDiUQCw8MDyUPDA8MDg0ODQ4mDiYODQ4NDg0ODA8MDwwODQ4KEQ0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8KEQwPDA8JEQ0ODQ4NDgwQCw8MDwwPDA4NDg0ODQ4NDgwPDA8lDwwOJhAkDwwPDA8MDiYOJg8ADQU=", + "23":"JgDMAV82DiYOJg4NDg0ODQ4mDg0ODA8lDyUPDA8lDwwPDA4mDiYPDA8lDiYPDA4KESYPDA8MDwwOJQ8MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4KEQwPDA8MDwwOJg8MDg0ODQ4NDg0PCw8MDwwPJQ8lDwwODQ4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4mDg0OJg4mDiYPAAj+ZDYOJg4mDg0ODQ4NDiYODQ4MDyUPJQ8MDyUPDA4NDiYOJg8MDyUPJQ8MDg0OJg8MDg0OJg4KEQ0OFAcMDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPJQ8MDwwPJg4NDgwPJQ8MDwwODQ4NDg0ODA8NDgwPDA4NDiYPDA4NDg0ODQ4mDiYODQ4KEQwPDA8MDwwOEQsMDgoRDA8MDwwPDA4NDwwODQ4NDgwPDQ4KEQwODQ4NDg0ODA8MDwwPCRENDg0ODQ4NDgwPDA8MDg0ODQ4mDyUODQ4mDg0ODQ4MDyUPJQ8ADQU=", + "24":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA4mDiYPDA4mDg0ODQ4mDiYODQ4mDiYODQ4NDiYODA8MDwwPJQ8MDwwOChENDg0ODQ4NDgwPDA8MDg0ODQ4KEgsPDA8MDwoQDQ4NDg0OJg4NDg0ODQ4MDwgTDA4NDg0OJg4NDg0ODA8MDwwPDA8MDwwOJg4mDg0ODQ4NDgwPDBALDwwPDA8MDg0ODQ4NDwsPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OCBMMDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4mDwwOJg4mDyUOAAkDYDYOJg4mDgwPDA8MDyUPDA4NDiYPJQ8MDiYPDA4NDiYOJg4NDiYOJg4NDg0OJg4NDg0OJQ8MDwwPDA8MDwwPDA4NDgoRDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0OJg4NDg0ODA8MDwwPDA8MDyUPJQ8lDwkSDA4NDg0ODQ4mDiYODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA4NDg0ODQ8MDgwPDA8MDwkRDQ8lDg0ODQ4mDg0ODQ4NDiUPJQ8ADQU=", + "25":"JgDMAV82DiYOJg4NDwwODA8lDwwPDA8lDyUPCRIlDwwODQ4mDiYODQ4mDiYODQ4NDiYODQ4NDgwPJQ8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0OJg4KEgwODQ4MDwwPDA8MDwwOJg8MDg0OChEMDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4KEQ0ODA8MDwwPDA4NDg0ODQ4NDg0ODQ4MDwwPDBALDg0ODQ4NDg0ODA8MDwwPDA4mDg0OJg4mDiYOAAj/ZDUPJQ8lDwwPDA8JEiUPDA4NDiYOJg4NDiYOChENDiYOJg4NDiUPJg4MDwwPJQ8MDwwOJg8MDg0ODQ4NDg0ODQ4MEAsPDA4NDg0ODQ4MDwwPDA8MDg0OJg4KEQ0OJg4NDgwPJQ8KEQwPDA8MDg0ODQ4NDgwPJQ8mDwsPDA8MDwwOChEmDiYPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ8MDg0ODA8MDwwPDA4NDg0ODQ4MDwkSChElDyUPJg4MDwwPDA8MDiYPJQ4ADQU=", + "26":"JgDMAV82DiYOJg8MDg0ODQ4mDg0ODQ4lDyYODA8lDwwPDA8lECQPDA8lDyUPDA4NDiYODQ4NDg0OJg4NDg0ODQ4MDwwPDA4NDgoRDQ4MDwwPDA8KEA0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDwwPJQ8MDwwODQ4NDg0ODQ4MDwwPJg4lDwwPDA4NDg0OCBMNDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwODQ4mDg0OJg4mDiYOAAj/ZDYOJg4mDgwPDA8MDyUPDA8MDiYPJQ8MDiYODQ4NDiYOJg4NDiYOJg4NDg0OJg4MDwwPJQ8MDwwPDA8MDwwODQ4NDg0ODA8NDgwPDA4RChALDQ4MDwwPJQ8MDwwOJg4NDg0OJg4NDg0ODQ4NDgwPDA8MDyUPDA8lDwwODQ4NDg0ODQ4mDiYODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwOChENDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDg0PDA4NDg0ODA8MDwwPDA4NDgoRDQ4mDg0OJg4MDwwPDA8MDyUPJQ8ADQU=", + "27":"JgDMAV82DyUPJQ8MDwoRDA8lDwwPDA4mDyUPDA8lDg0ODQ4mDiYODQ4mDiYODQ4NDiUPDA8MDwwPJQ4NDwwODQ4NDg0ODQ4MDwwPDA4NDg0PDA4KEQwPDA8MDwwODQ4NDg0OJQ8MDwwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4mDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDg0ODA8lDwwPJQ8lDyUPAAj+YzYPJQ8lDwwPDA8MDyUPDA4NDiYOJg8MDiYODQ4NDiYOJg4KESYOJg4NDgwPJQ8NDgwPJQ8MDwwPDA8MDg0ODQ4NDgoRDA8MDg0ODQ4NDg0ODA8MDwwPJQ8MDg0OJg4NDg0OJg4NDg0ODBALDwwODQ4NDg0ODQ4lDwwPChEMDwwODQ4mDiYODQ4NDg0ODBALDwwPDA4KEQ0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8lDyUPDA8MDg0ODQ4NDiYOJg4ADQU=", + "28":"JgDMAWA1DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ8lDiYODQ4mDiYODQ4MDyUPDA8MDwwOJg8KEA0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPJQ8MDwwPDA8MDg0ODQ4NDg0OJg8LDwwPDA8MDwwPDA4NDg0OJg4mDgwPDA8MDwwODQ8MDg0PDA4SCgsPDA8MDwwPDA4ODQ0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPEAsMDwwODQ4NDgwPDA8lDwwPJQ8lDyUPAAj/YzYOJg4mDg0ODQ4NDiUPDA8MDyUPJQ8KESUPDA4NDiYOJg4NDiYOJg4NDwwOJg4NDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPJQ8MDwwPJQ8MDg0OJg4NDwwODQ4MDwwPDA8MDyUPJQ8MDg0ODQ4ODQ0ODQ4mDiYODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODg0NDg0ODQ4NDgoRDA8MDg0ODQ4NDgwPDBALDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4mDg0PDA4NDgwPDA8MDiYPJQ8ADQU=", + "29":"JgDMAWA2DiUPJQ8MDwwPDA4mDwwODg0mDyUOChIlDwwODQ4mDiYODQ4mDiYPDA4NDiYODQ4MDw0OJQ8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0PDA4NDg0ODA8MDwwPDA4NDg0OJg4NDg0ODQ4MDwwPDA4NDg0OJg4NDgwPDQ4MDwwPDA4ODQ4NJg4mDg0ODQ8LDwwPDA8MDg0ODQ4NDg0ODQ4MDw0PDA4MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4ODQ0ODQ4NDgwPDA8MDg0ODQ4mDg0OJQ8lDyYOAAkCYDYPJQ8lDwwPDA8MDiYPDA4NDiYOJg4NDiYODQ8LDyUPJQ8MDyUPJQ8MDwwOJg8MDg0OJg4NDwwODQ4MDwwPDA8MDg0PDA4ODQ0PCw8MDwwPDA4NDg0OJg4NDg0OJg4NDgwPJQ8MDwwPDA4NDg0PDA4NDgwPJQ8MDwwODQ4NDwwODQ4mDiYPDA4NDgwPDA8MDwoRDA4NDggTDQ4KEQwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDwwODQ4MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA4mDiYPJQ8lDyUPJRAkDwwPJQ8ADQU=", + "30":"JgDMAV82DiYOJg4NDg0ODQ4mDg0ODA8lDyUPDA8lDwwPDA4mDiYODQ4mDiYODQ4NDiYODQ4ODQ0OJg4NDgwPDA8MDw0ODA4NDg0ODA8MDwwPDA4NDg0ODg0NDgwPDA8MDwwOJg4NDg0ODQ4NDg0PCw8MDwwOJg8MDg0ODQ4NDg0ODA8MDwwOJg8lDwwPDA4NDg0ODQ4ODQ0ODA8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODQ8LDwwPDA8lDwwPJQ8lDyUPAAj+YzYPJg4mDg0PDA4MDyUPDA8MDyUPJQ8MDyUPDA8MDiYOJg4NDyUOJg8MDg0OJg4NDg0OJg4MDwwPDA4NDg0ODQ4NDwoQDA8MDwwODQ4NDg0ODQ4NDgwPJQ8MDwwPJQ8MDwwPJQ4NDg0ODQ4NDgwPDA8MDyUPChEMDg0ODQ8LDwwPDA8lDyUPDA8MDwwPDA4ODQ0ODQ4MDwwPDA4NDwwODQ4NDwsPDBALDwwODQ8MDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4ODQ0ODQ4mDg0OJg4mDiYOJg8lDg0OJg4ADQU=", + "31":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA4mDiYODQ4mDwwODQ4mDiYODQ4mDiYODQ4NDiYODA8MDwwPJQ8MDwwPCRENDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0OJg4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0ODQ4MDwwQCw8MDg0OJg4mDg0PDA4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDBALDg0ODQ4mDg0OJg4mDiYPAAj/YzYPJQ4mDwwODQ4NDiUPDA8MDyUPJQ8MDyUPDA8MDyUQJA8KESUPJQ8MDwwOJg8MDg0OJg4NDg0ODA8MDwkSDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0OJg8MDgwPJg4MDwwPJQ8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4mDiYODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwoRDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDiYPDA4mDiYOJg4mDwwOJg8ADQU=" + }, + "high":{ + "16":"JgDMAV82DiYOJg4NDg0ODQ4mDg0ODQ8lDyUODQ4mDg0ODQ4mDiYODA8lECQPDA8MDyUPDA8MDwwOJg4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0OJQ8MEAsPDA4NDg0ODQ4NDiYOJg4NDgwPChEMDwwPDA4NDg0OJg8lDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4mDg0OJg4NDgwPAAkCYDYPJQ8lDwwODQ4NDiYODQ4NDiYOJQ8MDyUPDA8MDyUPJg4MDyUPJQ8MDwwQJA8MDwwOJg4NDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA8MDwwOJg4NDg0OJg4NDgwPJQ8MDwwPDA8MDwwPDA4NDiYOJg4mDyUPDA4NDg0ODA8lDwwPJQ8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODQ4MEAsPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4mDg0OJg4mDiYODQ4NDiUPJg4ADQU=", + "17":"JgDMAV82DyUPJQ8MEAsPChElDwwODQ4mDyUODQ4mDg0ODQ4mDiYODQ4mDiYODQ4NDiUQCw8MDwwPJQ8MDg0ODQ4KEQ0ODA8MDwwODQ4NDwwODQ8MDgwPDA8MDwwPDA4NDg0OJg8JEQwPDA8MDg0ODQ4NDiYOJg4NDg0ODA8MDwwPDA4NDg0OJg8lDg0ODQ4NDg0ODA8MDwwODg0ODQ0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4mDg0OJg4NDgwPAAj+YzYPJQ8mDgwPDA8MDiYODQ4NDiYOJg4NDiYODQ4NDiYOJg4NDiYOJg4KEQwPJQ8MDwwPJQ8MDg0PDA4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0OJg4NDg0OJg4NDg0OJg4MDwwPDA8MDg0ODQ4NDgwPJQ8lDyYODA8MDwwQCw8lDwwPJRALDwwODQ4NDg0ODA8MDwwPCRIMDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8lECQPDA8lECQPDA8MDyUPJQ8ADQU=", + "18":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ8lDiYODQ4mDgwPDA8lDyUPDA8lDyUPDA8MDiYPDA8MDg0OJg4NDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPJQ8MDwwPDA4NDgoRDQ4MDyUPJg4MDwwPDBALDwwODQ4NDg0OJQ8lDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8lDwwPJQ8MDg0OAAj/YzYPJQ8lDwwPDA4NDiYODQ4NDiYOJg4NDiYODA8MDyUPJg4MDyYOJg4NDgwPJQ8MDwwPJQ8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0OJg4NDg0OJg4NDwsPJQ8MDwwPDA8MDwwODQ4NDiYPDA4mDiYODQ4NDgwPDA8lDwwOJg8MDwwODQ4NDgwPDBALDwwODQ4NDg0ODA8MEAsPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8lDwwPDA8lDyUPDBALDyUPJQ8ADQU=", + "19":"JgDMAWA2DiYOJg4NDgwPDA8lDwwPDA4mDyUPDA4mDg0ODQ4mDiYODQ4mDiYODQ4NDiYODQ4NDgwPJQ8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0OJg4NDwsPDA8MDwwODQ4NDiYOJg4NDg0ODQ4MDwwPDA8MDg0OJg8lDg0ODQ4NDg0ODQ4MDw0ODA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwVBg4NDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDBALDwwPDA4NDg0ODQ4mDg0OJg4NDgwPAAj+ZDYPJQ4mDgoRDQ4MDyUPDQ4MDyUPJQ8KESUPDA4NDiYPJQ4NDiYOJg4NDg0PJQ4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgoRDA8MDwwPDA4NDg0ODQ4MDwwPJQ8MDwwPJQ8MDg0OJg8MDg0ODQ4MDwwPDA8MDwwODQ4mDiYODQ4NDg0ODA8mDg0PJA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0PDA4NDg0PCw8MEAsPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4mDiYOJg4NDiYODQ4NDiYOJQ8ADQU=", + "20":"JgDMAWA2DiYPJQ4NDwwODQ4lDwwPDA8lDyUPDA8lDwwPDA4mDiYPDA4mDiYODQ4NDiYODQ8MDg0OJQ8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwQCw8MDg0OJg4KEQ0ODQ4MDwwPDA8MDiYOJg4NDg0ODQ4NDg0ODA8IEwwOJg4mDwwODQ4NDg0ODBAJEQwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4mDg0OJg4NDg0OAAkAYjYOJg4mDg0ODQ4NDiYODQ4NDiUPJg8MDiUPDA8MDyUPJQ8MDyUPJQ8MDwwOJg4NDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDQ4MDg0ODQ4NDg0OJg4NDgwPJQ8MEAsPJRALDwwODQ4NDg0ODQ4JEiUPJQ8MDyUPChEMDg0ODQ4mDg0OJg4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJRALDyUPDA8MDiYOJg4ADQU=", + "21":"JgDMAV82ECQPJg4MDwwPDA8lDwwODQ4mDiYODQ4mDwwODQ4mDiYODQ4mDiYODQ4NDiYODA8MDwwPJQ8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPJQ8MDwwPDA8MDg0ODQ4NDiYOJg4NDg0OChENDgwODQ4NDg0OJg4mDg0ODQ4PDAwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4ODQ0ODBAMDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAj/YzYPJQ8lDwwODQ4NDiYODQ4NDiYOJg4NDiYOChENDiUPJQ8MDyUPJQ8MDwwPJQ8MDwwOJg4NDg0ODQ4MDwwPDA8KEQwOChENDg0ODQ4MDwwPDA4NDg0OJg4NDgwPJQ8MDwwPJQ8MDwwODQ4NDg0ODQ4MDwwPJQ8MDyUPDA8KEQwODQ4mDg0OJg4NDg0ODA8MDwkSDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDwwODQ4NDgwPDA8MDwwODQ4mDyUPDA4NDiYODQ4MDyUPJQ8ADQU=", + "22":"JgDMAV42DiYOJg4NDg0ODQ4mDgwPDA8lDyUPDA8lDwwODQ4mDiYPDA4mDiYODQ4NDiYPDA4NDgwPJQ8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0OJg4NDgoRDA8MDwwPDA4NDiYOJg4NDg0ODQ4MDwwPDA8MDg0OJg4mDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODQ4mDg0OJg4MDwwPAAj+YzYPJQ8mDg0ODA8MDyUPDA8MDioKJg8MDiYPDA4NDiYOJg4KESYOJg4NDgwPJQ8MDwwPJQ8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0PCw8MDwoRJQ8MDg0OJg4NDg0OJg4NDgwPDA8MDwwODQ4NDiYODQ4KESUPDA8MDwwPDA4mDwwOJg8MDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8NDgwODQ4NDg0ODA8MDwwPDA4NDwwODQ4NDgwPDA8mDgwPDA8MDiYODQ4NDiYOJg4ADQU=", + "23":"JgDMAWA1DiYPJQ8MDg0ODQ4mDg0ODA8lDyUPDA8lDwwQCw8lDyUPDA8lDiYPDA4NDiYODQ4NDg0OJg4NDgwPDA8MDwwPCRENDg0ODQ4MDwwPDA4NDwwODQ4MDwwPDA8MDwwOJg4NDg0ODQ4MDwwPDA8MDyUPJQ8MDg0ODQ4NDgoRDQ4MDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4NDg0ODA8MDwkRDQ4NDg0ODA8MDwwPDA4NDgoRDQ4NDgwPDA8MDwwODQ8lDg0OJg4NDgwPAAkBYTYOJg8lDg0ODQ4NDiUUBw8MDyUPJQ8JEiUPDA8MDyUPJQ8MDiYOJg4NDg0OJg4NDg0OJQ8MDwwPDA4NDg0ODQ4NDg0OChEMDwwPDA4NDg0ODQ8LDwwPJQ8MDwwPJQ8MDg0OJg4NDg0ODQ4MDwwPDA4NDg0OChENDiYODQ4MDwwPDA8lDwwOJg4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4NDg0ODA8MDg0ODQ4NDgwPDBALDwwPDA4NDg0ODQ4NDgwPDA8KEA0ODQ4NDgwPDA8lDyUPJQ8mDgwPDA8MDyUPJQ8ADQU=", + "24":"JgDMAV82DyUPJQ8MDwwPDA4mDg0ODQ4mDiYODQ4mDg0ODQ4mDiYODQ4mDiYOChEMDyUPDA8MDwwOJg4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDwwOJg4NDgwPDA8MDwwODQ4NDiYOJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDgoRDA8MDwwPCRIMDg0ODQ4NDg0PDA4MDwwPDA4NDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8lDwwPJQ8MDwwPAAkBYDYPJQ8lDwwPDA8MDyUPDA4NDiYOJg4NDiYODQ4MDyUPJQ8MDyUPJQ8MDwwPJQ8MDwwOJg8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0OJg4NDg0OJQ8MDwwPDA4NDg0ODQ4NDiYOJg4mDg0ODA8MDwwPDA8lDwwOJg4NDg0ODQ4MDwwPDA8JEgwODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPChEMDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8lDwwPJQ8lDwwPDA4NDiYPJQ4ADQU=", + "25":"JgDMAV42DyUPJg8LDwwPDA8lDwwPDA4mDiYODQ4mDg0ODQ4lDyYODA8lDyUPDA8MDyUPDA8MDg0OJg4NDg0ODQ4NDg0ODA8MDwwPDA4NDwwODQ4MDwwPDA4NDg0ODQ4NDg0OJg4NDgwPDA8MDg0ODQ4NDiUPJQ8MDwwPDA8MDg0PDA4NDg0OJg4lDwwPDA8MDwwODQ4NDg0ODQ4MDwoRCRIMDg0ODQ4NDgoRDA8MDwwODQ4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDg0OAAkAYjYPJQ8lDwwPDA8JESYODQ4NDiYPJQ4NDiYODQ4KESUPJQ8MDyUPJRALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0OJg4NDgwPJRALDwwPDA8MDg0ODQ4NDg0OJg4mDg0ODQ4MDwwPDA4mDg0OJg4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8lDyUPDA8lDwwPDA4NDiYOJg8ADQU=", + "26":"JgDMAV82DyUPJQ8MDwwPDA4mDwwODQ4mDiYODA8lDwwPDA8lDyUPDA8lDyUPDA8MESMPDA4NDg0OJg4NDg0ODQ4NDgwPChEMDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPJQ8MDwwODQ4NDg0ODQ4MDyUPJg8LDwwPDA8MDg0ODQ4NDg0OJg4mDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0PCw8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwPCRENDg0ODQ4NDgoRDA8MDg0ODQ4NDgwPDA8lDwwPJQ8MDwwPAAj+YzYPJQ8lDwwPDA4NDiYODQ4NDiYOJg4NDiYODQ4MDyUPJQ8MDyUPJQ8MDwwPJQ8MEAsPJQ8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPJQ8MDg0OJg8MDg0OJg4NDgwPDA8MDwwODQ4NDiYODQ4mDg0ODA8MDwwODQ4mDg0OJg4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8lDwwPCRIlDwkRDQ4NDiYPJQ4ADQU=", + "27":"JgDMAV82DyUOJg4NDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA4mDyUPDA8lDyUPDA8MDyUODQ4NDgwPJQ8MDwwPChEMDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwOJg8MDg0ODQ4MDwwPChEMDiYOJg8MDwwODQ4NDg0ODQ4MDwwPJQ8lDwwODQ4NDwwODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0OChEmDgoRJg4NDg0OAAj/YzYOJg4mDwwODQ4NDiYODQ4MDyUPJQ8KEiQPDA8MDyUPJQ8MDyUPJQ8MDg0OJg4NDg0OJg4NDwwODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwkRDQ4NDg0OJg4NDg0OJQ8MDwwPJQ8MDwwODQ4NDg0ODQ4MDwwPDA8lDwwODQ4NDg0ODA8mDg0OJg4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDgoSDA4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4mDiYOJg4NDg0ODQ4NDiUPJg4ADQU=", + "28":"JgDMAV82DiYOJg4NDg0ODQ4lDwwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDiYPCRENDyUODQ4NDgwPJg8LDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDg0ODA8MDyUPJQ8KEQwODQ4NDg0ODQ4NDgwPJQ8lDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0OChEMDwwODQ4NDg0ODQ4KEQwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8lDwwOJg4NDg0OAAj/YzYOJg4mDg0ODQ4NDiYODQ4NDiYOJg4NDiUPDA8MDyUPJQ8MDyUPJQ8MDwwPJQ8MDg0OJg4NDg0ODQ4NDwsPDA8MDwwPDA4NDg0OCBMNDgwPDA8MDg0OJg4NDg0OJg4NDg0OJg4MDwwPDA8MDwwODQ4NDiYOJg4NDgwPDA8MDwwODQ4mDg0OJg4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8lDwwOJg4NDg0ODQ4NDiYOJg4ADQU=", + "29":"JgDMAV82DyUPJQ8MDg0ODQ8lDg0ODQ4mDiYODA8lDwwPDA8lDyYODA8lDyUPDA8MDyUPDA8JEQ0OJg4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDwwODQ4MDwwPJQ8MDwwODQ4NDg4RCBIKDiUPJg4KEQwPDA8MDg0ODQ4NDg0OJg4mDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4MDwwPDA8lDwwPJQ8MDgoRAAj/YzYPJRAkDwwPDA8MDiYPDA8MDiYOJg8MDiYPDA4NDiYOJg4NDiYPJQ4KEQwPJg4NDgwQJA8MDwwODQ4NDwwODA8MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPJQ8MDwwOJg8MDg0OJg4NDg0ODA8MDwwPDA8MDg0OJg4NDg0ODQ4MDwwPDA8lDwwOJg4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ8MDg0ODA8MDwwPDA4NDg0PDA4MDwwPDA8lECQPDA8MDwwODQ4NDiYOJg4ADQU=", + "30":"JgDMAV82DyUPJQ8MDwwPCREmDg0ODQ4mDiUPDA8lDwwPDA8lDyUPDA8lDyUPDA8MDyYODA4NDg0OJg4NDg0OChEMDwwODQ4NDg0PCBIMDwwPDA8MDg0ODQ4KEQ0ODQ4MEQoQJA8MDwwPDA4NDg0ODQ4MDyUPJQ8MDwwPDA8MDwwODQ4NDg0OJg4mDgwPDA8MDwwODQ4NDg0ODQ4NDgwPDA8MDwwODQ4NDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDwsPDA8lDwwPJQ8MDwwOAAj/ZDUPJQ8lDwwPDA8MDiYPDA4NDiYOJg4NDiYODQ4NDiYOJg4MDyUPJQ8MDwwPJQ8IEwwPJQ8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwOJg4NDg0OJg4NDg0OJQ8MDwwPDA8MDg0ODQ4NDiYODQ4MDwwPDA8MDg0ODQ4mDg0OJg4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwkSDA8MDg0ODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8mDgwPDA4NDg0ODQ4NDiYOJg4ADQU=", + "31":"JgDMAV42DyUPJQ8MDg0ODQ4mDg0ODQ4mDiYODQ4mDgwPDA8lDyUPCRIlDyUQCw8MDiYPDA8MDg0OJg4NDgoRDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8NDgwPJQ8MDwwODQ4NDg0ODQ4MDyUPJQ8MDwwPDA8MDg0ODQ4MDwwPJQ8lDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwkRDQ4mDwwOJg4NDg0OAAj+ZDYPJQ4mDg0ODQ4MDyYODQ4MDyUPJQ8MDyUPDA8MDyUPJQ8JEiUPJQ8MDwwOJg8MDg0OJg4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPJQ8MDwwPJQ8MDwwPJQ8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4mDwwOJg4NDg0ODQ8LDw0ODA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDBALDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDgoRDQ4mDyUOJg4mDiYOJg4mDgwPJQ8ADQU=" + }, + "turbo":{ + "16":"JgDMAV42ECQPJQ8MDwwPDA4mDwwODQ4mDyUODQ4mDg0ODQ4mDiYODQ4mDyUODQ4MDyUPDA8MDwwOJg4NDg4ODA4NDgwPDA8MDwwPDQ4MDg0NDg4ODQwPDQ4MDwwPDA4NDg0OJg4MDwwPDA8MDg0ODQ4NDiYOJg4NDg0ODA8MDwwPDQ0NDg4NJg4mDwwODQ4NDg0PCw8MDwwPDA4NDg0ODA8MDwwPDA4NDg0PDA4MDwwPDA8MDw0NDg0NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODg4lDwwOJQ8MDwwPAAj+ZTUOJg8lDg0ODQ4NDyUODQ8MDiUPJg8MDiUQDA4MDyUQJA8MDyUPJQ8NDgwOJg4NDwwOJg8MDg0OChEMDwwPDA4NDg0PDA4NDgwPDQ4MDwwPDA4NDwwOJg4NDwsPJRALDwwPJQ8MDwwODQ4NDg0OJg8LDyYOJg4mDikMDA4NDg0ODA8lDwwOJg8MDwwPDA4ODQ0ODQ4MDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ8MDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA4NDg0PDQ0mDg0OJg8lDiYODQ4MDwwPDA8ADQU=", + "17":"JgDMAV42DyUPJRALDwwPDQ4lDg0PDA4mDiYODg4lDwwODg0mDiYPDA8lDiYODQ4NDiYPCw8MDwwPJQ8NDgwODQ4NDg0ODQ4NDgwPDQ4MDg4ODA4NDQ0PDQ4NDg0NDQ4NDgoSJQ4NDwsPDA8MDwkSDQ0NDiYOJg4NDg0ODQ8LDw0ODA8MDg4NJg4mDw0ODQ0NDg0PCw8MDg4NDg4NDQ0ODQ4MDw0ODQ0NDg4NDg0NDgwPDA8MDw0ODQ4NDQ0ODA8NDgwPDA4ODgwODQ4NDgwPDQ0NDg4NChEODQ0ODBAkDwwPJQ8MDw0OAAj/YjcOJg4mDg0ODA8NDiUPDA8MDiYPJQ8MDiYODQ4NDiYOJg4ODSYOJg4ODgwOJg4NDgwPJQ8MDw0PCw4ODQ4NDQ4NDwsPDA8MDg4NDQ4NDg0ODQ4MDw0PJA8MDw0OJQ4NDg4OJQ4NDg0PDA8MDwsPJQ8MDg4NJg4mDyUODQ4NDg4NDQ4mDg0PJQ4MDwwPDA4NDg0ODQ4NDg0ODQ8LDwwPDA4ODg0NDQ4MDwwPDA4NDg4NDQ4NDg0PDA8LDw0NDQ8MDg0ODA8NDg0ODQ4MDg0PDA8lDiYODQ4mDyUODQ4KEQ0ODQ4ADQU=", + "18":"JgDMAWA1DyUPJQ8MDwwPDA4mDg0ODg0mDiYODQ8lDg0ODQ4lECQPDQ4lDyUPDA8MDyUPDQ8MDwsOJg4NDg4NDQ8MDwsPDQ4NDgwODQ4ODQ0ODA8NDwwODQ0NDg0PDA4MDwwPJQ8MDg0ODg0ODg0ODA4NDiYPJQ4MDwwQCw8MDg4NDQ4NDg0OJQ8lEAsPDQ4NDg0NDQ4ODgwODA8MEAwODA8MDg0ODQ4MDwwPDQ4NDgwODQ4ODgwODQ4NDg0ODQ4MDwwODQ4NDg0ODQ4NDg0ODA8NDQ4NDQ8MDgwPDA4mDwwOJg4ODgwOAAj/ZDUOJg8lDwwPDA4NDiYODg0NDiYOJg4NDiYPDA4NDiUPJQ8KESUPJQ8NDwsPJQ8MDwwOJg4ODg0ODQ0NDwsPDQ4NDgwPDA4ODQ4NDg4MDg0PDA8MDQ0OJg4NDwwOJg4KEQ0OJg4NDg0ODQ4NDg0OJQ4NDyUODg0mDiYODg0NDg0PDA4lDwwQJA8LEAwPDQ4NDgwPCw8NDwsPDA4ODQ4NDQ4MDw0ODA8MDg0ODg0ODQ0PDA4NDwsPDQ4MDwwODg4NDQ0ODQ4NDg0NDg0ODQ0PDA4mDwwPDA4lDyUPDQ4MDw0ODA8ADQU=", + "19":"JgDMAWA2DiYOJg4ODQ0ODA8lDwwPDA8lDyUPDA4mDg0ODQ4mDyUODQ4mDiYPDA4NDiYODQ8LDwwPJRALDwwODQ4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODQ4MDwwQCw8MDg0OJg8MDg0ODA8MDwwODQ4NDiYOJg4NDg0ODBALDwwPDQ4NDQ4OJQ8lDg0PDA4ODgwPCw8NDg0ODQ0NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDg0ODQ4NDg4NDQ4MDwwPDA8MDwwODQ4ODQ0ODQ4NDg0PDA4MDg0PDA4mDg0OJg8LDw0OAAkCXzYPJg4mDg0ODA8MECQPDA8MDiYOJg8MDiYODQ8MDyUOJg4NDiUQJQ8LDwwQJA8MDwwQJA8MDwwODg0NDg0ODA8MDwwPDQ0ODQ0ODQ4MDwwPDA8MDg0OJg4ODQ0OJg4NDgwPJg4NDgwPDA8MDwwOJg8MDg0ODQ4mDiYODg0MDw0ODA4mDwwOJg4NDg0ODQ4NDgwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0PCw8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8lDyUPJRALDyUPDA8MDwwODQ4ADQU=", + "20":"JgDMAWA2DiYOJg4NDg0ODA8mDgwPDA8lDyUPDQ4lDwwPDA4mDiYPDA4mDiYODQ4NDyUPDA4MDwwPJQ8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDg0ODA8MDwwODQ4NDg0OJg8MDg0ODA8MDwwODQ4NDiYOJg4NDgoRDA8MDw0PDA4MDg0OJg4mDwwODQ4NDg0ODA8MDwwODQ4NDg0ODQ4NDg0ODA8MDg0ODQ8MDg0ODA8MDwwODg4MDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOChENDg0ODQ4lDw0OJRALDwwPAAj+YzYPJg4lDwwQCw8MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJg4NDg0OJRALDwwPDA4NDg0ODQ4MDw0ODA8NDgwPDBQHDg0OChENDg0PJA8MDwwOJg4NDg0OJg4NDg0OChEMDwwPJQ8MDyUOJg8MDiYODQ4NDg0ODQ8lDg0OJg4MDwwPDA4KEQ0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA4ODgwODQ4NDg0ODA8MDwwODg4MDg0ODQ4MEAsPDA8MDw0NDQ4mDg0OJg4NDiYOChENDgwPDA8ADQU=", + "21":"JgDMAV82DyUPJQ8MDwwPDA4mDg0ODQ4mDiYODQ4mDg0ODQ4mDiYOCBQlDiYODQ4NDiYPCw8MDwwOJg4NDg0ODQ8MDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwOChENDwwOJg4NDgwPDA8MDg0ODQ4NDiYOJg4NDgwPDQ4MDwwPDA4NDg0OJg4mDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0PCw8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDg0PDA4NDwwODQ4mDwsPJRALDwwPAAj+YzYPJRAkDwwPDA8MDyUPDA8MDiYOJg4NDiYODQ8MDiYOJg4NDiYPJQ4NDg0OJg4MDwwQJA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0PDA4NDgwPDA8MDwwOJg8MDg0OJg4NDwwOJg8MDwsPDA8NDgwPJQ8MDg0OJg8MDiYODQ4NDgwQCw8lDwwPJQ8MDg0ODQ4NDg0ODA8MEAsPDA4NDg0ODQ4NDgwPDBALDg0ODg0NDg0OChIMDgwPDA8MDg4NDQ4NDgwPDA8MDwwODQ4NDg0ODBAlDiYODQ4MECQQCw8MDg0ODQ4ADQU=", + "22":"JgDMAV82DyUPJQ8MDg0ODQ8lDg0ODA8lDyYODQ4lDw0ODA8lDyUPDA8lDyUPChEMDiYPDA8MDg0OJg4NDg0ODQ4MDwwPDg0MDg0ODQ4NDg0ODA8MDwwODQ4NDwwODQ4MDwwPJQ8MDg0ODQ4NDwwODA8MDyUPJQ8MDwwPDA4NDg0ODQ4NDg0OJRAkDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4NDg0ODQ4MDg0ODQ4NDwsPDA8MDwwODQ4NDwwODQ8MDgwQCw8lDwwOJg4NDg0OAAj/YzYOJg8lDwwPDA4NDyUODQ4NDiYOJg4NDyUODQ4NDiUPJQ8MDyUPJQ8MDwwPJQ8MDg0OJg4NDg0ODA8MDwwPDA8JEQ0ODQ4NDgwPChEMDwwPDA4NDg0OJg4NDg0OJg4MEAsPJQ8MDwwPDA4NDg0OJg8MDiYODA8MDyUPDA8MDwwODQ4mDg0OJg4NDg0ODQ4MDwwPDA4NDg0ODQ4NDg0ODA8MEAsPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MEAsPDA4NDg0ODQ4MDwwPDA8lDwwODQ4NDiYODQ4NDg0ODA8ADQU=", + "23":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA4mDyUPDQ0mDwwODQ4mDiYODQ8lDiYPDA4NDiYODQ4MDwwPJRALDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ8MDwwPJA8MDwwPDA4NDg0ODQ4NDiYOJg8MDwwPCxALDwwPDA4NDg0OJg4mDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDgoRDg0ODQ4NDQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0PDA8kDwwPJQ8MDwwPAAj+ZDUPJQ8mDg0ODA8MECQPDA8MDiYPJQ8MDyUQCw4NDiYPJQ4NDiYOJg4NDg0OJg4MEAsPJQ8MDwwPDA8MDwwODQ4NDwsPDA8MDwwODQ4NDg0ODA8MDwwPJRALDwwOJg8MDg0OJg4NDgoRDQ4MDwwPJQ8MDwwPDA4NDiYPDA8JEQwPDA8lDwwPJQ8MDwwODQ4NDg0ODQ4NDgwPDQ0NDwwODQ4NDwwODA8MDwwPDA8MDg0ODQ4NDgwPDA8NDQ4NDg0NDgwPDA8MDwwODQ4NDg0PDA4lDyUPJg4mDg0ODA8MDwwPDA4ADQU=", + "24":"JgDMAV82DiYOJg8MDg0ODQ8lDg0PCw8lDyUQCw8lDwwPDA8lDyUPDA4mDiYPDA4ODSYODQ8MDg0OJQ8MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8KEQwOJg4NDg0ODQ4NDgwPDA8NDiUPJQ8MDwwODQ4NDwwODQ4MDwwPJQ8lDwwODQ8MDg0ODQ4NDgwPDA8MDwwODQ4ODgwNDg4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwODQ4mDgoSJQ4NDg0OAAkDXzYOJg4mDwwODg0ODSYODQ4NDiYPJQ8MDiYODQ4MDyUPJQ8MDyUPJRALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDBALDwwODQ4NDg0ODA8MDwwODg0NDwwOJg4NDgwPJQ8MDwwPJQ8MDg0ODQ4NDg0OJg4KESYOJg4mDg0ODA8MDwwPDA4mDg0OJg4NDg0ODQ4NDwsPDA4NDg0PDA4ODQ0ODQ4MDwwPDA8MDg0ODQ4ODQ0ODA8MDwwODQ4NDg0ODQ4NDwwODQ4MDg0ODg0NDg0ODA8lDwwQJA8lDwwPDA4NDg0ODQ4ADQU=", + "25":"JgDMAV42ECQPJQ8MDwwPDA4mDwwODQ4tByYODQ4mDg0OChIlDiYODQ4mDiYODA8MDyUPDA8MDwwPJQ8MDg0OChIMDgwQCxAMDgwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0OJg4NDgwPDA8MDwwODQ4NDiYOJg4NDgwPDQ8MDgwODQ4NDgoRJg4mDg0PDA4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg4NDQ4MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4mDg0OJQ8MEAsPAAj+YzcOJg8lDg0ODQ4NDiUPDA8MDycNJQ8MDiYODQ4NDiYOJg4NDiYOJg8MDg0OJg8MDg0OJQ8NDgwPDA4NDwwODQ8MDgwPDA8MDwwPDA4NDg0ODQ4NDg0OJg8LDwwPJQ8MDwwPJQ8MDg0ODQ4NDg0OJg4NDgwPJQ8lDwwPDA4NDg0ODQ4mDg0OJg8MDgwPDA8MDwwODg0NDg0ODQ4ODQwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwoRDA4NDg0ODQ4NDg0ODA8MDwwODQ4mDiYODQ4mDg0PDA4NDg0ODA8ADQU=", + "26":"JgDMAV82DiYPJQ8MDwwODQ4mDg0ODQ4mDyUODQ4mDgwPDA8lDyUQCw8lDyUPDA8NDiUPDA8MDg0OJg8MDwwODQ4NDg0ODA8MDwwODQ4NDg0PDA4MEAsPDA8MDg0ODQ4NDg0OJQ8MDwwPDA8MDwwODRYFEyEOJg8MDgwPDA8MDw0ODA8MDg0OJg4mDg4ODA4MDw0ODA4ODQ4ODA4NDgwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4NDgwQCw8NDQ0ODQ4ODQ0ODA8lDwwPJQ8MDwwOAAkAYjYPJg4mDgwPChILDyUPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg4NDg0OJQ8MDw0OJQ8MDwwPDQ0NDg0ODQ8HEw0ODA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0OJg4NDg0ODQ4MDwwPJQ8MDyUPDA8lDg0ODQ4NDgwPDBAkDwwQJA8MDw0PCw4NDwwODQ4NDg0ODA8NDQ0ODQ4NDg0ODA8MDwwPDA8MDwwODg0NDgwPDA8MDwwPDA4NDg0ODQ4MDwwQCw8MDg0ODQ4mDg0ODQ4lEAsQCw8NDQ0ODQ4ADQU=", + "27":"JgDMAWA0ESMRIxEJEgkRChEkEAoRChEjESMRCRIjEQkSCRIjESMRCRIjECQQChEKESMRChEJEgkSIxEJEgkRChEKEQoRCRIJEgkSCREKEQoRCRIJEgkSCRIJEQoRChEKEQkSIxEJEgkSCRIJEQoRChEJEiMRIxEJEgkSCREKEQoRCRIJEgkSIxEjEQkSCREKEQoRCRIJEgkSCREKEQoRChEJEgkSCRIJEgkRChEKEQoRCRIJEgkRChEKEQkSCRIJEgkRChEKEQoRCRIJEgkSCREKEQoRChEJEgkSCRIjEQkRJBAKEQoRAAj7ZjMRJBAkEAoRChEJEiMRCRIJESQQJBEJESQQChEKESMRIxEKESMRIxEJEgkSIxEJEgkSIxAKEQoRChEJEgkSCRIJEgkRChEKEQkSCRIJEgkRChEKEQkSIxEJEgkSIxEJEgkSIxAKEQoRChEJEgkSIxEJEgkRChEkEAoRChEJEgkSCRIjEQkSIxAKEQoRCRIJEgkSCRIJEQoRChEKEQkSCRIJEgkRChEKEQoRCRIJEgkSCREKEQoRCRIJEgkSCRIJEQoRChEKEQkSCRIJEgkRChEjESMRJBAKEQoRCRIJEgkSCREADQU=", + "28":"JgDMAV82DyUOJg4NDgoRDA8lDwwPDA8lDyUPDA8lEAsPDA8lDyUPDA8lDyUQCw8MDiYODQ4NDg0OJRAJEgsPDA8MDwwODQ4NDg0ODA8KEQwPDA8MDg0ODQ4NDg0ODA8NDgwPJQ8MDg0ODQ4NDg0ODA8MDyUPJRALDwwPDA8MDg0ODQ4NDgwPJQ8lDwwPDA8MDwwODQ4NDgwQCw8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4KEQ0ODBALDwwPDA4mDwwOJg8MDwwOAAj/YzYPJQ8lDwwQCw8MDyUPDA4NDiYOJg4ODiUODQ4NDiYOJg4NDiYOJg8MDg0OJg8LEAsPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDgoSDA4MDw0OJRQHDg0OJg4NDg0OJg4NDgwPDA8MDwwPJQ8MDiYPJQ8MDwwPDA4NDg0ODA8lDw0OJQ8MDw0ODA8MDg0ODg0NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0PDQ0mDwwOJg4NDg0ODA8MDwwPDA8ADQU=", + "29":"JgDMAV42DyUPJRALDwwPDA8lDwwODg0mDyUPDA4mDg0PDA4lDyYODA8lECQPDA8MDyUQCw8MDwwOJg4NDg0ODQ4NDgwQDA4LEA0ODA4NDgoRDQ4NDgwPDA8MDg0ODQ4NDg0OJg4MDwwPChEMDg0ODQ4NDiYOJg4NDg0PCw8MDwwODQ4NDg0OJg4mDg0OChEMDwwPDA8MDg0ODQ4NDg0ODQ4MEAsPDA8MDg0ODQ4NDg0ODA8MDwwODQ8MDg0PDA4NDgwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4lDwwPJQ8MDwwPAAkCYDYOJg4mDwwPDA4MDyUPDA8MDyUQJBALDyUQCw8MDyUOJg4NDyUPJQ8MDg0OJg4ODQ4NJg4ODgwPDA8LDw0ODQ0ODg0NDA8MEAwODQ4MDg0ODQ4NDg0OJQ8MDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPJQ8MDwwPJQ8MDwwPDQ0ODQ0ODQ4mDg0OJRALDwwPDA4NDg0ODQ4NDgwPDBALDw0NDg0NDg0ODQ8LDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDiYPDA4NDg4NDQ4MDwwPDA8ADQU=", + "30":"JgDMAV82DiYOJg4NDgwPDA8lDwwPDQ4lDiYPDA4mDg0ODQ4mDiYODQ4mDiYODQ4NDiYPDA4MDw0PJA8MDw0NDQ4NDg0PDA8MDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0OJg4ODQ0ODQ8LDwwPDQ4MDiYPJQ4NDg0ODQ8LDwwPDA8MDw0NJg8lDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8KEQoRDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwkSDA4NDg0ODQ4mDg0PJQ8MDgwPAAj+ZDYOJg4mDg0ODg0NDiUPDA8MDiYPJQ8MDiYPDA4NDiYPJQ8NDSYOJg4NDg0PJQ8LDwwPJQ8KEgsPDA4NDg0ODQ4NDgwPDA8MDwwODQ8MDg0ODQ8MDgwPJRALDwwPJQ8MDg0OJg4NDg0ODQ4NDgwQJA8MECQPDA8MDwwODQ4NDgwPDA8lDwoRJQ8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0PDA4NDg0ODQ4MDw0ODA8MDg0ODQ4ODQ0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4mDg0ODA8MDwwPDA4NDg0ODQ4ADQU=", + "31":"JgDMAV42DyUPJQ8MDwwPDA4mDg0ODQ4mDyUODQ4mDgwPDA8lDyUPDA8lECQPDA8KEiQPDA4NDg0OJg4NDg0ODA8MDwwPDA4NDg0PDA4NDg0ODA8MDwwODQ4NDg0ODQ4NDgwPJQ8MDwwPDA8MDg0ODQ4MDyYOJg4NDg0ODA8MDwwPDA4NDg0OJg4mDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8lDwwPJQ8MDwwPAAj/YjYQJQ4mDgwPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg4NDiUPJg4MDwwPJQ8MDwoRJQ8MDwwODQ4NDgoRDA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPJQ8MDg0OJg4NDg0OJg8MDgwPDBALDwwPJQ8MDg0ODQ4NDgwPDA8KEA0ODQ4mDg0OJg8MDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA8lDyUQJA8lDyUPJQ8lDyUPJQ8ADQU=" + }, + "auto":{ + "16":"JgDMAWA1DyUPJQ8MDg0ODQ4mDg0NDg4lECUODA8lEAwODA4nDSYODg0mDiYPDQ0ODSYPDA4ODQwPJQ8NDg0ODA4NDwwODg4MDg0ODQ4MDwwPDQ0ODQ0ODQ4NDg0ODA8NDQ0OJg4ODQ0PDA8MDgwPDA4nDQ4NDQ4NDg0ODQ4MDw0ODQ4NDQ4MJw4mDwwODQ4NDg0PDA4NDQ4ODA4ODQ0ODQ4NDg0ODQ4NDQ4ODA4NDg0ODQ4NDQ0ODg0ODQ0ODA8NDg0ODQ4MDgoRDQ4NDg0ODA8NDg0NDQ0PDQ0ODQ4mDg0ODQ4MDyUPAAj+ZDUPJg4lDwwPDA8MDiYODgwPDiUOJg4ODSYODQ4ODSYPJQ4NDiYOJg4NDgwPJQ8NDg0OJRAMDg0NDQ8MDgwPDQ4NDgwPDQ0ODQ0ODQ4NDg0ODA8MDg4NJg4ODQ0OJg4NDg0OJg4NDgwPDQ4MDw0NDg0ODSYOJg4mDiYODQ4NDQ4ODQ4MDwwPDQ0NDw0NDQ8LDw0ODQ0ODQ0ODQ4NDg0ODQ4MDg0ODg0ODg0NDQ8MDg0NDg0ODQ4NDg0NDg0ODQ4NDgwODg0MEAwODQ4NDgwQCw8lDyURCg8MDiYODg0ODSYPJQ8ADQU=", + "17":"JgDMAV82DyUPJQ8NDg0NDg0mDg4NDQ4mDiYPCREmDwsPDQ4lDyYODQ4mDScPDA4MDyYODA8MDg0OJg4NDwwOChEMDwwPDQ4NDQ4NDgwODgwPDA8MDg0ODg0ODQ0ODQ4MDw0OJg4NDg0NDQ4ODQ0ODQ4mDg0ODQ4NDg0NDQ4ODQ0ODQ4MDwwOJg8lDwwPDA4ODQ4NDQ4NDg0ODQ4NDwwNDgwPDg0NDQ4NDg0ODQ4NDg0ODQ0ODQ0ODQ4NDQ4NDg0NDwwNDg4NDg0ODA4NDg4NDg0NDg0OChEMEAwODQ0mDwwPDQ0NDiYOAAkBYTYOJg8lDw0NDA8NDiYODQ4NDiUPJg4NDiUPDA8MDiYPJQ4KESYPJQ8NDQ0OJw4MDg0OJg4NDg0ODQ4NDg0ODA4ODg0MDg4NDwsPDQ4MDg4NDg0NDg0OJg4NDgwPJQ8NDwwNJg4NDg0ODg0NDQ0PDA8NDQ4NJg8lDiYPDA4NDg0ODg0NDg0ODQ4MDw0ODQ0ODQ4NDQ4NDg0ODA4ODQ0ODQ0ODg0ODQ4MDw0ODQ0ODQ4NDQ4NDg0ODQ0ODQ4NDQ4NDg0ODQ4NDgwNDw4MDgoSDQ0mDwwODQ4NDiUPDA4ODSYOJg8ADQU=", + "18":"JgDMAV81DyUPJQ8NDgwODQ8lDg4NDgwnDiYODQ4mDgwPDA8lECQPDA8lECQPDQ4NDiUODQ4NDg4NJg4NDg0NDg4NDgwPDQ0NDwwODQ4MDwwPDA8MDg4NDQ4ODQ0ODQ4MDw0OJQ4ODQ0ODg0NDgoRDQ4lDw0ODA8MDg4NDQ4NDgwQCw8NDg0NJw4lDw0NDg0ODQ4NDQ4NDwwNDg4NDg0NDQ4ODQ0ODQ4MDw0NDQ4NDg0ODQ4MDwwPCREODQ4NDA8ODQ0PDA4NDgwODQ4ODQ0ODA8MDwwODg0ODQ0ODQ4mDg0PDA4MDyUPAAj/YjcPJQ4mDwwODQ4NDiUQDA4MDyUOJg4ODSYPDQ0NDiYOJg4NDyUPJQ4NDwsPJQ8NDg0OJQ8KEQkRDg0NDg0PDA4NDg0NDg0ODQ0ODg0NDg0ODA8MDwwPJQ8MDg0OJg4ODQ0OJg8MDg0PCxALDwwODQ0ODiYOChEmDyUODQ4NDg0ODA8MDwwPDQ0NDg0ODQ4MDwwODg0ODQ0PDA4NDwwODA8MDwwPDA8MDwwODQ4NDgwPDA4NDg0PDA8NDQ0ODQ4MDw0NDQ8MDg0ODQ4NDQ4ODQ0mDiYOJg8lDwwPDA8MDyUPJQ4ADQU=", + "19":"JgDMAV42DyUPJQ8NDg0NDg0mDg0ODg0mDiYODQ4mDg0OChElDyUQCxAkDyUPDA8MDyUPDA8NDQ4NJg4NDg0ODQ4MDw0ODA4NDg4NDg0ODQ0PCw8MDw0NDQ4ODQ0ODQ4MEAsPJQ8MDw0NDg0NDg0ODQ0nDg0PCw8NDg0NDg0ODQ0ODQ4MDwwPJQ8lDw0NDg0NDw0NDg0NDgwPDQ0NDw0NDg0NDg0ODA8NDgwODg0ODQ0PCw8NDwwODQ0NDg4NDQ8MDgoQDg4NDg0ODQ0ODQ0ODQ4NDQ4ODQ0ODQ0ODQ4mDwwODA8MDyUPAAj+ZDUPJg4lDw0ODQ4MDiYODg0NDSgNJg8NDiUPDA4ODSYOJg4NDiYOJg4NDg0OJQ8MDw0OJQ8NDQ4NDg0NDg0ODBALDw0NDg0ODQ0ODg0NDwwODQ4NDg0NJg8KEA4NJg4NDg0OJQ8MEAwODQ0ODQ4NDg0NDQ4ODQ4lDyUPDA4ODQ0ODQ4NDg0ODQ4NDgwPDA4NDQ8NDQ4NDQ0PDQ4NDQ4NDg0NDQ8NDQ4NDg0ODA4NDg4NDQ4NDg0ODA4ODQ0ODA8ODQ0ODQ4NDg0ODQ0ODQ4NDg0mDg0OJg8lDgwPDA8NDSYPJQ8ADQU=", + "20":"JgDMAV82DiYOJg8MDwwODA8lEAsPDA8lDyUQDA4lDw0ODA4mDyUPDA4mDiYPDA4NDiYPDA4NDg0OJg4MDw0ODA4ODQ4NDQ8LDw0NDQ4NDg4NDg0NDg0ODA8MDwwODQ8NDQ4NJg4KEQ0ODA8NDQ4NDQ4mDg0ODg0NDgwPDA8NDQ4ODQ0NDw0NJg4mDwwOChEMEAsQDA4MDg0ODg0NDg0ODQ4NDQ4NDQ8MDg0PDA4NDgwPDA4NDwwODQ4NDg0NDg4MDw0NDQ4ODQ0ODQ4MDwoRDA8NDQ0ODA8ODQ0MDg8lEAwODA4NDyUPAAkCYDUPJQ8lDw0ODQ0ODSYODQ4NDiYOJg8MDiYODQ4NDiUPJRALDyUPJRAJEQ0OJQ8MEAwOJQ4ODQ0ODQ4NDg0ODA8MDg4NDg0NDg0ODQ4NDwsPDQ8MDQ4NJg4NDg4MJw4MDw0OJQ8MDw0NDg0ODA4NDg4NDiYOJg8MDiYODQ4MDwwPDQ0ODQ4NDQ4NDg0ODA4NDg4NDg4MDg0ODA8NDg0NDg0ODA8NDQ4NDg0ODA8NDQ4NDg0NDQ4ODQ8MDgwPDA8NDQ0ODg0NDgwPDQ4MDg0ODg0mDiYODg0mDwwODA8MDyUPJg4ADQU=", + "21":"JgDMAV82DyUOJg8MDwwODQ8lDg0PDA4lDyYODA8lDwwPDA8lDiYODQ4mDiYODg0NDiYODg0MDwwPJg8MDwwNDQ4NDwwODQ4NDg0ODQ4NDgwODg0ODg0NDA8NDg0ODQ0KEQ0OJg8MDg0PDA4NDgwODgwnDwwODg0NDgwPDA8NDgwOChEODQ4NJg8lDwwODQ4MDwwODQ4ODQ4NDQ4NDgoRDQ4MDwwODg0NDg0ODQ8LDwoRDQ4NDQ4NDQ4NDwsPDA8NDQ4ODQ4NDQ0ODQ4NDg0ODA0PDQ4NDQ4NDg0ODA4mDwwODQ4MDyYOAAj+ZDYOJg4mDg4NDQ4NDiYODQ4NDSYPJg4NDyQQDA4MDyUOJg4NDiYOJg8NDQ4OJQ4ODQ0OJg4NDwwODQ4MDg4NDg0ODQ0ODA8MDwwPDA4ODQ4NDQ4ODgwOJg8LEAsQJA8NDg0OJQ4NDg4ODA4NDgwPDA4ODQ0OJg4NDyUODQ8MDwwODQ4MDwwPDA4NDg4NDQ8LDwwPCxANDQ4ODA4NDg0ODQ4NDQ4NDQ4NDg0ODQ4MDw0ODQ4NDg0NDg0NDg0ODQ4MDg4NDQ4ODQ0ODQ4NDg0ODQ0mDw0NDg0mDg0ODQ8MDiYPJQ4ADQU=", + "22":"JgDMAWE1DSYOJg4NDg4NDQ4mDwwODA8lDyUPDQ4lDw0NDQ4mDiYODQ4mDiYQCw4NDiYNDg8MDg0OJQ8NDg0ODQ4MDg0ODg0ODA4NDg4MDwwODg0NDg4ODA4NDwoRCw4ODgwOJg4ODQ4MDg8MDg0ODA8lDg0PDQ0ODQ0ODQ4MDw0ODQ4NDQ0OJw0mDg4MDw0NDg0ODQ0ODQ0ODgwODg0ODQ0ODgsQDA4ODQ0ODg0NDg0ODA8NDQ4NDg0NDg0ODA8MDg4NDQ8MDg0PDA4NDg0ODQ4MDw0NDg0NDg0ODA8lDw0ODA8MDiYOAAj/YzYPJQ4mDg0ODg0NDyUPCw8NDyUOJg4NDiYPDA4NDiUPJQ8NDiUPJQ8MDg0OJg4NDg0OJg4NDwsPDA8NDwsPDA8NDQ4NDQ8MDgwPDA8MDg0ODg4NDQ0OJQ8MEAsQJA8NDg0OJQ4NDg0ODg0NDg0ODQ4NDiUPDA8MDyUPDA8NDQ0ODQ4NDgwQCw4NDg4NDQ0ODg0ODQ4NDQ0ODQ4NDg4NDQ4NDgwQCw8KEA4NDg0NDgoSDA0ODgwPDA4ODQ0ODQ8MDQ4NDg4MDg0ODg0ODQ0ODQ4lDyUPJQ8MDwwPDQ0ODSYOJg4ADQU=", + "23":"JgDMAV82DiYOJg4ODQwQDA4mDg0PDA8kDyUPDQ4lDw0NDQ8lDiYODg0mDiYODQ4ODSYODg0NDwwOJg8MDg0PCw8NDgwODg0NDgoRDQ4MDw0NDg0ODQ4NDg0NDg0ODQ4NDgwOJg4ODQ0PDA4MDwwPDQ0mDg0PDA4ODQ4NDg4MDg0ODA8NDQ4NJg4mDg4NDg0ODQ0ODQ4MDwwPDA4NDg0ODQ4NDg0ODA8NDQ0PDA4ODQ0ODQ4MDw0NDg0ODQ4ODA4KEQ0ODQ4NDQ0ODQ4ODQ0ODQ8LDwwPDA4NDg4NDQ4mDwwODQ4MDyUPAAj+ZDUPJg8lDg0ODQ8LDyUQCw8MDyUPJQ8MDiYODQ4ODSYOJg4NDiYPJQ4ODgwOJg4NDQ4OJRAMDgwPDQ0ODQ4MDg4MDwwPDQ4MDw0NDQ4NDg4NDQ4NDgwPJQ8MDwwOJg8MDg0OJg4ODQ0ODQ4NDgwPDQ0ODA8ODA8MDiYODQ4MDwwPDA4ODQ4NDQ4NDgwPDQ4NDg0NDg0NDg0ODBAMDgwODQ4NDg0ODQ4MDwwPDA4NDw0NDQ4NDwwODQ8MDg0ODA4NDg0ODQ4MDwwODQ8MDg0ODg0mDw0NJg4NDg0ODQ4NDiUPJRAADQU=", + "24":"JgDMAV42DyUPJQ8MDg4NDg0nDQ0ODQ4lDyUQCxAkDwwPDA4mDyUPDA4mECQPDA4ODSYPDA4NDg4NJg4NDg0PCw8NDgwODg0ODgwODQ4MDg4ODA8NDQ0ODQ4NDQ4ODA8NDg0OJQ8MDg4NDg0NDg4ODA4lDw0NDg0ODQ4OCREMDwwPDA8NDg0NJg4mDwwODg0NDg0ODQ0ODg0NDg4MDQ4QCw4MDw0ODA8NDg0ODA8NDQ0ODQ4NDgwPDA4ODQ0NDw0MDw0ODQ4MDw0NDg0ODQ0ODQ4KEQ0ODA0PDQ0NDg4mDg0ODQ4NDiUPAAj+YzcNJw4mDgoSCRENDiYODA8NDScNJw0NDScODQ0PDSYOJg4NDiYPJQ4NDg0NJg8MDwwPJQ8MDw0NDg0ODgoRDA4MDwwPDQ4NDQ4MDw0NDQ4ODA8NDg0OJQ8MDg0OJg4ODQ0OJg8JEgwODQ4NDgwODQ4NDiYOJw0mDw0NDQ8LDwwPDQ4MDg4NChENDg0ODA8MEAwNDg0ODQ0ODg0NDwwODQ4NDg0NDQ8NDQ0ODA8MDg0ODg4NDQ4NDQ8MDg0ODA4ODQ4NDQ4NDg0ODA8NDgwODQ4mDScPDA4ODQ0ODQ4NDiUQJA8ADQU=", + "25":"JgDMAV82DiYOJg4ODQ0ODQ4mDg0ODQ4lDyUPDA8lDw0NDg0mDiYODg0mECQODQ4ODSYODg0ODQ0OJg4NDg0NDg4MDg4NDQ4KEgsPDA8NDg0NDQ4ODQ4ODA4NDg0NDg4MDwwOJw4MDg4MDg4NDg0ODgwmDg4NDQ8MDw0NDQ4MDw0ODQ8MDQ0OJg4mDg4NDQ4NDg0ODQ4MDw0ODA4ODQ4MDg4NDg0ODQ4MDg4NDg4MDQ4ODQ4NDgwODg0NDQ8NDQ8MDg0ODA8NDQ0ODQ4NDwwODQ4NDQ4NDQ4ODQ0ODQ4lEAwOCxANDSYOAAj/YzcPJQ0nDg0ODQ4NDiYPCw8MDiYPJQ8MDyUODQ4ODCcPJQ8MDiYPJQ8MDg0OJg8MDwsQJBAMDgwPDA8MDg0ODQ4MDwwPDA8LEA0NDQ4NDg0PDA4MDg4OJQ8MDw0NJg4NDg0OJg8MDg0ODQ4MDw0ODQ0NDg0NJw4mDg0ODQ4MDwwPDQ4KEAwPDg0ODA4ODQ4MDwwPDQ0NDg0ODQ4NDg0ODA8MDwwODQ4ODA4ODQ4MDw0ODA8MDg8MDQ4NDg0ODA4ODQ4NDg0NDwwOChEMDwwPDQ4lDg0PDA4NDQ4ODBALDiYPJQ8ADQU=", + "26":"JgDMAWA1DyUOJg8NDQ4NDQ4nDQ0ODA8mDyUODA8lDw0ODQ4mDSYODQ4mDyUPDA4ODSYPDA4NDg0NJw4KEQ0PCw8NDQ4NDg4MDg0MDw4NDgwODg0ODQ4NDg0NDg0ODQ4NDg0NJg8NDQ4MDw4MDgwPDQ4lDwwODA8MDw0ODQ4NDg0ODQ4MDg4NJg4mDg0ODg0ODQ0ODQ4NDg0NDgwPDQ4NDQ4MDwwPDQ4NDQ4NDg0NDg0PDA4NDgwPDA4ODQ0PDA8MDg0NDg4MDg0ODgwPDQ0ODQ4MDw0ODQ0ODQ4ODA4mDwwNDg4NDiUPAAkBYDYPJRAkDw0ODQ0NDyUQCw8NDSYOJg4NDiYODg0ODSYOJg8MDiYOJg8JEQ0PJQ4NDg0OJg4NDQ0PDQ0ODQ4NDQ4MDwwODQ4ODQ0PDQ0NDg0ODQ4MDwwOJg8NDQ4NJg4ODQ0OJRAMDg0ODQ0NDg0ODg0NDiYPDA4mDg0ODQ4MDw0NDQ4ODQ0ODQ4MDwwPCxANDQ0ODQ4ODQ0ODA8MDwwODQ8MDg0ODQ8LDw0ODQ0NDg4NDg0ODQ0ODQ4NDwwNDg0ODQ4NDQ4NDg0ODQ4NDg0NDgwnDiYOJg4mDiYOJg4mDwwOJw0ADQU=", + "27":"JgDMAWA1DyUOJg8NDQ0PDQ0mDg0ODA8lDyUPDQ4lDwwPDQ0mDiYPDQ0nDSYPDA4NDiYODQ8MDgwPJQ8NDgwPDA4ODQ0PChANDg0ODQ8MDgwPDQ0NDg0ODQ4MDwwQCRAODQ4NJw0NDg0ODQ4NDg0ODA4mDwwPDA4ODQ4ODA8LDw0ODQ4MDg0PJQ8lDg0ODg0NDg0ODQ4MDg0ODQ4ODQ0ODQ4NDgwPDQ0ODQ0PDA8MDg0ODA8MDg4NDg0NDwwODQ8MDg0ODQ0ODQ0ODg0NDg0PDA4MDw0ODQ0NDwwODg0mDg0PDA4NDiUPAAj+ZTQPJg8lDwwPDA4NDiUQDA4MDyUPJQ8MDyUPDA8MDiYPJQ4ODSYOJg4NDg0OJg4NDwsQJQ8MDg0ODQ4NDQ0ODg0NDg0ODA8MDw0NDQ4NDg4MDg4NDg0OJQ8MDw0NJg4MEAwOJg8NDQ0ODQ4NDgwPDQ0NDg0PDA8lDg0ODQ4NDgwPDQ4MDwwODQ4ODQ0OChENDgwPDA8NDA4PDA4NDwwODQ4MDw0NDQ4ODQwPDA8NDgwODg0NDwwODQ8LDwwPDQ0ODQ4NDgwODg0PDA4MDwwPDA8lDg0OJg4mDiYOJg4mDg0OJg4ADQU=", + "28":"JgDMAV82DyUPJQ8NDgwODQ4mDg4NDQ4mDiYODg0mDg0ODQ4mDiYODQ4lDyUPDQ4NDiUPDA0PDg0NJg4NDwwODQ4MDwwPDQ4NDQ4NDQ4ODQ4NDA8NDwwODQ4NDQ4NDg4JEQ0NJg8MDwwPDQ0ODQ4MDg4mDg0PDA8LDw0ODA4NDQ4ODQ4NDgwQJBAlDgwPDQ4NDQ4NDg0NDg0ODQ4MDQ8ODQ0ODQ4ODA0ODgwPDQwPDg0NDg0NDg0ODg8KDw0ODQ4NDQ4NDQ4ODQ0ODA8NDg0ODQ0ODQ4NDQ4NDQ4ODQ0mDw0NDg0NDiYOAAj/ZDUOJg4mDwwODg0ODSYPDA8MDSYQJQ4NDiYPDA4NDiUPJg8LECUPJA8MDw0OJQ8MDg4NJg4ODQ0ODQ0ODg0ODBALDw0ODQ0NDg0ODA8MDwoQDg0ODQ4NJg4NDg0OJg8MDg0OJg4NDg0NDQ4MDw4NChENDiYOJg0ODg0ODQ4NDQ4NDgwPDQ4OCRENDgwPDQ4NDQ4ODA4ODQ0ODA8NDQ0ODQ4ODQ0ODQ4MDwwPDQ0ODg0NDg0ODQ0ODQ4NDg0ODQ4MDw0ODQ0NDgwODg4NDg0NDg0nDSYODQ4mDiYOJg4mDg0OJg4ADQU=", + "29":"JgDMAV82DicNJg8NDQ0ODQ0nDg0NDQ8lDyUPDA8lDgsRDQ0mDiYNDwwnDiYODg4NDSYODRALDgwPJQ8NDg0NDQ4NDg4NDQ4NDg0PDA4NDg0NDg0ODQ0NDw0NDg0ODA8MDw0NJg4ODQ4MDw0NDwsPDA8lDwwPDQ0ODQ4NDQ4NDwsPDQ4NDQ4NJg4nDQoTDA0NDwwODQ4MDw0ODQ4NDQ4NDg0NDg0ODA0ODg4NDg0ODQ0ODQ0NDw0NDg0ODQ4ODQ0NDg0ODQ4NDg0ODQ0ODQ4NDQ4NDg0ODQ4KEQwODQ0nDg0ODQ4MDyYOAAj/YzYOJg4mDg4NDQ4MDyYPCxALDyUPJQ8NDScODA4NDiYPJQ4NDiYOJg4ODQ4NJg8NDgwOJQ8MDw0ODQ0ODQ4NDQ4NDg0PDA4NDg0OCxANDQ0ODQ4NDg0OJQ8KEQ0NJg8NDQ0OJw0NDg0ODg0ODQ0ODA8NDg0NJg4ODQ4ODQ0NDQ4ODA8MDwwODg0ODQ0ODQ4MDwwPDQ0ODg0NDQ4NDQ4ODQ4MDg4NDQ4NDg0ODQ0ODgwPDA8ODQ0ODA4NDg4NDQ4MDwwPDQ0NDQ8MDw0ODQwPDQ4lDw0ODQ0mDiYPJQ8lDw0NJg4ADQU=", + "30":"JgDMAWA0ESMRIxEJEgkSCREkEAoRChEjESMRChEjEQkSCRIjESMRCRIjESMRCRIJEiMRCREKEQoRIxEJEgkSCRIJEQoRChEKEQkSCRIJEgkSCREKEQoRChEJEgkSCREKEQoRIxEKEQoRCRIJEgkSCREkEAoRChEJEgkSCRIJEQoRChEKEQoRIxEjEQkSCRIJEQoRChEKEQkSCRIJEgkSCRIJEQoRChEJEgkSCRIJEgkRChEKEQkSCRIJEgkRChEKEQoRCRIJEgkSCRIJEQoRChEKEQkSCRIJEgkRChEkEAoRChEJEiMRAAj7ZjMRIxEkEQkRChEJEiMRCRIJEiMRIxEJESQQChEKESMRIxEKESMRIxEKEQkSIxEJEgkSIxEJEQoRChEKEQkSCRIJEgkSCREKEQoRCRIJEgkSCRIJEQoRIxEKEQkSIxEJEgkSIxEJEQoRChEKEQkSCRIJEiMRCREKEQoRChEJEgkSCRIJEQoRChEKEQoRCRIJEgkSCRIJEQoRChEJEgkSCREKEQoRChEKEQkSCRIJEgkRChEKEQkSCRIJEgkSCREKEQoRChEKEQkSCRIJEQoRChEjESMRIxEKESMRIxEjEQkSIxEADQU=", + "31":"JgDMAV82DyUQJA8MDwwPDQ0mDwwPDA4mDiYPDA8lDg0ODQ8lDiYODQ4mDiYODQ4NDiUPDA8MDw0OJQ4NDg0ODQ4ODgwODQ4NDwsPDA4NDg0ODQ4MDw0PDA8MDgwODQ4ODgwOJg4MDwwPDQ4NDg0NDQ4mDwwODQ4MDwwPDQ4MDw0NDQ8MDg0OJg4mDg0ODQ8LDwwPDQ4MDg0ODQ4NDg0ODQ8LEAwODQ0NDg0ODQ4NDwwPDA4MDwwODQ4NDg0ODA8NDgwODQ4ODQ4NDQ4NDgwPDA8NDgwODg4NDQ0ODQ4lDwwPDA8MDiYOAAkBYjYOJg8lDg0ODQ4MDyUQCw8MDyUPJQ8NDSYPDA4NDiYOJg4NDiYPJQ4NDg0OJg4MDwwPJQ8MDwwPDQ4MDwwODQ4NDgwPCxAMDg0ODQ4NDg0ODA8NDg0OJQ8MDg0OJg8MDg4NJg4NDg0ODA8MDwwPDA4NDg0ODg4MDgwPDA8MDw0NDg4NDQ4NDQ8LDw0ODA4NDg0ODQ4NDgwPDBALDwwODQ4NDg0ODQ4NDg0ODA8NDgwODQ4NDg4ODA4MDwwPDQ4MDg0ODQ4NDgwPDQ4MDg0ODQ4mDg0OJg4NDiYPJQ4mDwwOJg4ADQU=" + } + }, + "cool":{ + "silent":{ + "16":"JgDMAV82DiYOJg4NDg0ODQ4mDg0ODA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA8MDiYODQ4NDg0OJRALDwwPDA8MDg0ODQ4NDgwPDA8KEQwPDA4NDg0ODQ4MDwwPDA8MDyUPJQ8MDg0ODQ4NDg0ODA8MDyUPDA8MDg0ODQ8MDgwPDA8MDwwOJg8lDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4mDg0ODA8mDiYOAAj+YzYPJg4mDg0ODA8MDyUPDA8MDyUPJQ8MDyUPDA4NDiYOJg4NDiYPJQ4NDg0OJg4MDwwPJQ8MDwwPChEMDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPJQ8IEwwOJg4NDg0OJg4mDgwPDA8MDwwPDA4NDiYOJg4mDyUPDA4NDg0OChENDiUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDwsPDA8MDwwPDA4NDwwODQ4MDwwQCw8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4mDiYPDA4lDyUQCw8MDyUPJQ8ADQU=", + "17":"JgDMAV82DyUPJQ8MDwwPDA8lDwwODQ4mDiYODQ4mDg0ODQ4mDiYODA8mDiYODA8MDyUPDA8MDwwOJg4NDg0ODA8MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDiUPJRALDwwPDA8JEQ0ODQ4MDyUPDA8MEAsPDA4NDg0ODQ4MDwwPJQ8lDwwPDA8MDg0ODQ4NDg0ODQ4MDwoRDA8MDg0ODQ4MDwwPDA8MDwwODQ4KEQ0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8lDwwODQ4mDiYOAAj/YzYOJg4mDg0ODQ4NDiYODQ4MDyUPJQ8MDyUPDA8MDyUPJQ8MDyUPJQ8MDwwOJg4NDg0OJg4MDwwPCBMMDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0OJQ8MDwwPJQ8MDwwPJQ8lDwwODQ4NDg0ODQ4MDwwPJQ8lECQPDA8MDwwODQ4NDiYODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDggTDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4KEQwPDA8MDwwPDA4mDg0ODQ4mDiYODQ4MDyUPJQ8ADQU=", + "18":"JgDMAWAzESMRIxEJEgkSCRIjEQkSCRIjESMQChEkEAoRCRIjESMRCRIjESMRCRIJEiMRCRIJEQoRJBAKEQkSCRIJEgkRChEKEQoRCRIJEgkSCRIJEQoRChEJEgkSCRIJEiMRIxEJEgkRChEKEQkSCRIJEiMRCREKEQoRCRIJEgkSCRIJEQoRJBAkEAoRChEJEgkSCRIJEgkRChEKEQkSCRIJEgkRChEKEQkSCRIJEgkRChEKEQkSCRIJEgkSCREKEQoRCRIJEgkSCRIJEQoRChEKEQkSCRIJEgkRChEjEQoRChEjESMRAAj8ZTMRIxEjEQoRCRIJEiMRCRIJESQQJBEJESQQChEKESMRIxEKESMRIxEJEgkSIxEJEgkSIxEJEgkRChEKEQoRCRIJEgkSCREKEQoRChEJEgkSCREKEQoRIxEKEQkSIxEJEgkSIxEjEQkSCRIJEQoRChEJEiMRCRIjESMRCRIJEgkRChEKESMRCRIJEgkSCREKEQoRCRIJEgkSCRIJEQoRChEKEQoRCRIJEgkSCREKEQoRChEJEgkSCRIJEQoRChEKEQkSCRIJEgkRChEKEQkSCRIjESMRIxEJEiMRCREKESQQJBAADQU=", + "19":"JgDMAV82DiYPJQ8MDg0ODQ4mDg0ODQ4mDiYODA8lDwwPDA8lDyUPDA8lDyUPDA8MDiYPDA4NDg0OJg4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDyoKJQ8MDwwPDA4NDggTDQ4NDiUPDA8MDwwODQ4NDg0ODA8MDwwPJQ8lDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwQCw8MDg0ODQ4NDgwPDA8MDwwPDA8lDwwOChEmDiYOAAkAYjYPJQ4mDg0ODQ4NDiYODQ4MDyUPJQ8MDyUPDA8MDyUPJQ8MDyUPJQ8MDg0OJg4NDg0OJg4NDwsPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDg0ODQ4NDg0OJg4NDgwPJQ8MDwwPJQ8lDwkRDQ4NDg0ODA8MDwwPDA8lDyUPDA4NDgoRDQ4MDyUPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4mDg0OJg4NDiYODA8MDyUPJQ8ADQU=", + "20":"JgDMAV82DyUPJQ8MDwkSDA4mDwwODQ4mDiYODQ4mDg0ODQ4mDiYODQ4mDiUPDA8MDyUPDA8MDwwOJg4NDg0ODQ4MDwwPDA4NDg0OChEMDwwPDA8MDwwODQ4NDg0ODA8MDyUPJQ8MDwwPDA4NDg0ODQ4MDyUPDA8MDwwPDA4NDg0ODQ4NDgwPJQ8lDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4mDg0ODQ4mDiYOAAkDXzYOJg4mDg0ODQ4NDiYODA8MDyUPJQ8MDyUPDA8MDiYOJg8MDiYOJg4NDg0OJg4NDg0OJg4NDgwPDA8MDwwODQ8MDg0ODA8MDwwPDA4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0OJg4mDg0ODQ4MDwwPDA8MDiYPJQ8JESYODQ4NDg0ODA8MDyUPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPCRENDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8lDyUPDA8MDyUPDA8MDiYOJg4ADQU=", + "21":"JgDMAV82DiYOJg4NDwwODA8lDwwPDA8lDyUPDA4mDg0ODQ4mDiYODQ4mDiYODQ4NDyUODQ4NDgwPJQ8MDwwPDA4KEQ0ODQ4MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPChANDiYOJg4NDg0ODQ4MDwwPDA8MDiYODQ4NDg0ODA8MDwwPDA8MDg0OJg4mDg0ODQ4NDgwPDA8KEQwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPChANDg0ODQ4MDwwPDA8MDg0ODQ8MDgwPDA8lDwwPDA8lDyUPAAj+YzYPJQ8lDwwPDA8MDiYODQ4NDiYPJQ4NDiYOChENDiYOJg4MDyUPJg4NDgwQJA8KEQwPJQ8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwOJg4KEgwOJQ8MDwwPJRAkEAsPDA8MDwwODQ4NDg0OJQ8MDyUPDA8MDwwPDA4NDiYOChENDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDQ4JEgwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4mDg0ODQ4NDiYODQ4MDyUPJQ8ADQU=", + "22":"JgDMAV82DiYOJg4NDg0ODQ4mDg0ODQ4lDyYODA8lDwwPDA8lDyUPDA8lDiYODQ4NDiYODQ4NDg0OJg4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDiYOJg4KEgwODQ4NDgwPDA8MDyUPDA8MDg0ODQ4MDwoSCw8MDwwPJQ8lDwwODQ4NDgwPDA8MEAsPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4mDg0ODA8lDyUPAAj+ZDYOJg8lDgwPDA8MDyUPDA8MDiYPJQ8MDyUPDA4NDiYOJg4NDiYOJg4NDgwPJQ8MDwwPJQ8MDwwPDA4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwPJQ8MDg0OJg4NDgwPJg4mDg0ODA8MDwwPDA4NDiYODQ4NDyQPDA8MDwwPCRIJESYODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8lDyUPJQ8lDwwPDA8MDiYOJg8ADQU=", + "23":"JgDMAV42DyYPJQ4NDgwPDA8lDwwODQ4mDiYODQ4mDg0ODQ4mDiYODQ4mDiYODA8MDyUPDA8MDg0OJg8MDwwODQ4NDgwQCw8MDwwODQ4NDg0ODQ4MDwwPDA4NDgoRDQ4MDyUPJQ8MDwwPDA8MDwwPDA8MDiYODQ4NDgwPDA8MDwwODQ4NDgwPJQ8lDwwPDA8MDg0ODQ8MDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8lDwwODQ4mDiYOAAj+ZDYOJg4mDg0PDA4MDyYODQ4MDyUPJQ8MDyUPDA8MDiYPJQ8MDiYOJg4NDg0OJg4NDgwPJQ8MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0PDA4MDwwPJQ8MDwwPJQ8MDwwOJg4mDg0ODQ4MDwwQCw8MDwwPDA4NDiYODQ4NDgwPDA8MDyUPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8lDwwPJQ8lDwwPDA8MDiYOJg4ADQU=", + "24":"JgDMAV82DyUPJQ8MDwwPDA8lDwwPDA8lDyUPDA4mDg0ODQ4mDiYODQ4mDiYODA8MDyUPDA8MDwwOJg4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDiUPJQ8MDwwPDA8MDwwODQ4NDiYODA8MDwoRDA8MDwwODQ4NDg0OJg4mDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ8MDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA8lDwwODQ4mDiYPAAj+YzYOJg4mDg0ODQ4NDyUODQ4MDyUPJQ8MDyUPDA8MDyUPJQ8MDyUQJA8MDg0OJg8MDg0OJg4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0OJg4MEAsPJQ8MDwwOJg8lDwkRDQ4KEQ0ODA8IEyUPJg4lDwwPDA8MDg0ODQ4NDiYODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0OChENDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4mDiYODQ4mDwwODQ4MDyUPJQ8ADQU=", + "25":"JgDMAV82DyUPJQ8MDgoSDA4mDg0ODA8lDyUQCw8lDwwPDA4mDiYPDA4mDyUPDA8MDiYPDA4NDg0OJg4NDwsPDA8MDwwODQ8MDgwPDA8MDwwPDA4NDg0PDA4MDwwPDA8MDiYPJQ8MDg0ODQ4MDwwPDA8MDyUPDA8MDg0ODQ4NDgwPDA8MDg0OJg4mDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBAkDwwPDA8lDyUPAAj+YzYPJg4mDgwPDA8MDiYPDA4NDiYOJg4NDiYODQ4MDyUPJQ8MDyUPJg4MDwwPJQ8MDwwPJQ8MDg0ODQ4NDwwODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDg0OJg4NDg0OJg4MDwwPJQ8lDwwPDA8MDg0ODQ4NDwsPJg4mDg0ODA8MDwwPDA8MDiYODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDgoRDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8lDwwODQ4mDg0ODQ4MDyUPJQ8ADQU=", + "26":"JgDMAV82DyUPJQ8MDwwPDA4mDg0ODQ4mDiYOChEmDg0ODQ4mDiYODQ4mDiUPDA8MDyUPDA8MDg0OJg4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDwwODQ4MDyUPJg4MDwwPDA8MDg0ODQ4NDiYODQ4MDwwPDA8MDg0ODQ4NDgwPJQ8lDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwoRDA8MDwwODQ4NDgwPDA8KEQwODQ4NDg0ODA8MDwwPDA8lDwwPDA4mDiYPAAj+YzYPJQ8lDwwPDA4NDiYOCBMMDyUPJg4MDyUPDA8MDyUPJQ8MDyUPJQ8MDwwPJQ8MDg0OJg4NDgwPDA8MDwwPCRENDg0ODQ4NDgwPDA8MDwwODQ4NDg0OJg4MDwwPJQ8MDwwPJQ8lDwwPDA8MDg0ODQ4MDyUPDA8lEAsPDA4NDg0ODQ4NDiYODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ8MDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4mDiYOJg4NDgwPDA8MDyUPJQ8ADQU=", + "27":"JgDMAV82DiYPJQ8MDwwODQ4mDg0ODQ4mDyUODA8mDgwPDA8lDyUPDA8lDyUPDBALDyUQCw8MDg0OJg4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPChEMDyUPJQ8MDwwPDA4NDg0ODQ4MECQPDA8MDwwPCRIMDg0ODQ4NDgwPJQ8lDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwkSDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDgwQCw8MDwwPDA4mDg0ODQ4mDiYOAAj/YzYOJg4mDwwODQ4NDiYODA8MDyUPJQ8MDyUPDA8MDyUPJQ8MDyUPJQ8KEQwOJg4NDg0OJg4NDgwPDA8KEQwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPJQ8MDwoRJQ8MDwwPJQ8lDwkRDQ4NDgwPDA8MDwwPDA8lDwwODQ4NDgwPDA8MDyUPDA8MDwwODQ4NDg0OChEMDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8lDwwPJQ8MDwwPDA4NDiYOJg4ADQU=", + "28":"JgDMAV82DiYPJQ8MDwwODQ4mDg0ODBAkDyUQCw8lDwwPDA8lDyUPDA4mDiYODQ4NDiYODQ8MDgwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDiYOJg8MDg0ODQ4NDgwPDA8MDiYPDA4NDg0ODQ4MDwwPDA8MDg0OJg4mDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwQCw8MDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4KEQ0ODA8lDwwPDA8lECQQAAj9YzYPJQ8mDgwPDA8MDyUPDA8MDiYOJg4NDiYODQ4NDiYOJg8IEiUPJQ8MEAsPJQ8MDwwPJQ8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDBALDwwOJg8MDwwPJQ4NDgwPJQ8mDg0ODA8MDwwPDA4NDiYOJg8MDg0ODQ4MDwwPDA8MDiYODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsQCw8MDwwODQ4NDgoSCw8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDiYPDQ0NDg0ODQ4NDiUPJQ8ADQU=", + "29":"JgDMAV82DyUOJg4NDg0ODQ4mDwwODA8lECQPDA8lDwwQCxAkDyUPDA4mDyUPCRENDiYODQ8MDg0PJQ4NDgwPChEMDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDiYOJg8MDwwODQ8MDg0ODQ4MDyUPDA8MDwwODQ8MDg0ODA8MDwwOJg4mDwwODQ4NDg0ODA8MDwwPDA4NDwwPDA4NDgwPDA8KEQwODQ4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODQ4NDwsPDA8MDwwPDA4mDwwODQ4mDiYOAAj+ZDYOJg4mDg0ODQ4MECQPDA8MDyUPJQ8MDyUPDA8MDyUPJQ8MDyUPJQ8MDg0OJg4NDg0OJg4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDwwODQ4NDgwPJQ8MDwwPJRALDwwOJg8lDwwPDA4NDgwPDA8MDwwPJQ8MDwwODQ8MDgwPDA8MDyUPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwoRDA8MDg0ODQ4MDwwQCw8MDg0ODQ8MDwwODA8MEAsPDA4NDg0ODQ4NDwsQCxAlDwsPDA4NDg0ODQ4MDyUQJA8ADQU=", + "30":"JgDMAWA2DiYOJg4MDwoRDA8mDgwPDA4mDiYODQ4mDg0ODQ4mDiYODQ4lDyYOChEMDyYODA8MDxMIJQ8MDwwODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwoRDA4NDg0ODQ4NDiYOJg4NDgwPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDg0ODQ4NDg0OJg4mDgoSDA4MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8lDwwPDA4mDyUPAAj+YzYPJQ8lDwwPDA8MDiYPDA4NDiYOJg4NDiYODQ4NDiUPJQ8MDyUPJRALDwwPJQ8MDwwPJQ4NDg0ODQ4NDwsPDA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0OJg4NDg0OJQ8MDwwPJRAkDwwPDA8MDwwOChENDiYODQ4NDgwPDA8MDwwPDA4NDiYODA8MDwwPDA8MDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwQCw8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0OChEmDiYOJg4mDyUPJQ8lDwwOJg8ADQU=", + "31":"JgDMAV82DyUPJQ8MDwwPDA4mDg0ODQ4mDiYODQ4mDwwODQ4lDyUPDBAkDyUPDA8MDyUPDA8KEA0OJg4NDg0ODQ8LDwwPDA8MDgoRDQ4KEgsPDA8MDg0ODQ4NDg0ODQ4MDyUPJQ8MDwwPDA8MDg0ODQ4NDiUPDA8MDwwPCRIMDg0ODQ8MDgwPJQ8lDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDg0OChEMDwwPDA8MDg0ODQ4NDgwPDBALDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4mDwwPDA4mDiYOAAj+YzYPJg4mDg0ODBALDyUPDA8MDiYPJQ8MDiYPDA4NDiYOJg4NDiYOJg4NDgwPJQ8MDwwPJQ8MEAsODQ4NDg0ODA8MDwwPDA8MDg0PDA4NDg0ODA8MEAsPJQ8MDwkRJg4NDg0OJg4mDgwPDA8MDwwODQ4NDg0ODQ8LDwwPDA8MDwwODQ4NDiYODQ4MDwgUCw8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDBALDwwPDA4mDg0OJg8lDiYOJg4mDg0OJg4ADQU=" + }, + "low":{ + "16":"JgDMAV42DyUPJQ8MDwwPDA8lDwwODQ4mDiYODQ4mDg0ODQ4mDiYODA8lDyUPDA8MDyUPDA8MDwwOJg4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPChEMEAsPDA4NDg0ODQ4MDwwPJQ8MDwwODQ4NDg0ODQ4MDyUQCw8MDwwPDA4NDg0ODQ4MDwwPJQ8lDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0PCw8MDwwPDA8MDwkRDQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ8lDg0OJg4NDiYOAAj/YzYOJg4mDg0ODQ4NDiUPDA8MDyUPJQ8MDyUPDA8MDyUPJQ8MDyUPJQ8MDwwOJg4NDwwOJg4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPJQ8MDwwPJQ8MDg0OJg4mDwwOChENDg0ODA8MDyUPJQ8lDyYODA8MDwwPDA4NDiYODQ4NDwsPChEMDwwPDA4NDg0ODQ4NDgwQCw8JEgwODQ4NDgwPDA8MDwwODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4mDiYODQ4mDyUPDA4NDiYOJg4ADQU=", + "17":"JgDMAV82DyUPJQ8MDwwPDA4mDg0ODQ4mDiYODQ4mDg0ODA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwoRJQ8MDwwPDA4NDg0ODQ4MDyYPCw8MDwwPDA8MDg0ODQ4NDgwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwQCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4KEQsQDA8MDwwPDA8lDg0OJg4NDiYOAAj/YzYOJg4mDg0ODQ4KESYODA8MDyUPJQ8MDyUPDA8MDyUPJQ8MDyUPJQ8MDg0OJg4NDg0OJg4NDgwPDA8MDwwPDA4KEQ0ODQ8LDwwPDA8MDg0ODQ4NDg0OJQ8MDwwPJQ8MDwwPJQ8lDwwPDA4NDg0ODQ4NDgwPJQ8lDyUPDA8KEQwPDA4KESYPDA4NDgwQCw8MDwwPDA4NDwwODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPChEMDwwODQ4mDg0ODQ4lDyYODQ4MDyYOJg4ADQU=", + "18":"JgDMAV82DiYOJg4KEQ0ODQ8lDwsPDA8lDyUPDA8lDwwPDA4mDiYPDA4mDiYODQ4NDiYODQ4MDwwPJQ8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDgoRJg4NDg0ODQ4MDwwPDA8MDiYODQ4NDg0ODA8MDwwPDA8MDg0OJg8lDg0ODQ4MDwwPDA8MDwkRDQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8lDwwPJQ8MDyUPAAj+YzYPJQ8lDwwPDA4NDyUODQ4NDiYOJg4NDiYODQ4KESYQJA4MDyUPJQ8MDwwPJQ8MDg0OJg4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0OJg4NDg0OJg4MDwwPJRAkDwwPDBALDwwODQ8MDiYODQ4lDyUPDA8MDwwODQ4NDiYODQ4NDg0ODQ4MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ8MDg0ODQ4MDwwPDA8tBiYOJg8MDiYPDA4NDiYOJg4ADQU=", + "19":"JgDMAV82DiYOJg4NDgwPDA8lDwwPDA8lDyUPDA4mDwwODQ8lDyUODQ4mDiYODQ4NDiYODA8MDwoRJQ8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwPDA4NDg0OJg4MDwwPDA8MDg0ODQ4NDiUPDA8MDwwPDA8MDwwODQ4NDgwPJQ8lDwwQCREMDg0ODQ4NDwsPDA8MDwwPDA8MDg0PDA4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDg0OJg4NDiYOAAj+ZDYOJg4mDg0ODQ4MDyYPCw8MDyUPJQ8MDyUPDA8MDyUPJQ8MDiYOJg8MDg0OJg4NDg0OJg4NDgwPDA8MDwwODQ4NDg0ODQ8LEAsPDA8MDg0OChEMDwwPJQ8MDwwPJQ8MDg0OJg4mDg0ODQ4NDgwPDA8MDwwPDA4mDiYODQ4NDg0ODQ4MDyUPDBALDwwODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8lDwwPJQ8MDyUPDA4KESYOJg4ADQU=", + "20":"JgDMAV82DiYOJg4NDg0ODQ4mDgwPChElDyUPDA8lDwwPDA8lDyUPDA8lDyUPDA8MDiYODQ4NDg0OJg4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0ODA8MDwwPDA8MDyUPDA4NDg0ODQ4NDgwPDA8MDwwOJg8lDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MEAsPDA4NDg0OChEMDwwPDA8MDwwODQ4NDwsPDBALDwwOChENDg0ODQ4mDg0OJQ8MDyUPAAj+YzYPJRAkDwwPDA8MDiYODQ4NDiYOJg4NDiYODQ4NDiYOJg4NDiYPJQ4NDgwPJQ8MDwwPJQ8MDwwPDA4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4KEQ0OJg4NDg0OJg4NDgwPJQ8lEAsPDA8MDg0PDgwNDiYOJg4NDiYPDA4MDwwPDA8MDiYPCREKEQ0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4KEQ0ODA8MDwwPDA4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA4NDiYODQ4NDiUPJg4ADQU=", + "21":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA8lDwwODQ4mDiYOChEmDiYODQ4MDyUPDA8MDwwPJQ8MDgoRDQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwoRDA4NDg0OJg4MDwwPDA8MDg0ODQ4MDyYODA8MDwwPDA8MDwwODQ4NDg0OJg4mDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4KEQwPDA8MDwwODQ4NDg0ODA8MDwwPDA8lDwwPJQ8MDiYPAAj+YzYPJQ8lDwwPDA4NDiYODQ4MDyUPJQ8NDiUPDA8MDyUPJQ8MDyUPJQ8MDwwPJQ4NDg0OJg4NDg0ODQ4MDwwPDA8MDwkRDQ4NDgwPDA8MDwwODQ4NDgwPJQ8MDwwPJQ8MDwwPJQ8lDwwODQ4NDg0ODQ4MDwoRJQ8MDyUQCw8MDwwODQ4NDiYODQ4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPChEMDwwPCRENDg0ODQ4MDwwPDA8MDwwPDA4mDg0ODQ4MDyUPDA8MDyUPJQ8ADQU=", + "22":"JgDMAWA1DyUPJQ8MDwkSDA4mDg0ODQ4mDiYOChEmDg0ODA8lECQPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDg0ODA8MEAsPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgoRDQ4MDwwPJQ8MDg0ODQ4NDg0ODA8MDyUPDA8MDwwODQ4NDg0ODA8MDwwPJQ8lDwwPDA4NDg0ODQ4KEQwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4KEQwPChEMDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4mDg0OJg4NDiYOAAj+ZDYOJg4mDg0ODQ4MDyUPDA8MDyUPJQ8MDyUPDA8MDiYPJQ4NDiYOJg4NDg0OJg4NDgwPJQ8KEQwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPJQ8MDwwPJQ8MDg0OJg4mDg0ODQ4MDwwPDA8MDiYODQ4NDiYODQ4NDgoRDA8MDyUQCw8JEQ0ODQ4NDg0ODA8KEQwPDA4NDg0ODQ4NDg0ODQ4MDwwODQ8JEQwPDA8MDwwOChENDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8lDyUPJQ8lDwgTDA8MDyUOJg8ADQU=", + "23":"JgDMAV82DyUPJRALDwwPDA8lDwwPDA4mDiYPDA8lDg0ODQ4mDiYODQ4mDiYODA8MDyUPDA8MDwwOJg4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPJQ8MDwkSDA4NDg0ODQ4NDiUPDA8IEwwODQ4NDgoRDA8MDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDgoRDQ4MDwwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8JEQ0ODQ4mDwwOJQ8KESUPAAj+YzYPJQ8lDwwPDA8MDyUODQ4NDiYOJg4NDiYPDA4MDyUPJQ8MDyYOJRALDwwPJQ8MDwwPJQ8MDwwODQ4NDgoRDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0OJg8MDgwPJQ8MDwkSJQ8lDwwPDA8MDwwODQ4NDgwPDA8MDyUPDA4NDg0ODQ4NDiYODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwoRDA4KEQ0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODA8lDwwPJQ8lDwwPDA8MDiYPJQ8ADQU=", + "24":"JgDMAV82ECQPJQ8MDwwPDA4mDg0ODA8mDiYODQ4mDgwPDA8lDyUPDA8lDyUPDA8MDiYODQ4NDg0OJg4MDwwPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0OChEMDwwPDA8MDwwPJQ8MDg0ODQ4MDwwPDA8MDyUODQ4NDg0ODQ4MDwwPDA8MDg0OJg4mDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0OChEMDwwPDA8MDwwODQ4NDgwPDA8lDwwPJQ8MDyUPAAj+YzYPJQ8lDwwPDA4NDiYODQ4MDyUPJQ8MDyUQCw8MDiYOJg4NDiYOJg4NDg0OJg4NDg0OJg4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPJQ8MDwwPJQ8MDg0OJg8lDg0PCw8MDwoRDA8MDiYPJQ8lDg0OChENDg0ODA8MDyUPDA8MDwwODQ4NDgoRDA8MDwwPDA4NDg0ODQ4KEQwPDA8MDwwODQ4NDgwPDA8IEwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8lDyUPDA8lEAsPDA8JESYOJg4ADQU=", + "25":"JgDMAV82DyUPJQ8MDwwPDA8lDwwODQ4mDiYODQ4mDg0ODA8lDyUPDA8lDyUPDA8MDyUPDA8MDwwPJQ8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPJQ8MDwwPDA8MDg0ODQ4MDyUPDA8MDwwODQ4NDgoRDQ4MDwwPJQ8lDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4IEw0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwPCREKEQ0ODQ4MDwwPDA8MDg0ODQ4MDwwPChEMDwwPDA4mDwwOJg4NDiYOAAkAYjYPJQ8lDwwODQ4NDiYODQ4MDyUPJg8LDyUPDA8MDyUPJQ8MDyUOJg4NDg0OJg4NDg0OJQ8MDwwPDA8MDwwODQ4NDg0ODA8MDwwPCBMMDg0ODQ4NDgwPJQ8MDwwPJQ8MDwwOJg8lDwwPDA4NDg0ODQ4MDwwPJQ8lDwwPDA4NDg0ODQ4NDiUQCw8MDwwPDA4NDg0ODA8MDwwPDA4NDggTDQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPDA8lDwwPDA4NDiYOJg4ADQU=", + "26":"JgDMAV82DyUPJQ8MDg0ODQ4mDg0ODQ4mDiYODQ4mDgwPDA8lDyUPDA8lDyUPDA8MDiYPDA4NDg0OJg4NDgwPDA8MDwwODQ4NDg0ODQ4NDgwPDA8MDwwODQ4NDwsPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODQ4MDwoRDA8MDwoRJQ8lDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUPAAj+ZDYOJg4mDg0ODQ4NDiUPDA8MDyUPJQ8MDyUPDA8MDyUOJg8MDiYOJg8KEA0OJg4NDg0OJg4MDwwPDA8MDwwODQ4NDg0ODA8NDgwPDA8MDwwODQ4NDgwPJQ8MDwwPJQ8MDgoRJg4mDg0ODQ4NDgwPDQ4MDyUPDA8lDwwPDA8MDg0ODQ4KESYODA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8JEQoRDQ4NDg0ODA8MDwwPDA8MDg0ODQ4mDiYOJg4NDgwPDA8MDyUPJQ8ADQU=", + "27":"JgDMAV82DiYOJg4NDg0ODA8mDgwPDA8lDyUPDA8lDwwPDA8lDiYODQ4mDiYPDA4NDiYODQ4MDwwPJQ8MDwwPDA4NDg0ODBALDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4KEQ0OJg4NDgwPDA8MDwwPDA4NDiYODQ4NDgwPDA8MDwwPDA4NDg0OJg4mDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8lEAsPJQ8MDyUPAAj+YzYPJQ8lDwwODQ4NDiYODQ4MDyUQJA8MECQPDA8MDyUPJQ8MDyUPJQ8MDwwOJg4NDg0OJg4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0OJQ8MDwwPJQ8MDwwPJQ8lDwwODQ4NDg0ODQ4MDwwPDA8lDwwODQ4NDg0ODQ4MDyUPDA8MDwwPDA8MDg0ODA8MDwwPDA4NDgoRDQ4MDwwPDA8MDwwPDA4NDg0PCw8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPDA8MDiYPJQ8ADQU=", + "28":"JgDMAV82DiYOJg4MDwwPDA8lDwwPDA8lDyUPDA4mDg0ODQ4mDiYODQ4mDiYODQ4MDyUPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MEAsPDA8MDg0ODQ4NDwwOJg4NDgwPDA8MDwwODQ4NDyUPCw8MDwwPDA8MDg0ODQ4NDg0OJQ8lDwwPDA8MEAsODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODA8MDwwPDA4NDgoRDQ4MDwwPDA8lDwwOJg4NDiYOAAj/YzYPJQ4mDg0ODQ4NDiYPCREMDyUPJg4MDyUPDA8MDyUPJQ8MDyUPJQ8MDwwOJg4NDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA8MDg0OJQ8MDwwPJRALDwwPJQ8lDwwPDA8MDg0ODQ4MDyUPJQ8MDwwPDA8MDg0PDA4NDiUQCw8MDwwPDA4NDg0ODQ4NDgoRDA8MDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ8MDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4mDiYOChENDgwQCw8MDyUPJQ8ADQU=", + "29":"JgDMAV82DiYOJg4MDwwPDA8lDwwPDA4mDiYPDA4mDg0ODQ4mDiYODQ4mDiYODQ4NDiYOChEMDwwPJQ8MDg0ODQ4NDg0ODA8MDwwPCRIMDg0ODQ4NDg0ODA8MDwwODQ4NDg0OJg4KEQwPDA8MDwwPDA4NDiYODQ8MDgwPDA8MDwwODQ4NDg0OJg4mDg0ODA8MEAsPDA8MDg0ODQ4KEQwPDA8MDwwPDA4NDgwPDA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8lDwwPJQ8MDyUPAAj+YzYPJQ8lDwwPDA8MDyUPDA4NDiYPJQ4NDiYPDA4MDyUPJQ8MDyUPJQ8MEAsPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwPDBALDg0ODQ4MDwwPDA8MDwwOJg4NDg0PJQ4NDgwPJQ8lDwwPDA8MDwwPDA8MDg0OJg8LDwwQCw8MDwwODQ4KESYODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ8MDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4mDg0ODQ4NDgwPDA8MECQPJQ8ADQU=", + "30":"JgDMAWA1DyYOJg4MDwwPDA8lDwwODQ4mDiYODQ4mDg0ODA8lDyYODA8lECQQCw8MDyUPDA8MDg0OJg4NDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPJQ8MDwwPDA8JEQ0ODQ4MDyUPDA8MDwwPDA8MDg0ODQ4NDgwPJRAkDwwPDA4NDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MEAsPDA4mDg0OJg4NDiYOAAj/YzYOJg4mDg0ODQ4MDyUPDA8MDyUPJQ8MDiYPDA4NDiYOJg4NDiYPJQ4NDg0OJg4NDgwQJA8MDwwPDA8MDg0PCw8MDwwPDA8MDwwPDA4NDg0ODA8MDwwPJQ8MDg0OJg8MDg0OJg4mDgwPDA8MDwwPDA8MDiYODQ4NDgwPDA8MEAsPDA4NDicNDQ4MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDwwOChILDwoRDA4mDyUOJg8lDyUOJg8lDwwPJQ8ADQU=", + "31":"JgDMAV81DyUPJQ8MDwwPDA4mDwwODQ4mDiYODQ4mDgwPDBAkDyUPDA8lDyUQCw8MDiYODQ4NDg0OJg4MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4MDwwPJQ8MDwwPDA8MDg0ODQ4NDiUPDA8MDwwODQ4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDgkSDA8MDwwPDA8MDg0ODQ4MDwoRDA8lDwwOJg8MDiYOAAj+YzYPJQ8lDwwODQ4NDiUPDA8MDyUPJQ8MDyUPDA4NDiYOJg8MDiYOJg4NDg0OJQ8MEAsPJQ8MDwkSDA4NDg0ODQ8LDwwPDA8MDg0OChEMDwwPCRIMDg0OJg4NDgwPJRALDwwPJQ8lEAkSCw4NDg0ODA8MDwgTDA4NDgoRDQ4MDwwPDA8MDiYODQ8MDg0ODA8MDwwPDA4NDg0PCw8MDwoRDA8MDggTDQ4NDg0ODA8MDwwOCRINDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4lDwwPJQ8lDyUPJg8lDg0OJQ8ADQU=" + }, + "mid":{ + "16":"JgDMAV82DiYOJg4NDgwPDA8lEAsPDA8lDyUPDA8lDwwODQ4mDyUPDA4mDiYPDA4NDiYODQ4MDwwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0OJg4NDgwPDA8MDwwPDA8MDg0PJQ4NDgwPDA8MDwwODQ4NDg0OJg4mDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8MDg0PDA8MDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDwwODA8lDwwPJQ8lDyUPAAj+ZDUPJQ8lDwwPDA8MDyUODQ4NDyUPJQ4NDyUODQ8MDiYOJg4NDiUPJQ8MDwwPJQ8MDwwPJQ8MDwwODQ4NDg0ODA8MDwwPDA4NDg0OChEMDwwPDA8MDg0OJg4NDg0OJg4NDgwPJQ8lDwwQCw8MDg0ODQ4NDiUPJg4mDiYODQ4MDwwPDA8lDyUPDA8MDg0ODQ4NDgwPDA8MEAsPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4lDwwPJQ8lDyUPDBALDyUPJQ8ADQU=", + "17":"JgDMAV82DiYOJg4NDg0PCw8lDwwPDA8lDyUPDA4mDwwODQ8lDyUODQ8lDyUODQ4NDiYPCxALDwwPJQ8MDwwODQ8MDg0ODQ4MDwwPDA8MDg0ODQ4NDgwQCw8MDwwODQ4NDg0OJQ8MDwwPDA8MDwoRDA4NDg0PJBALDwwQCw8NDQ0ODQ4NDwsQJBAlDwsPDA8MEAsPDA4NDg0ODQ4MDwwQCxALDg0ODQ4NDgoRDA8MDwwPDA4NDg0OChENDwsPDA8MDg0ODQ4NDg0ODBALEAsPDA8MDg0ODQ4NDgwQCxAkDwwPJQ8lDyUOAAj/YzYPJQ8lDwwPDA8MDiYPDA8MDyUOJQ8MDyYODA8MDyUQJA8MDyUQJA8MDwwOJg4NDg0OJg8MDwwODBALDw0ODA8NDQ0PDA4NDgwPDA8MDwwODQ4NDg0OJg8LDwwPJQ8MDwwPJQ8lDwwPDA4NDg0ODA8MDwwPJQ8lDyUPDA8MDg0ODQ4mDyUODQ4NDgwPDBALDwwPDA4KEQ0PDA4NDgwPDA8MDwwODQ4NDg0ODA8KEQwPDA4NDg0ODQ8LDwoSCw8MDwwODQ4KEQwPDQ4MDwwPDA8lDyUPDA4mDiYODQ4NDyUOJg4ADQU=", + "18":"JgDMAV82DyUPJQ8MDwwPDA8lDwwPDA4mDiYODQ4mDwwODQ4mDyUODQ4mDiYPDA4NDiUPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDwwPDA4NDwwODQ4MEAsPDA8MDg0ODQ8LDwwPJQ8MDwwPDA8MDgoSDA4MDwwPJQ8MEAsPDA4NDg0ODQ4NDgwPJQ8lDwwPDA8MDwwODQ4NDg0ODA8KEQwPDA4NDg0ODA8MDw0ODA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8MDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4mDg0OJg8lDiYOAAj+ZDYOJg8lDwwODQ8MDiYPDA4NDiYOJQ8MDyUPDA8MDyUPJQ8MDyUPJQ8MDg0OJg4NDg0PJQ8MDg0ODA8MDwwPDA8MDg0OChENDgwPDA8MDwwPDA4NDg0OJg4MDwwPJQ8MDwwOJg4mDg0ODQ4NDgwPDBALDyUQCw8lDyUPChEMDg0ODQ8lDiYODQ4NDgwPDA8MDwwPDA4NDgoRDA8MDwwQCw4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDwsPDA8MDwwODQ8MDg0ODA8MDwwPDA8lDg0ODQ4mDiYODQ4MDyUPJg4ADQU=", + "19":"JgDMAV42ECQPJQ8MDwwPDA8lDg0ODQ4mDiYPDA4mDwwODQ4mDiYODBAkECQPDA8MDyUQCw4NDg0OJg8MDg0ODA8MDwwPDA8MDwwOChENDgwPDQ4MDwwODQ4NDwwODQ4NDgwPJQ8MDwwPDA4NDg0ODQ4MDwwPJQ8MDwwODQ4NDg0ODA8MDwwPJQ8lDwwODQ4NDg0ODQ4MDwoRDBALDwwODQ8MDgwQCw8MDwwPDA4NDg0ODQ8LDwwPChEMDg0ODQ4NDg0ODA8MDwwODQ4NDg0PDA4MDwwPDA8MDwwODQ4mDg0OJg8lDyUOAAj/YzYPJQ8lDwwODQ4NDiYODQ4NDiUPJQ8MDyUPDBALDyUPJQ8MDyUPJQ8MDg0OJg8MDg0PJQ8MDgwPDA8MDwwODQ4NDg0ODA8MEAsPDA8MDg0PDQ0NDg0OJg8MDgwQJA8MDwwPJQ8lDwwODQ4NDg0ODQ4MDwwQCw8lDyUQCw8MDwwODQ8lDiYODQ4NDgwPDA8MDg0PDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4NDgwPDA8MDwwPDA4mDiYOJg4NDyUODQ4NDiYPJQ8ADQU=", + "20":"JgDMAV42DyUPJRALDwwPChElDwwPDA8lDyUPDA4mDg0ODQ4mDyUPDA4mDyUODQ4MDyUPDA8MDwwOJg8MDg0ODQ4NDwwODA8MDwwODQ4NDwwODA8MDwwPDA8MDg0ODQ4NDgwQJA8MDwwPDA4NDg0ODQ4MDwwPJQ8MDwwPDA4KEgwPDA8LEAsPJQ8lEAsPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ8LDwwPDA8MDg0ODQ8MDgwPDA8MDwwPDA8lDg0OJg4mDiYPAAj+YzYPJRAkDwwPDA8MDiYPDA4NDyUOJg4NDiYODQ4MDyUPJQ8MDyUPJQ8MDwwPJQ8MDg0OJg4NDg0ODQ4MDwwPDA8JEQ0PDA4NDwsPDA8MDwwPDA8MDg0OJg4NDg0OJQ8MDwwPJQ8lDwwPDA8MDwwODQ4NDiUPJg4MDyUQCw8MDwwPDA8lDyUPDA8MDg0OChEMDwwPDA8MDwwODQ4NDwwODA8MDwwPDA4NDg0ODQ8MDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ8LDwwPDA8lDwwPJQ8MDyUPDA8MDiYOJg4ADQU=", + "21":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDyUPDA8lDg0ODA8lDyUQCw8lECQPDA8MDyUPDA8MDg0OJg4NDg0ODA8MDwwPDA4NDwwODQ4NDgwPDA8MDwwODQ4NDwsQDA4MDwwPJQ8MDwwPDA4NDg0PDA4MEAsPJQ8MDwwODQ8MDwwODA8MDwwPJQ8lDwwPDA4NDg0PDA4NDgwPDA8MDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwkRDQ8MDgoRDA8MDwwODQ4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDg0OJg8lDyUOAAj/YzYOJg4mDg0ODQ4NDiYODQ4MDyUPJQ8MDyUPDA8MDyUPJQ8MDyUQJA8MEAsPJQ8MDg0PJQ8MDgwPDA8KEQwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0PJQ4MDwwPJQ8MDwwPJQ8lDwwPDA4NDg0PDA4MDwwQJA8MDiYPDA4NDg0ODQ4mDiYODA8KEQwQCw8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4mDiYODQ4NDiYODQ4MDyUQJA8ADQU=", + "22":"JgDMAV80ESMRIxEJEgkSCRIjEQkRChEkECQQChEkEAoRChEjESMRCRIjESMRCRIJEiMRCREKEQoRIxEKEQkSCRIJEgkSCREKEQoRChEJEgkSCRIJEQoRChEJEgkSCRIJEQoRIxEKEQoRCRIJEgkSCRIJEQoRIxEKEQkSCRIJEQoRChEKEQkSIxEjEQoRCRIJEgkRChEKEQkSCRIJEgkSCREKEQoRChEJEgkSCRIJEQoRChEJEgkSCRIJEgkRChEKEQkSCRIJEgkSCREKEQoRCRIJEgkSCREKEQoRChEjEQkSIxEjESMRAAj7ZjMRIxEkEAkSCRIJEiMRCREKESQQJBAKESMRChEJEiMRIxEJEiMRIxEJEgkSIxEJEQoRJBAKEQoRChEJEgkSCRIJEQoRChEJEgkSCRIJEQoRChEKEQkSIxEJEgkSIxEJEgkRJBAkEAoRChEJEgkSCRIJEiMQChEKESQQChEJEgkSCRIjESMRCRIJEQoRChEKEQkSCRIJEgkRChEKEQoRCRIJEgkSCREKEQoRCRIJEgkSCREKEQoRChEJEgkSCRIJEQoRChEKEQkSCRIJEgkSCREkEAoRChEKESQQCRIJEiMRIxEADQU=", + "23":"JgDMAV82DyUQJA8MDwwPDA4mDg0PDA8lDiYODQ4lEAsPDA8lDyUQCw8lDyYODA8MDyUPDA8MDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ8MDg0ODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPJQ8MDwwPDA4NDg0ODQ4MDwwPJQ8lDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODQ8LDwwPDA4NDg0ODQ4NDgwPDQ4MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ8JEQwPDA8MDwwPDA4mDwwOJg8lDiYOAAj/YzYPJQ8lDwoRDA8MDiYPDA4NDiYOJg8LDyUPDA8MDyUPJQ8MDyUQJA8MDwwOJg8MDg0PJQ4NDg0OChENDgwPDA8MDwwPDA4NDg0ODQ8LDwwQCw8MDg0OJg4NDgwPJQ8MEAsPJQ8lDwwPDA8MDg0ODQ4NDwwODBALDyUPDA8MDwwODQ8lDiYODQ4MDwwPDA8MDwkRDQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ8MDgwPDA8MDwwPDA4mDiYOJg4mDg0ODQ4NDiYPJQ4ADQU=", + "24":"JgDMAV42ECQPJQ8MDwwPDA8lDwwPDA4mDiYPDA4mDg0ODQ4mDiYODQ4mDyUODQ4MDyUQCw8MDwwPJRALDwwPDA4NDwkRDA8MEAsPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPJQ8MDwwPDA8MDwwPDA4NDg0OJg4MEAsQCw8MDg0ODQ8MDg0OJg4mDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDwwODBALDwwPDA8MDg0ODQ4MDwwQCw8NDQ0ODQ4NDgwPDA8MDwwPDA4mDwwPJQ4mDiYOAAj/YzYPJRAkDwwPDA8MDyUPDA4NDiYOJg8LDyUPDA8MDyUPJQ8MDyUQJA8MEAsQJBALDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0OJg4NDgwPJRALDwwQJA8lDwwPDA8MDwwODQ4NDyUPJQ4mDwwODQ4MDwwPDA4mDiYPDA4NDg0ODQ4MDwwPDA8MDwwPDA8MDg0PCw8MDwwPDA4NDg0ODQ4MDwwQCw8MDg0ODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDwwODA8lDwwPJQ8lEAsQCw8MDiYOJg4ADQU=", + "25":"JgDMAV82DyUPJg4NDgwQCw8lDwwPDA4mDyUODQ4mDg0ODQ4mDyUODQ4mDiYODA8MDyUPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDwwODQ4NDwwODQ4NDwsPDA8MDwwODQ4NDg0OJg4NDgwPDA8MDg0ODQ4NDgwQJA8MDwwPDA8MDg0ODQ4NDgwPJQ8lEAsPChEMDwwPDA8MDwwODA8MEAsPDA8MDg0ODQ4NDg0ODA8KEQwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8lEAsPJQ8lDyUPAAj+YzYPJQ8lDwwPDA8MDyUPDA4NDiYOJg4NDiYPDA4NDiYPJQ4NDiYOJg8MDg0PJQ4NDgwPJQ8MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPJRALDwwPJQ8MDg0OJg4mDg0ODQ4MEAsPDA8MDg0OJg8lDg0ODQ4NDwwPCw8lDyUPDA8MDwwPDA4NDg0ODA8MDwoRDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4mDiYPCw8mDgwPDA8MDyUPJQ8ADQU=", + "26":"JgDMAV82DiYOJg4NDg0ODA8mDwsPDA8lDyUPDBAkDwwPDA8lDyUPDA4mDiYODQ4NDiYODQ4NDgwPJRALDwwPDA8MDwwODQ8MDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA4KEQ0OJg4NDg0ODA8MDwwPDA8MDg0PJQ4NDg0ODQ4MDwwPDA4NDg0OJg4mDwwODQ4MDwwQCw8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0ODA8MDg0ODQ4NDgwQCw8MDwwPDA4NDg0ODQ4NDgwQCw8MDwwODQ4NDg0ODQ4mDg0OJQ8lDyUPAAj+ZDYPJQ8lDg0ODQ4MDyUPDA8MDyUPJQ8MDyUQCw8MDyUOJg8MDiYOJg8MDg0OJg4NDwwPJA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwoRJRALDwwPJQ8MDg0OJg4mDwwPDA4NDgwPChEMDyUPDA8lDwwPDA4NDg0ODQ4mDyUODQ4MDwwPDA8MDg0ODQ4NDwsPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODBALDwwPDA8MDwkSDA4mDg0ODQ4mDgwPDA8MDyUPJQ8ADQU=", + "27":"JgDMAWA1DyUOJg4NDg0ODQ4mDg0ODA8lDyUPDA8lDwwPDA4mDiYPDA8lDiYODQ4NDiYPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0ODBALDwwPDA4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0PDA4NDgwPDA8MDwwPJQ8MDg0ODQ4MDw0ODA8MDwwOJg8lDwwODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwODQ4NDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDBALDwwPDA8MDwwODQ4mDg0OJg4mDiYOAAj/YzYOJg4mDg0ODQ4NDiYPDA4MDyUPJQ8KETAEFgUMDyUPJQ4NDiYOJg4NDg0OJg8MDg0OJg8LDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPJQ8MDwwPJRALDwwPJQ8lDwwPDA4NDg0PCw8MDwwPDA4mDwwODQ4NDg0ODA8lECQPDA8NDgwPDA8MDg0ODQ4NDgwQCw8MDwwODQ4NDwwODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0PCw8MDwwPDA4NDg0ODA8mDiYOJg4NDwwODBALDyUPJRAADQU=", + "28":"JgDMAV82DyUPJQ8MDg0ODQ4mDwwODQ8lDiYODQ4mDg0ODA8lECUODA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0PCw8KEQwPDA8MDg0PDA4NDgwPDA8MDwwODQ4NDg0PDA4MEAsPJQ8MDwwPDA4NDg0ODA8MDwwPJQ8MEAsPDA4NDg0ODQ8LEAsPJRAkDwwPDA8MDwwODQ4NDgwPDA8KEQwODQ4NDg0ODQ4MEAsPDA8MDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MEAsPDA8lDwwOJg4mDyUOAAj/YzYPJQ8lEAsPDBALDyUPDA4NDiYOJg4NDiYPDA4NDiUPJQ8MDyUPJg4MDwwPJQ8MDwwOJg4NDg0ODQ4NDg0ODA8MDwwPDQ0NDg0ODQ4MDwwPDA8MDg0OJg4NDg0OJQ8MDwwPJQ8lDwwQCw8MDwwPDA4KESYOJg4NDg0ODQ4MDwwQCRElDyUPDA8MDg0ODQ4MDwwPChEMDwwODQ4NDwsQCw8MDwwODQ4NDg0ODQ4NDgwQCw8MDwwPDA4NDg0PCw8MDwwPDA8MDg0ODQ4NDwsPDA8mDgwPJQ8MDg0PDA4NDiYOJg4ADQU=", + "29":"JgDMAV42DiYOJg4NDwsPDA8lDwwQCw8lDyUPDA8lDwwPDA4mDiYODQ4mDyUPDA4NDiYODQ4NDgwPJQ8MDwwPDA8MDwwPDA4NDwwODA8MDwwQCw8MDg0ODQ4NDg0ODBALDwwPJRALDwwODQ4NDgwQCw8MDwwOJg8MDwkRDQ4NDgwPDA8MDwwOJg8lDwwODQ4NDg0ODQ4KEQwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwQCw8MDg0ODQ8MDg0ODQ4MDwoRDA8MDg0ODQ4KEQ0PCw8MDwwPDA4mDg0OJg4mDiYOAAj/ZDYOJg4mDg0ODQ4NDiUPDA8MDyUPJQ8KESUQCw8MDiYOJg4NDiYOJg8MDg0OJg4NDg0OJRALDwwPDA8MDwwPDA4NDg0ODQ4MDwoRDA8MDg0ODQ4NDgwPJQ8MDwwPJQ8MDwwPJQ8lDwwPDA4NDg0ODQ4MEAkSJA8MDwwODQ4NDwwODA8lECQPDA8MEAsPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4mDyUPDA4NDg0ODQ4MDyUPJQ8ADQU=", + "30":"JgDMAV42DyYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDg0ODQ4mDiYODQ4mDiYODQ4NDiYODQ4MDwwPJQ8MDwwPDA4NDg0ODA8MDwwPDA8MDwkSDA4NDgwQCw8MDwwPDA8MDwwOJg4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0ODA8MDwwPDA4NDg0OJg4mDg0OChENDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0PCw8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4mDg0OJg4mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg4NDiYODQ8MDiYOJQ8MDyUPJQ8MDwwPJQ8MDwwPJQ8MDg0ODQ4NDgwPDA8MDwwPDA4NDgoRDQ8LDwwPDA8MDwwPJQ8MDg0OJg4NDgwPJQ8lDwwPDA8MDwwPCRIMDiYPDA4NDg0ODA8MDwwPDA4mDyUPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4MDwwPDA8MDwwODQ4NDg0ODA8lDwwPDA8MDg0ODQ4NDiYPJQ4ADQU=", + "31":"JgDMAWE2DiYOJg8MDg0ODQ4mDgwPDA8lECQPDA8lDwwPDA4mDiYPDA4mDyUPDA4NDiYODQ4NDg0OJg4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ8MDg0ODQ4MDwwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8KEQwPJQ8MDg0ODQ4NDgwPDA8MDg0OJg4mDwwODQ4NDg0OChEMDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDwwODQ4MDwwPDA4NDg0ODQ4MDwwQCw8MDg0ODQ4NDg0ODBALDwwPDA8MDg0ODQ8lEQoOJg4mDiYOAAj/YzYQJQ4lDwwODQ8MDiYODQ4NDiYOJg8LDyYODQ4MDyUQJA8MDyUPJQ8MDwwPJQ8MDg0OJg8MDg0ODQ4NDgwPDBALDwwPDA4NDg0ODA8MDwwPDA4NDg0OJg4NDg0OJg4NDgwPJQ8lDwwPDA8MDg0ODQ4NDgwPDBALDwwPDA8MDg0ODQ4mDiYODQ4NDgwPDA8MDwwODQ4NDg0PCw8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8lDiYPJQ8lDyUPJQ8lDwwPJQ8ADQU=" + }, + "high":{ + "16":"JgDMAV82DyUPJQ8MDg0ODQ4mDg0ODA8lDyUPDA8lDwwPDA8lDyUQCw8lDyUPDA8MDiYODQ4NDg0OJg4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgoRDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwODQ4NDg0ODA8MDwwPJQ8lDwwPDA8MDg0ODQ4KEQwQCw8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDwwODQ4NDgoRDA8MDwwPDA8MDwwODQ4mDg0OJg4NDgwPAAj+YzYPJRAkDwwPDA8MDyUPDA8MDyUPJQ8MDiYODQ4NDiYOJg4MDyUPJQ8MDwwPJQ8MDwwPJQ8MDg0ODQ4NDgwPDA8MEAsPDA4NDg0ODQ4NDgwPDA8MDwwOJg4NDg0OJg4MDwwPJQ8lDwwPDA8MDg0ODQ4NDiYOJg4mDiYODQ4NDgwPDA8lDwwPJQ8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDggTDQ4MDwwPDA8MDwwODQ4NDg0ODQ4lDwwPDA8MDwwOJg8MDiYOJg4ADQU=", + "17":"JgDMAV82DiYOJg4NDg0ODQ4lDwwPDA8lDyUPDA8lDwwODQ4mDiYPDA8lDiYODQ4NDiYODQ4NDgoRJQ8MDwwPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODBALDwwPDA8MDg0OJg4NDg0ODA8MDwwPDA4NDiYOJg4NDg0ODQ4MDwwPDA8MDwwOJg4mDg0ODQ4NDwsPDA8MDwwODQ8MDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4lDwwPJQ8MDwwPAAj+YzYPJQ8lDwwPDA8MDiYPDA4NDiYOJg4NDiYODQ4NDiUPJQ8MDyUPJQ8MDwwPJQ8MDwwOJg8MDg0ODQ4MDwoSCw8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0OJg4NDg0OJg4mDgwPDA8MDwwPDA4KEQ0OJg4mDiYODQ4MDwwPDA8lDwwPJQ8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8mDiYOJg4mDiYODQ4MDyUPJQ8ADQU=", + "18":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ8kDwwPDA8lECQPDA8lDyUPDA8MDiYOChENDg0OJg4NDgwPDA8MDwwPDA4NDg0PCw8MDwwPDA8MDwwODQ4NDgwPDA8MDwwOJg4NDg0ODQ4MDwoRDA8MDyUPJQ8MDwwODQ4NDgwPDQ4MDwwPJQ8lDwwODQ8JEQ0ODA8MDwwPDA8MDg0ODQ8MDgoRDA8KEQwPDA4NDg0ODA8MDwwPDA8MDgoRDQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4mDg0OJg4NDg0OAAj+ZDUPJQ8mDgwPDBALDyUPDA8MDiYOJg4NDiYODQ4NDiYOJg4NDiYPJQ4NDg0OJQ8MDwwPJQ8MDwwPDA4NDg0PCxALDwwPDA8MDwwODQ4MDwwPDA8KEQwPJQ8MDgoRJg4NDg0OJQ8lDwwPDA8MDwwPDA4NDiYODQ4mDiYODQ4NDgwQCw8lDwwPJQ8MDwwPDA4NDg0ODBALDwwPDA8MDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDwsPDA8MDwwPDA4NDwwODA8lDwwPJQ8mDiYODA8MDyUPJQ8ADQU=", + "19":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYODQ4KEQwPJQ8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0ODQ4MDwwPDA4NDiYOJg4NDg0ODQ4MDwwPChEMDwwOJg4mDg0PDA4NDgwPDA8MDwkRDQ4NDg0ODA8MDwwPDA8MDwwODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4mDg0OJQ8MDwwPAAj+ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYPDA4MDyUPJQ8MDyUPJQ8MDwwPJQ8MDwwOJg4NDg0ODQ8LDwwPDA8MDg0ODQ4NDwsPDA8MDwwPDA4NDg0OJg4NDgwPJQ8MDwwPJQ8lDwwPDA8MDwwODQ4NDg0ODA8lDyUPDA8LEAwODQ4mDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDg0PDA4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4mDiYPDA4mDiYODQ4NDiYOJg4ADQU=", + "20":"JgDMAV82DyUPJQ8MDwwPDA4mDg0ODQ8lDiYODA8lEAsPDA8lDyUPDA8lDyUPDA4NDiYODQ8MDg0OJg4NDgwPDA8MDwwPDA4KEgsPDA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwOJg4NDg0ODQ4MDwwPDA8MDyUPJQ8MDwwODQ4NDg0ODA8MDwwPJQ8lDwwPDA4NDg0ODQ4MDwwPChEMDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8JEQ0ODQ4NDg0ODA8MDwwPCRIMDg0ODQ4mDgoRJQ8MDwwPAAj+YzYPJQ8mDgwPDA8MDyUPDA8MDiYOJg4NDiYODQ4NDiYOJg4NDiUPJg4MDwwPJQ8MDwwPJQ8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0OJg4NDwwOJg4NDgwPJQ8lDwwPDA8MDwwPDA4NDiYOJg4NDiYODQ4MDwwPDA8lDwwPJQ8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDwwPDA8LDwwPDA8MDg0ODQ4NDgwPDA8lDwwPDBAkECQPChEMDigMLwYADQU=", + "21":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4lDwoRDA8lECQPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDg0ODA8MDwwPDA4NDgsQDQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPJQ8MDwwPDA4NDg0ODA8MDyUPJQ8MDwwOChENDg0PCw8MDwwPJQ8lDwwPDA4NDg0ODQ4MDwwPDA8JEgwODQ4NDgwPChEMDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwPDA4mDwwOJg4NDg0OAAj+ZDYOJg4mDg0ODQ4MDyUPDA8MDyUPJQ8MDyUPDA8MDiYOJg4NDiYOJg4NDg0OJg4NDgwPJQ8MEAsPDA8MDwwODQ4NDgwPChEMDwwODQ4NDg0ODA8MDwwPJQ8MDwwOJg8MDg0OJg4mDg0ODQ4MDwwPDA8MDwwOJg4NDiYODQ4KEQwPDA8lDwwPJQ8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ4MDwoRDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwODQ4NDg0ODA8lDyUPJg4MDyUPDA8MDyUPJQ8ADQU=", + "22":"JgDMAV42DiYOJg4NDg0ODA8lDwwQCw8lDyUPDA8lDwwPDA8lDyUPDA8lDyUPDA4NDiYODQ4NDgwPJQ8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwOJg4NDg0ODA8MDwwPDA8MDyUPJQ8MDg0ODQ4NDgwPDA8MDwwOJg4mDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDgoRDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4mDg0OJg4NDgwPAAj+YzYPJg4mDg0ODA8MDyUPDA8MDyUPJQ8MDiYODQ4NDiYOJg4NDiYOJg4NDg0OJg4NDgwQJA8MEAsPDA4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ4NDg0OJg4NDg0OJg4mDg0ODA8MDwwPDA8MDiYPDA4NDiYODQ4MDwwPDA8lDwwPJQ8MDwwODQ8MDg0OChEMDwwPDA4NDwwODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODBAkDwwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "23":"JgDMAV82DiYOJg4NDg0ODQ4lDwwPDA8lDyUPDA8lDwwPDA4mDyUPDA4mDiYODQ8MDiYPDA4NDgwPJQ8MDwwPDA4NDgoRDQ4MDwwPDA8MDwwPDA4NDg0ODQ4KEQwPDA8MDg0OJg4NDg0ODQ4MEAsPDA8MDiYOJg4NDg0ODQ4NDgwPDA8MDwwOJg4mDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPChEMDwwOChENDg0ODA8MDwwPDA4NDg0ODQ4MDwwPCRIMDg0PDA4NDgwPDA8MDwwODQ4NDg0ODBAkDwwPJQ8MDwwPAAj+YzYQJA8lDwwPDA8MDyUODQ4NDiYOJg4NDiYODQ4MDyUQJA8MDyUPJQ8MDwwPJQ8MDwwOJg8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0OJg4NDg0OJg4NDgwPJQ8lDwwQCw8MDwwODQ8MDg0ODQ4MDyUPDA8MDwwODQ4mDg0OJg4NDgwPDA8MDwwODQ4NDwwODA8MDwwPDA8MDwwODQ4NDwsPDA8MDwwPDA4NDgoRDQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4mDiYPDA4NDiYODQ4MDyUPJQ8ADQU=", + "24":"JgDMAV82DyUPJQ4NDg0ODQ4mDg0ODQ4lDyUPDA8lDwwPDA4mDyUPDA4mDyUPDA8MDyUODQ4NDgwPJg4MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg4NDg0ODQ4MDwwPDA8MDyUPJQ8MDwwOChENDgwPDA8MDwwPJQ8lDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0OChEMDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4mDg0OJg4NDg0OAAj/YzYOJg4mDg0ODQ4NDiYODBALDyUPJQ8MDyUPDA8MDyUOJg4NDiYOJg4NDg0OJg4NDg0OJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0PDA4NDgwPJQ8MDwwPJQ8MDg0OJg8lDg0ODQ4MDwwPDA8MDyUPJQ8lDwwPDA4NDg0ODQ4lDwwPJQ8MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDgoRDA8MDwwPDA4NDgoRDQ4MDwwPDA4NDgoRDQ4MDwwPDA8MDwwODQ4mDg0ODQ4MDyUPDA8MDyUPJQ8ADQU=", + "25":"JgDMAV82DiYOJg8MDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDiYODQ4NDiYODQ4NDg0OJg4NDgwPDA8MDg0ODQ4NDg0ODBALDwwPCRENDg0ODA8MDwwPDA8MDwwOJg4NDg0ODA8MDwwPDA8MDiYPJQ8MDwwODQ4NDgwPDA8MDwwPJQ4mDg0OChENDg0ODA8MDwwPDA8MDwwODQ4KEQ0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4mDg0OJg4NDgwPAAj+ZDYOJg4mDg0ODA8KESUPDA8MDyUPJQ8MDyUPCBMMDiYOJg4NDiYOJg4NDg0PJQ4MDwwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwQJA8MDwwPJQ8MDg0OJg4mDg0ODQ4MDwwPDA8MDg0OJg4mDg0ODQ4NDgwPDA8lDwwPJQ8MDwwODQ4NDg0ODQ4MDwwPDA8MDwwODQ8MDwsPDA8MDwwODQ4NDg0ODA8KEQwPDA8MDg0PDA4MDwwPDA8MDwwPDA4NDg0ODQ4mDiYOJg4mDg0ODA8KESUPJQ8ADQU=", + "26":"JgDMAV82DyUPJQ8MDwwPDA8lDwwPDA4mDyUPDA8lDwwODQ4mDiYODQ4mDiYODQ4NDiUPDA8MDwwPJQ8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDBALDwwODQ4NDg0OJQ8MDwwPDA8MDg0ODQ4NDiYOJg4NDg0ODA8MDwwPDA4NDg0OJg4mDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAj+YzYPJQ8lDwwPDA4NDiYODQ4NDiYOJg4NDiYODA8MDyUPJQ8MDyUPJQ8MDwwPJQ8MDg0OJg4NDg0ODA8MDwwPDA8MDg0ODQ4KEgsPDA8MDg0ODQ4NDg0PJQ4NDgwPJQ8MDwwPJQ8lDwwODQ8MDg0ODBALDyUPDA8lDwwPDA8MDg0ODQ4mDg0OJg4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4mDg0OJg4mDg0ODQ4MDyUPJg4ADQU=", + "27":"JgDMAV81DyYOJg8MDgwPDA8lDwwPDA8lDyUPDA4mDg0ODQ4mDiYODQ4mDiYODQ4NDiYODA8MDwwPJQ8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ8MDgwPJQ8MDwwPDA8MDwwODQ4NDiUPJQ8MDwwPDA4NDg0ODQ4NDgwPJQ8lDwwPDA8MDwwODQ8MDg0ODA8MEAsPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0OCRIMDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4mDwwPJQ4NDg0OAAj+ZDYOJg4mDwwODQ4MDyUPDA8MDyUPJRALDyUQCw8MDiYOJg8MDiYOJg4NDg0OJg4MDwwQJA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPJQ8MDg0OJg8MDg0OJg8lDg0ODA8MDwwPDA8KEQkRDQ4mDg0ODQ4MDwwPDA8lDwwPJQ8MDg0ODQ4MDwwPDA8MDwwODQ4NDwsPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8lDyUPDA8lDwwPDA4NDiYOJg4ADQU=", + "28":"JgDMAV42DyUPJQ8MDwwPDA4mDg0ODQ4mDyUOChEmDg0ODA8lECQPDA8lDyUQCw8MDiYPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4KEQwPDBALDwwOJg8MDg0ODQ4NDgwPDA8MDyUPJQ8KEQwODQ4NDg0ODA8MDwwPJQ8lDwwPDA4NDg0ODQ4MDwwPDA8MDgoRDQ4NDgwPDBALDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwODQ4mDg0OJg4NDg0OAAj+ZDYOJg4mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDgwPJQ8MDwwPJQ8MDg0ODQ4NDgwQCw8MEAsPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDg0OJg4mDgoRDA8MDwwPDA4NDiYPJQ4IEw0ODQ4MDwwPChElDwwPJQ8MDg0ODQ4NDgwPDBALDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8lDwwPDA8lDwwODQ4NDiYOJg4ADQU=", + "29":"JgDMAV82DyUPJQ8MDwwODQ4mDg0PDA4mDiYODQ4mDg0ODA8lDyUPDA8lDyYOChEMDyUPDA8MDg0OJg4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ8LEAsPJQ8MDwwPDA4NDg0ODA8MDyUPJQ8MDwwPDA8MDg0ODQ4NDgwPJQ8lEAsPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDg0ODQ4KEQwPDA8MDwwPDA4mDg0OJg4NDg0OAAkCYDYOJg4mDg0ODQ4MDyUPDA8MDyUPJQ8KESUPDA8MDiYOJg4NDiYPJQ8MDg0OJg4NDgwPJQ8MDwwPDA4NDg0PCw8MDwwPDA8MDwwODQ4NDgwPDA8MDwwPJQ8MDg0OJg4NDg0OJg4mDwwODA8MDwwPDA8MDg0OJg4NDg0ODA8MDwwPDA4mDg0OJg4NDg0ODQ4MDw0ODA8MDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODA8MDwoRDA4mDyUPJQ8MDwwPDA8MDiYOJg4ADQU=", + "30":"JgDMAWA1DyUPJQ8JEQ0ODQ4mDg0ODQ4mDiYODQ4mDg0ODA8lDyUPDA8lDyUPDA8MDiYPDA4NDg0OJg4NDg0ODA8KEQwPChANDg0ODQ4KEQwPDA8MDwwODQ4NDgwPDA8MDwwOJg8MDg0ODQ4NDg0ODA8MDyUPJQ8MDwwPDA8MDg0ODQ4MDwwPJQ8lDwwPDA8MDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDg0PCw8MDwwPDA8lDg0OJg4NDgoRAAj+ZDUPJQ8lDwwPDBALDyUPDA8MDyUPJQ4NDiYODQ4NDiYOJg8MDiYOJg4NDgwPJQ8MDwwPJQ8MDwwODQ4NDg0OChEMDwwPDA8MDg0PDA8LDwwPDA8MDg0OJg4NDg0OJg4NDg0OJg4mDg0ODA8MDwwPDA8MDiYODQ4NDgwPDA8MDwwODQ4mDg0PJQ4NDwsPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4mDwwOJg4NDg0ODQ4NDiUPJQ8ADQU=", + "31":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4lDwwQCw8lDyUPDA8lDyUPDA8MDyUPDA4NDg0OJg4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPJQ8MDwwODQ4KEQwPDA8MDyUPJRALDwwODQ4NDwwODA8MEAsPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ8MDgwPDA8MDwwQCw8MDg0ODQ4MDw0ODA8MDwwODQ4NDg0PCw8MDwwOChEmDwwOJg8MDwwOAAj/YzYOJg4mDg0ODQ8MDiUPDA8MDyUPJQ8MDyUPDA8MDiYOJg4NDiYPJQ4NDg0OJg4NDg0OJg4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0OJg4mDg0ODQ4NDgwPEQoMDwwPDQ0NDg0ODQ4MDwwPDA8lDwwPJQ8MDg0ODQ4NDgwPChEMDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ8LDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MEAsPChElDyUPDA8MDg0ODQ4NDiYOJg4ADQU=" + }, + "turbo":{ + "16":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODA8mDgoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPJQ8MDwwODQ4NDg0ODA8MDyUPJQ8MDwwODQ4NDg0ODA8MDwwPJQ8lDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODBALDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ8lDg0OJg4NDwwOAAj/YzYOJg4mDg0ODQ4MDyUPDA8MDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg4NDg0OJg4NDgwPJQ8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDwwOJg4mDg0ODQ4MDwwPJQ8MDyUPJQ8lDyUPDA8MDwwPDA4mDg0OJg4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODQ4MDwwPDA8lDwwPDA4NDg0OJg4NDgwPDA8ADQU=", + "17":"JgDMAV82ECUOJg4NDgwPDA8lEAsPDA4mDiYPDA4mDwwPDA4mDiYODQ4mDiYODQ4KEiUODQ4MDwwPJQ8MDg0ODQ4NDg0OChILEAsPDA8MDg0ODQ4NDgwPDA8MDwwODQ4MDw0OJQ8MDwwPDA8MDwwODQ4NDyUPJQ4NDg0ODA8MEAsPDA4NDg0OJg8lDg0ODA8MDwwPDA8MDg0ODQ8MDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwQCw8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODQ4lDwwPJQ8MDwwPAAj+YzYPJQ8lDwwPDA8MDyUPChANDiYPJQ8MDiYODQ8MDyUOJg8LDyUPJg4NDgwPJQ8MDwwPJQ8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0OChENDg0ODA8MDwwPJQ8MDg0OJg4NDg0OJg4mDwsPDg0MDwwOJg4NDg0OJg4mDiYPDA4NDg0PDA8lDgwPJQ8MDwwPDA8MFgUODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDBALDwwODQ4mDiYPJQ4mDyUPDA4NDgwPDA8ADQU=", + "18":"JgDMAV82DyUPJQ8MDwwPDA8lDwwPDA4mDiYODQ4mDg0ODQ4mDiYODQ4mDiYODA8MDyUPDA8MDwwOJg4NDg0ODQ4MDwwPDA8MDwwODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDgwPJQ8MDwwPDA8MDg0ODQ4NDyQPJQ8MDwwPDA8MDg0ODQ4NDgwPJQ8mDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDwsPDA8lDwwPJRALDwwOAAj/YzYPJQ8lDwwPDA8JESYODQ4NDiYOJg4NDiYODQ4MDyUPJQ8MDyUPJQ8MEAsPJQ8MDwwOJg8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDgoRJg4NDg0OJg4MDwwPJQ8lDwwPDA8MDg0OJg4NDiYODQ4mDiYODQ4NDgwPDBAkDwwQJA8MDwwPChEMDg0ODQ4MDwwPDA8MDg0ODQ4NDgwQCw8MDwwPDA8MDwwODQ4MEAsPDA8MDwwODQ4NDgwPChEMDwwPDA4NDg0OChEmDgoRJQ8lDyUPDA8MDwwPDA4ADQU=", + "19":"JgDMAV82DyUPJQ8MDwwPDA4mDg0ODQ4mDiYODQ4mDg0ODA8lDyUPDA8lECQPDA8MDyUPDA4NDg0OJg4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODBALDwwPJQ8MDg0OChENDg0ODA8MDyUPJQ8MDwwPDA4NDg0ODQ4MDwwPJQ8lDwwPDA8MDg0OChENDgwPDBALDwwPDA4NDg0ODQ4MDwwQCw8JEgwODQ4NDg0ODA8MDwwPDA4NDwwODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwOJg4NDg0OAAj/YzYPJQ4mDg0ODQ4NDiUQCxAJESUPJRALDyUPDA8MDiYPJQ8MDiYOJg4NDg0OJg4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8KEQwODQ4NDg0ODQ8LEAsPJQ8MDwwPJQ8MDg0OJg4mDg0ODQ4NDgwPJRALDwwPDA8lDyUPDA8MDg0ODQ4mDg0OJg4MDwwPDA8MDwwODQ8MDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDwwODQ4MDwwPDA8MDg0ODQ4mDiYODQ4mDiYODQ4MDwwPDA8ADQU=", + "20":"JgDMAV42DyUPJQ8MDwoRDA4mDg0ODQ4mDiYODQ4mDg0ODQ4mDiYODQ4mDiYODA8MDyUPDA8MDg0OJg4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPChEMEAsPDA4NDg0ODQ4MDwwPJQ8MDwwPDA4NDg0ODA8MDyUPJQ8MDwwPDA4NDgoRDQ4KEQwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDgoRDQ4NDgwPDA8MDwwPDA4mDg0OJg4NDg0OAAj+ZDYOJg4mDg0ODQ4MDyUPDA8MDyUPJQ8MDiYODQ8MDiYOJg4NDiYOJg4NDg0OJg4NDgwPJQ8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPJQ8MDg0OJg4NDg0OJg4mDwwODQ4MDwwPJQ8MDyUPJRALDyUPDA8MDg0ODQ4mDg0OJg4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODA8MDwwPDA8MDg0ODQ4NDgwPChEMDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4mDg0ODQ4lDyUPDA8MDwwPDA4ADQU=", + "21":"JgDMAWA0ESMRIxEJEgkRChEkEAoRChEjESMRChEjEQkSCRIjESMRCRIjESMRCRIJESQQChEKEQoRIxEJEgkSCREKEQoRChEJEgkSCRIJEgkSCREKEQoRCRIJEgkSCREKEQoRIxEJEgkSCRIJEgkRChEKESMRIxEJEgkSCRIJEgkRChEKEQoRIxEjEQkSCRIJEgkRChEKEQkSCRIJEgkSCREKEQoRChEJEgkSCRIJEQoRChEJEgkSCRIJEQoRChEJEgkSCRIJEgkRChEKEQkSCRIJEgkRChEKEQoRCRIjEQkSIxEJEgkSAAj7ZTQQJBAkEAoRChEKESMRCRIJEiMRIxEJEiMRCRIJEiMQJBEJESQQJBAKEQkSIxEJEgkSIxEJEgkRChEKEQkSCRIJEgkSCRIJEQoRChEKEQkSCRIJEQoRIxEKEQkSIxEJEgkSIxEjEQkSCRIJEQoRIxEKEQkSIxEJEiMRCRIJEQoRChEjEQkSIxEJEgkSCRIJEgkRChEKEQoRCRIJEgkSCREKEQoRCRIJEgkSCREKEQoRChEJEgkSCREKEQoRChEJEgkSCRIJEQoRChEKEQkSCRIjESMRIxEJEiMRCRIJEQoRChEADQU=", + "22":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYPDA4mDg0PDA4mDiUQCw8lDyUPDA8MDyUPDA8MDwwOJg4NDg0ODQ4MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwODQ4NDgoRDA8MDyUPJQ8MDwwPDA4NDg0ODQ4MDwwPJQ8lDwwPDA8MDwwODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4mDg0OJg4NDg0OAAj/YzYOJg4mDg0ODQ4MDyUPDA8MDyUPJQ8MDiYPDA8MDiYOJg4NDiYOJg4NDg0OJg4NDgwPJQ8MDwwODQ4NDg0ODA8MDwwPDA8MDg0PDA4NDgoRDA8MDwwOJg4NDg0OJg4NDg0OJg8lDg0ODA8MEAsPJQ8MDiYODQ4NDyUODQ4MDwwPDA8lDwwPJQ8MDwwODQ4KEQ0ODA8MDwwPDA4NDg0PDA4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0PCw8lDwwPJQ8MDyUPDA8MDg0ODQ4ADQU=", + "23":"JgDMAWE0ECQQJBAKEQoRCRIjEQkSCRIjESMRCRIjEQkRChEkECQQChEjESMRChEKESMRCRIJEgkSIxEJEQoRChEJEgkSCRIJEgkSCREKEQoRChEJEgkSCRIJEQoRChEJEgkSIxEJEQoRChEKEQkSCRIJEiMRIxEJEgkRChEKEQkSCRIJEgkSIxEjEQkRChEKEQoRCRIJEgkSCREKEQoRChEJEgkSCRIJEgkRChEKEQkSCRIJEgkRChEKEQoRCRIJEgkSCREKEQoRCRIJEgkSCRIJEQoRChEJEgkSCRIjEQkSIxEJEgkRAAj8ZTQRIxEjEQkRChEKESMRCRIJEiMRIxEJEiMRCRIJESQQJBAKESMRIxEKEQkSIxEJEgkSIxEJEgkRChEKEQkSCRIJEgkRChEKEQoRCRIJEgkSCRIJEQoRJBAKEQoRIxEJEgkSIxEjEQkSCREKEQoRIxEKEQkSCRIJEiMRCREKEQoRChEjEQkSIxEJEgkSCRIJEQoRChEKEQkSCRIJEQoRChIJEQoRCRIJEgkSCREKEQoRCRIJEgkSCREKEQoRCRIJEgkSCRIJEQoRChEKEQkSCRIjESMRCRIJEiMQChEKEQoRChEADQU=", + "24":"JgDMAV82DyUPJQ8MDwwPDA4mDwwODQ4mDiYPDA4mDg0ODQ4mDiYPDA4lDyYODA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDg0ODQ4NDwwODA8MDwwPDA8MDg0ODQ4NDgwPJQ8MDwwPDA4NDg0ODQ4MDyUPJQ8MDwwPDA8MDg0ODQ4NDgwPJQ8lDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ8LDwwPDA8MDwwODQ4NDg0ODBALDwwPDA8lDwwPJQ8MDwwOAAj/YzYOJg8lDwwPDA4NDiYODQ4MDyUPJRALDyUPDA8MDyUPJQ8MDiYPJQ8MDg0OJg4NDg0OJg4NDgwPDBALDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPJQ8MDwwPJQ8MDwwPJQ8lDg0ODQ4NDg0OJg4NDiYOJg4mDgwPDA8MDwwPDA4mDwwOJg4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDBALDwwODQ4NDg0PDA4MDwwPDA8MDg0ODQ8MDgwPDA8MEAsPDA4NDg0ODQ4MDwwPDA8lDwwPDA8MDiYODQ4MDwwPDBAADQU=", + "25":"JgDMAWA1DyUPJQ8MDwwPDA4mDg0ODQ4mDiYODQ4mDwwODA8lECQPDA8lDyUPDBALDyUPDA8JEQ0OJg4NDgoRDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPJQ8MDwwODQ4NDgoRDQ4MDyYOJQ8MDwwPDA8MDg0ODQ4NDgwPJQ8lDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4mDg0OJg4NDg0OAAj+ZDYOJg4mDg0PDA4NDiYODQ4MDyYOJg4MDyUPDA8MDyUPJQ8MDiYPJQ8MDg0OJg4NDg0OJg4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA8JEQ0ODQ4NDgwPJQ8MDwwPJQ8MDwwPJQ8lDwwPDA4NDg0OJg4NDgwPJQ8lDwwPDA8MDg0ODQ4mDg0OJg4MDwwPDA8MDwwPDA8MDg0ODQ4MDwgUCw8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPChEMDgoRDQ4NDgwPDBALDwwPDA4mDyUOJg8lDwwPDA4NDg0ODQ4ADQU=", + "26":"JgDMAV82DiUPJQ8MDwwPDA8lDwoRDA4mDyUODQ8lDg0ODQ4mDyUPDA4mDiYODQ4MDyUPDBALDwwPJQ8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDg0OJg8LDwwPDA8MDwwODQ4NDiYOJg4NDg0ODQ4MDwwPDA4NDg0OJg4mDg0ODQ4MDwwPDA4KEQ0ODA8MDwwPDA8MDg0ODQ4NDgwQCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0PCw8MDwwPDA4NDg0ODQ4MDwwPDA8lDwwPJQ8MDwwOAAj/YzYOJg8lDwwODQ4NDiYODQ4MDyUPJQ8MDyUPDA8MDyUPJRALDyUPJQ8MDwwOJg4NDg0OJg4NDgwPDA8KEQwPDA8MDg0ODQ4NDgoRDA8MDwwODQ4NDg0OJQ8MDwwPJQ8MDwwPJQ8lDwwPDA8MDg0OJg4MDyUPDA8lDwwPDA8MDwwODQ4mDg0OJg4MDwwPChEMDwwPDA4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwQCw8MDwwPDA4mDwwOJg4mDg0ODQ4NDgwPDA8ADQU=", + "27":"JgDMAV82DiYPJQ8MDg0ODQ4mDg0ODQ4mDiYODA8lDwwQCw8lDyUPDA8lDyUPDA8MDiYPDA4NDg0OJg4NDg0ODA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUQJA8MDwwPDA8MDg0ODQ4MDwwPJQ8lDwwPChEMDg0PDA4NDwwODA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPCRIMDg0ODQ4NDg0ODA8MDwwPDA8lDwwOJg4NDg0OAAj+ZDYOJg4mDg0ODQ4NDiYODA8MDyUPJQ8MDyUPDA8MDiYPJQ8JEiUPJQ8MDg0PJQ4NDg0OJg4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPEAsMDwwODQ4NDgwPJRALDwwPJQ8MDg0OJg4mDg0ODQ4NDgwPJQ8MDwwPDA8lDwwODQ4NDg0PDA4mDgwPJQ8MDwwPDA4NDgoRDQ4NDwsPDA8MDwwODQ4NDgwPDBALDwwODQ4NDg0ODA8MDwwPDA8MDwwPDA4NDg0ODA8MDwwQCw8MDg0ODQ4lDyUPDA8mDgwPDA8MDwwODQ4ADQU=", + "28":"JgDMAWA0ESMRIxEJEgkRChEjEQoRChEjESMRCRIjEQkSCRIjESMRCREkECQQChEKESMRChEJEgkSIxEJEgkRChEKEQoRCRIJEgkSCREKEQoRCRIJEgkSCRIJEQoRChEKEQkSIxEJEgkSCRIJEQoRChEKESMRIxEJEgkSCREKEQoRChEJEgkSIxEjEQkSCREKEQoRChEJEgkSCRIJEQoRChEKEQkSCRIJEgkRChEKEQkSCRIJEgkRChEKEQkSCRIJEgkRChEKEQoRCRIJEgkSCRIJEQoRChEKEQkSCRIjEQkSIxEJEQoRAAj8ZTMRIxEjEQoRCRIJEiMRCRIJESQQJBAKESMRChEKESMRIxEJEiMRIxEJEgkRJBEJEQoRJBAKEQoRChEJEgkSCREKEQoRChEJEgkSCREKEQoRChEJEgkSIxEJEgkRJBAKEQoRIxEjEQoRCRIJEgkSIxEJEiMRIxEJEgkRChEKEQkSCRIjEQkRJBAKEQoRChEJEgkSCRIJEQoRChEKEQkSCRIJEgkSCRIJEQoRChEJEgkSCREKEQoRChEKEQkSCRIJEgkRChEKEQkSCRIJEgkRChEjEQoRChEjEQkSCRIJEgkRChEADQU=", + "29":"JgDMAV82DiYOJg4NDg0ODQ4lDwwPDA8lDyUPDA8lDwwPDA4mDyUODQ4mDiYODQ4NDiYODQ8MDgwPJQ8MDwwODQ4NDg0ODQ4NDgoRDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDg0OJg4NDgwQCw8MDwwPDA8MDiYOJg8MDwwODQ4MDwwPDA8MDwwOJg4mDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4KEQwPDA8MDwoRDA4NDg0ODBALDwwPDA8MDg0ODQ4mDg0OJg4MDwwPAAj+ZDYOJg8lDg0ODA8MDyUPDA8MDiYOJg8MDyUPDA4NDiYOJg4NDiYPJQ4NDg0OJg4MDwwPJQ8MDwwPDA4NDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODA8MDwwPJQ8MDwwOJg4NDg0OJg4mDg0PCw8MDwwPJQ8MDwwOJg4NDg0ODQ4MDwwPDA8lDwwPJQ8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MEAsPDA4NDg0ODA8MDwoRDA4NDg0PDA4MDwwPDA8MDwwODQ4NDg0ODA8mDiYOJQ8MDwwPDA8MDg0ODQ4ADQU=", + "30":"JgDMAV42DyUQJBALDwwPDA4mDwwODQ4mDiYODQ4mDg0ODBAkDyUPDA8lDyUPDA8MDyUPDBALDwwPJQ8MDg0ODQ4NDgwPDA8MDg0PDA4NDwsPDA8MDwwPDA4NDg0ODQ4MDwwPJQ8MDwwPDA4NDg0ODQ4MECQPJQ8MDwwPDA8MDg0ODQ4NDgwQJA8lDwwPDA8MDwwPDA4NDg0ODQ4MDw0ODA8MDg0PDA8MDwsPDA8MDwwPDA4NDwwODQ4NDgwPDA8MDwwODQ8MDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8lDwwPJQ8MDg0OAAj/YzYPJRAkDwwPDA4NDiYODQ4NDyUOJg4KESYODQ4MDyYOJg4MDyUPJQ8MDwwPJQ8MDwwOJg8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0OJg4NDg0OJQ8MDwwPJQ8lDwwPDA8MDwwOJg4NDiYODQ4NDgwPDA8MDwwPChAmDwwOJg4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODBALEAsPDA4NDwwODA8MDwwPDA4NDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8lDwwOJg4NDg0ODQ4NDgwPDA8ADQU=", + "31":"JgDMAV82DyUPJQ8JEgwODQ4mDg0ODQ4mDiYODQ4mDgwPDA8lDyUPDA8lECQPDA8KESUPDA4NDwwOJg4NDg0ODQ4KEQwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0PDA4MDwwPJQ8MDwwODQ4NDgoRDA8MDyUPJQ8MDwwPDA8MDgoSDA4NDwsPJQ8lEAsPDA8MDg0PDA4NDgwPDA8MDwwODQ4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8lDwwPJQ8MDg0OAAj/ZDUPJQ8lDwwQCw8MDiYODQ4NDiYOJg4NDiYODQ4MDyUPJQ8MECQPJQ8MDwwPJQ8MDw0OJQ4NDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDg0OJhMIEQkPJQ8lDwwPDA4NDg0OJg8MDgoRDQ4KEQwPDA8MDg0ODQ4mDg0OJQ8MDwwPDA8MDgoRDQ4NDg0ODA8MDwoRDA8MDg0ODQ8MDgwQCw8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4mDiYODQ4NDg0ODA8MDwwPDA4ADQU=" + }, + "auto":{ + "16":"JgDMAV42DyUQJBALDwwPDA8lDwwODQ4mDiYODQ4mDwwODA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0PCw8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPDA4NDg0ODQ4lEAsPDA8MDwwODQ4NDg0PCw8MDwwPJQ8lDwwPDA4NDwwODQ4MDwwQCw8MDwwODQ4NDgwPDA8MDwwPCRENDg0PDA4NDgwPDA8MDwwODQ4NDg0PCxALDwwPDA4NDwwODQ4MEAsPDA8MDwwPDA4mDg0ODQ4MDyUQAAj9YzcOJg4mDg0ODBALDyUPDA8MDyUPJQ8MDyUPDA8MDiYOJg8MDyUOJg4NDg0OJg4MDwwPJQ8MDwwPDA8MDg0ODQ4NDwsPDA8MDwwPDA4NDg0ODQ4MDwwPJRALEAsOJg4NDg0OJg4mDg0ODQ8MDgwQCw8MDyUPJQ8lDyUPDA4NDg0ODQ4NDwsQCw8MDwwODQ4NDg0ODA8MEAsPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0PDA4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4mDyUOJg4NDiYODQ8LECQQJA8ADQU=", + "17":"JgDMAV42DyYOJg4NDgoRDA8lDwwPDA8lDyUPDA4mDwwODQ4mDiYODQ4mDiYODA8MDyUPDA8MDwwPJQ8MDgsQDQ4NDgwPDBALDwoRDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0OJg4NDgwPDBALDwwODQ4mDg0ODA8MDwwPDA8MDwwODQ4KEQ0OJg4mDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MEAsPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDwwODQ4NDgwPDA8lDwwPDA8MDiYOAAj/YzYPJQ8lDwkSDA4NDiYPDA4NDiYOJg4NDiYODQ4NDiYOJg4NDiYOJg8LDwwPJQ8MDwwPJQ8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPJQ8MDg0OJg4NDg0OJg4mDwsPDA8MDwwPDA4NDg0OJg4mDiYODQ4NDgwPDA8MDwwODQ8MDg0ODA8MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8lDwwPJQ8MDyUPDA4KESYOJg4ADQU=", + "18":"JgDMAV42DyYOJg4MDwwPDA8lDwwPDA4mDiYPDA4mDg0ODQ4mDiYODQ4mDiYOChEMDyUPDA8MDwwPJQ8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDgwPJQ8MDwwPDA8MDwwODQ4mDwwODQ4MDwwPDA8MDg0PDA4NDg0OJg4mDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ8LDwwQCw8MDwwOChENDgwPDA8MDwwPDA8MDg0ODQ4KEQwPDA8lDwwPDA8MDiYPAAj+YzYPJQ8lDwwPDA4NDiYODQ4NDiYOJg4NDiYODQ4NDiYOJRALDyUPJQ8MDwwPJQ8MDwwOJg4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0OJg4NDgwPJQ8MDwwPJQ8lEAsPDA8MDwwODQ4NDiYODQ4mDiYODA8MDwwPDA8MDg0ODQ4NDwsPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDwwODQ4KEQwPDA8MDwwODQ4NDg0ODQ4KEQwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4mDiYODQ8MDiYODQ4MDyUPJQ8ADQU=", + "19":"JgDMAV82DiYPJQ8MDwwODQ4mDg0ODQ4lDyYODQ4mDgwPDA8lDyUPDA8lDyUPDA8MDyUODQ4NDg0OJg4NDgwPDBALDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwOJg4NDwwODQ4NDgwPDA8lDwwPDA4NDg0ODQ4MDwwPDA8MDwwOJg4mDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgoRDA8MDwwPDA4NDg0PCxALDwwPDA8MDgoRDQ8lDg0ODA8MDyUPAAj/YzUPJQ8mDg0ODA8MDyUPDA8MDyUPJQ8MDyUPDA4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwQJA8MDwwODQ4KEQ0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwPJQ8MDg0OJg4NDg0OJg4mDg0ODQ4MDwwPDA4NDg0ODQ4mDyUOCxAMDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4mDg0ODQ4NDiYPDA4NDiUQJA8ADQU=", + "20":"JgDMAV81DyYOJg4NDgwPDA8lDwwPDA8lDyUPDA8lDwwPChAmDiYODQ4mDiYODQ4NDiYODA8MDwoRJQ8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwkSDA4NDg0OJg4NDgwPDBALDwwODQ4mDwwODQ4MDwwPDA8MDwwODQ4NDg0OJg4mDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MEAsPDA4NDg0ODA8MDwwPDA8MDgoRDQ4MDwwPDA8lDwwPDA4NDiYOAAj/YzYOJg4mDg0ODQ4NDiUPDA8MDyUPJQ8MDyUPDA8MDyUPJQ8MDyUPJQ8MDg0PJQ4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPJRALDwwPJQ8MDwkRJg4mDg0ODQ4NDgwPDA8MDyUPJQ8MDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDwwODA8MDwkSDA4NDg0ODA8MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8lDyUPJRAkDwwPDA8MDiYOJg8ADQU=", + "21":"JgDMAV82DiYOJg4NDg0ODA8mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA8lDiYODQ4KESYODQ4MDwwPJQ8MDwwODQ8MDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4MDwwPDA8MDwwODQ4mDwwODQ4NDgwPDA8MDwwODQ4NDg0OJg4mDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OChEMDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4MDwwPDA8lDwwPDA8MDiYOAAj/YzYOJg4mDwoRCRENDiYODA8MDyUPJg4NDiYODA8MDyUPJQ8MDyUPJQ8MDwwPJQ8MDg0PJQ4NDwsPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0OJg4MDwwPJQ8MDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPJQ8MDyUPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4NDwsPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA8JEQ0ODQ4NDgwPDA8MDwwODQ8MDg0ODQ4lDwwPJQ8lDwwPDA8MDyUPJQ8ADQU=", + "22":"JgDMAV82DiYOJg4NDwwODA8lDwwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDiYPDA4NDiYODQ4NDgwPJQ8MDwwPDA4NDgoRDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0OJg8MDgwPDA8MDwwPDA8lDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4mDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ8MDg0ODQ4MDwwPDA8MDg0ODQ4MDwwPDA8lDwoRDA8MDiYPAAj+YzYOJg4mDg0ODQ4MDyUQCw8MDyUPJQ8MDyUPDA8MDyUOJg8MDiYOJg8MDg0OJg4NDg0OJg4MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwPJQ8MDwwOJg4NDg0OJg4mDg0ODQ4MDwwPDA8MDyUPDA8MDiYODQ8MDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDg0OChEMDwwPDA4NDg0ODQ4NDgwPDA8lDyUPDA4mDg0ODQ4JEiYOJg4ADQU=", + "23":"JgDMAV81DyUPJg8MDgwPDA8lDwwPDA8lDyUODQ4mDg0ODQ4mDiYODQ4lDyUPDA8MDyUPDA8MDwwPJQ8MDwwODQ4NDgwPDA8MDwwODQ4KEQ0ODA8MDwwPDA8MDg0ODQ4MDwwPJQ8MDwwPDA8MDwwODQ4mDwwODA8MEAsPDA4NDg0ODA8MDwwPJQ8lDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDwwODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwODQ4mDg0ODA8MDyUPAAj+YzYPJQ8lDwwPDA8MDiYPDA4NDiYOJg4NDiYODA8MDyUPJQ8MDyUPJQ8MDwwPJQ8MDwwOJg4NDg0ODA8MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDgwPJRALDwwPJQ8MDwwPJQ8lDwwODQ4NDg0ODA8MDwwPDA4NDiYODQ4NDg0ODA8MDwwPDA8MDg0ODQ8LDwwPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODA8MDwwPDA8lDwwPDA4mDg0ODQ4NDiUPJg4ADQU=", + "24":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA8lDwwPDA4mDyUPDA4mDiYODQ4NDiYODQ4MDwwPJQ8MDwwODQ4NDg0PCw8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ8MDg0OJg4MDwwPDA8MDwwODQ4mDg0ODQ4MDwwPDA8MDwwODQ4NDgwPJQ8mDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDgwPDBALDwwPDA8lDwwPDA4NDiYPAAj9YzYQJA8mDgoRDA8MDyUPDA8MDiYOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDgoRJQ8MDwwPJQ8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0OJg4NDg0OJg4NDgwPJQ8lDwwPChEMDwwPDA4NDiYOJg4mDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MEAsPDA4KEQ0ODQ8LDwwPChEMDwwPCRENDg0ODQ4MDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8lDyUPJQ8MDg0ODQ4NDiUPJQ8ADQU=", + "25":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA8lDwwODQ4mDiYODQ4mDiYODQ4MDyUPDA8MDwwPJQ8KEA0ODQ4NDg0ODQ8LDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDg0OJQ8MDwwPDA8MDg0ODQ4mDg0ODA8MDwwPDA8KEQwODQ4NDg0OJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDgwPChEMDwwPDA4NDg0ODA8MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA8lDwwODQ4NDiYOAAj+ZDYOJg4mDg0ODA8MDyUPDA8MDiYOJg4NDiYODQ4NDiYPJQ4NDiYOJg4NDgwPJg4MDwwPJQ8MDwwODQ4NDg0ODA8MDwwPDA8MDwwODQ4MDwwPDA8MDwwOJg4NDg0OJg8MDgwPJQ8lDwwPDA8MDwwOChENDg0OJg4mDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwQCw8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4mDg0OJRALDwwPDA8MDyUPJQ8ADQU=", + "26":"JgDMAV82DiYOJg4KEQwPDA8lDwwPDA4mDiYPDA4mDwwODQ4mDiYODQ4lDyUPDA8MDyUPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPCRIMDg0ODQ4NDgwPJQ8KEQwPDA8MDg0ODQ4mDg0ODA8MDwwPDA8MDg0ODQ4MDwwPJQ8lDwwPDA4NDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDgwPDA8MDwwPDA4KEQ0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4mDg0ODQ4MDyUPAAj+YzYPJQ8lDwwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg4MDyUPJQ8MDwwPJQ8MDwwPJQ8MDg0ODQ4MEAsPDA8MDwwPDA4NDg0OChEMDwwPDA8MDg0OJg4NDwwOJg4NDgwPJQ8lDwwPDA4NDg0ODQ4MDyYODA8lDwwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDwwODQ4NDwsPDA8MDwwPDA4NDg0OChEmDiYODA8MDwwPDA4NDiYOJg4ADQU=", + "27":"JgDMAV82DiYOJg4NDg0ODA8lDwwQCw8lDyUPDA8lDwwPDA4mDyUPDA4mDiYODQ4NDiYPDA8LDwwPJQ8MDwwPDA4NDwwODQ4MDwwPDA4NDwwODQ4MDwwPDA8MDwwODQ4NDg0OJg4NDgwPDA8MDwwODQ4mDg0ODQ4MDwwPDA8MDg0ODQ8MDgwPJg4mDg0PCw8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPChEMDwwPDA4NDg0ODA8MDwwODQ4NDg4NDA8MDwwPDA8lDwwPDA4NDiYOAAj+ZDYOJg4mDg0PDA4NDiYODQ4MDyUPJQ8MDyUPDA8JEiUPJQ8MDiYOJg8MDg0PJQ4NDgwPJQ8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA8MDg0ODQ4MDwwPJQ8MDwwPJQ8MDwwOJg4mDwwODQ4NDg0ODA8MDwwODQ4mDg0ODQ4MDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4KEQwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8lDwwPDA4NDg0ODQ4MDyUPJQ8ADQU=", + "28":"JgDMAV82DiYOJg4NDg0ODA8lDwwPChElDyUPDA8lDwwQCw8lDyUPDA4mDyUPDA4NDiYODQ4NDg0OJQ8MEAsPDA4NDg0ODA8MDwwPDA8MDg0ODQ8MDg0ODA8MDwoRDA8MDwwOJg4NDg0ODA8MDwwPDA4mDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4mDg0ODQ4MDwwPDA8MDwwODQ4MDwwPDA8MDwwODQ4NDg0OChEMDwwQCw8MDwwODQ4NDg0ODA8MDwwPDA4NDgoRDQ8LDwwPDA8MDg0ODQ4MDwwPDA8lEAsPDA8MDiYOAAj+ZDYOJg8lDg0PDA4MECQPDA8MDyYOJg4MDyUPDA8MDiYPJQ4NDiYOJg4NDg0OJg4NDg0OJg4NDgwPDA8MDwwODQ4NDwsPDA8MDwwPDA4NDg0ODQ4MDwwPJQ8KEQwOJg8MDg0OJg4mDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDg0ODQ4KEQwPDA8MDg0ODQ4NDgwQCw8MDg0ODQ4NDwwPCw8MDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8lDyUPJQ8lDyUPJQ8lDwwQJA8ADQU=", + "29":"JgDMAV42DiYOJg4NDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA8lDyUQCw8MDiYODQ4NDg0OJg4NDgwPDA8MDwwODQ4NDgwPChEMDwwODQ4NDg0ODQ4NDgwPDA8MDwwOJg4NDg0ODA8MDwwPDA8lDwwODQ4KEgsPDA8MDwwPDA8MDwwPJQ4mDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8JEgwODQ4NDgwPDA8lDwwPDA8MDiYOAAj+ZDYOJg4mDwwODQ4NDiYODA8MDyUPJQ8MDyUQCw8MDiYOJg4NDiYOJg4NDg0PJQ4NDgwPJQ8MDwwPCRIMDwwODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDg0OJg4NDg0OJg4mDg0ODQ4MDwwPDA8MDwwOJg4NDg0ODQ4MDwwPDA8JEQ0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4MDw0ODA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4mDg0OJg4mDiYOJg4mDg0OJg4ADQU=", + "30":"JgDMAV82ECQPJQ8MDwwPDA4mDwwODQ4mDiYODQ4mDg0ODA8lDyYODA8lDyUPDA8MDyUPDA4NDg0OJg4NDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8MDwwPJQ8MDwwODQ4NDg0ODA8lDwwPDA4NDg0ODQ4MDwwPCRIMDwwOJg4mDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwPDA8MDg0ODQ4mDg0OChEMDyUPAAj+YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg4NDiYODQ4MDyUPJQ8MDyUPJg4NDgwPJQ8MDwwPJQ8MDwwODQ8MDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8MDwwOJg4NDg0OJg8JEQwPJQ8mDgwPDA8MDwwODQ4NDiYODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8MDgwPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODA8MDwwPDA8KEA0ODQ4mDiYODA8lDyYOJg4mDgoRJg4ADQU=", + "31":"JgDMAV81DyYOJg4NDwsPDA8lDwwPDA8lDyUPDA4mDg0ODQ4mDiYODQ4mDiYODQ4MDyUPChEMDwwPJQ8MDwwODQ4NDgwPDA8MDwwPDA4NDwwODA8MDwwPDA8MDwwPDA4NDg0OJg4MDwwPDA8MDwwODQ4mDg0ODA8MDwwPDA8MDg0ODQ4NDgwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8NDgwPDA4NDg0ODA8MDwwPDA4NDg0OChENDgwPDA8MEAsODQ4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODA8MDwwODQ4mDg0ODQ4NDiUPAAj+ZDUPJQ8lDwwPDA8MDyUPDA4NDiYOJg4NDiYODQ4MDyUPJQ8MDyUQJA8MDwwPJQ8KEQwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0OJg4NDgwPJQ8MDwwPJQ8lDwwPDA8MDg0PDA4NDgwPDA8MDg0OCRINDg0ODA8MDwwPDA8MDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA4NDg0ODQ4NDg0PCw8MDwwPDA8MDg0ODQ4MDwwPDA8lDwwODQ4mDiYOJg4mDg0OJg4ADQU=" + } + }, + "fan_only":{ + "silent":{ + "16":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "17":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "18":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "19":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "20":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "21":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "22":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "23":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "24":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "25":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "26":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "27":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "28":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "29":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "30":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=", + "31":"JgDMAV82DiYOJg4NDg0ODA8lDwwPDA8lDyUPDA4mDwoRDA4mDyUPDA4mDiYPCRENDyUODQ4NDgwPJQ8MDwwPDA8JEQ0OBxQNDgwPDA8MDg0ODQ4KEQ0ODA8MDwwPDA8MDyUPJQ8MDwwODQ4NDwwPDA4MDyUPDA8MDwwODQ4NDg0ODBALDwwPJQ8lDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4mDwwODQ4mDiYOAAj/ZDUQJA8lDwwPDA8MDiYODQ8MDiYOJg4NDiYODQ4NDiUPJQ8MDyUQJBALDwwPJQ8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDg0OJg4NDg0OJg4NDgwPJQ8lDyYODQ4MDwwQCw8MDg0PDA4NDiYODQ4NDg0ODA8MDyUPDA4NDg0ODQ4MDwwPChEMDwwODQ4NDg0ODA8MDwwPDA4NDg0PDA4NDgwPDA8MDwwPDA4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODQ4mDgwPJQ8MDyUPDA8MDiYPJQ8ADQU=" + }, + "low":{ + "16":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "17":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "18":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "19":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "20":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "21":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "22":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "23":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "24":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "25":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "26":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "27":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "28":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "29":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "30":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=", + "31":"JgDMAV82DyUPJQ4NDg0ODQ4lEAsPDBAkECQPDA8lDwwPDA4mDyUPDA4mDyUPDA4NDiYPDA4NDgwPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODQ8LDwwPDA8NDwwOJRALDwwODQ4NDg0ODQ4MDyUQCRANDg0PDA4NDgwPDA8MDwwOJg4mDwwODQ4NDg0ODQ4NDg0ODA8MDg4ODQ8LDg0ODA8MDwwODQ4ODQ0ODQ4NDgwPDBALDwwODQ4NDg0PCw8MDwwPDA8MDwwODQ4NDg0ODA8MDwwQCRElDg0OJg4NDiYOAAj/YzYQJA8lEAsPDA8MDiYPDA4NDiYOJg8MDiYODQ4NDiYOJg8MDiYOJg8MDwsPJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODQ4MDwwQJBALDwwPJQ8MDg0OJg4mDiYODg0NDgwPDA8MDwwODQ4ODiUPDA4NDg0ODQ4MDyUPDA8MDg0ODQ8MDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwODQ4NDg0ODQ4MEAsPDA8MDwwODQ4NDg0ODA8MEAsPDA4NDg0ODQ4mDg0OJg4NDyQPDA8MDyUPJQ8ADQU=" + }, + "mid":{ + "16":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "17":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "18":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "19":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "20":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "21":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "22":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "23":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "24":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "25":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "26":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "27":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "28":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "29":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "30":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=", + "31":"JgDMAV42DyUPJQ8MDwwPDA8lDwwPDA8lDiYODQ4mDg0ODQ4mDiYPDA4mDiYODQ4MDyYODA8MDwwPJQ8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0OJg4NDgwPDA8MDwwPDA4NDg0OJg4NDwwODA8MDwwPDA8MDg0OJg4mDwwODQ4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPDQ4MDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ4lDwwPJQ8mDiYOAAj/YzYPJQ8lDwwPDA8MDiYODQ4NDiYOJg8MDiYODQ4NDiYOJg4NDiYOJg4MDwwPJQ8KEQwPJQ8MDwwODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwOJg8MDg0OJg4NDgoRJg4mDiYODQ4KEQwPDA8MDwwPDA8MDiYODQ4NDg0ODA8lDyUPDA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0PDA4NDg0ODA8MDwwPDA8MDg0ODQ4NDg0ODA8MDwwPDA4KEQ0ODQ4MDwwPDA8MDg0ODQ4mDyUOJg4NDiYODA8MDyYOJQ8ADQU=" + }, + "high":{ + "16":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "17":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "18":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "19":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "20":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "21":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "22":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "23":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "24":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "25":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "26":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "27":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "28":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "29":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "30":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=", + "31":"JgDMAV82DiYPJRALDg0ODQ4mDgwPDA8lDyUPDA8lDwwPDA8lDyUPDA4mDyUPDA4NDiYPDA8MDg0PJQ4NDg0ODA8MDwwPDA8MDg0PDA4MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPJQ8MDwwPDA4NDwwODQ4NDgwPJQ8lEAsPDA8MDg4ODA4NDg0PCw8MDwwPDA4NDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODBALDwwPDA8MDg0ODQ4NDg0ODBALDwwPDA4NDg0ODQ4NDgwPDA8lDwwPJQ8MDwwOAAkAYzUPJRAkDwwPDA8MDyUPDA4NDyUPJQ8MDiYPDQ0NDiYOJg8MDyUOJg4NDg0OJQ8MEAsPJQ8MDwwPDA8MDg0ODQ4NDgwPDA8MDg0ODQ4NDg0ODA8MEAsQJA8MDwoRJQ8MDwwPJQ4mDiYODQ4ODQ0ODA8MDwwPDA4NDiYPDA4KEQ0ODQ4mDg0OJQ8KEQwPDA8MDg0PDA4NDwwPCw8MDwwODQ8MDwwODQ4NDgwQCw8MDg0ODQ4NDg0ODA8MDwwPDA4NDg0ODQ8NDQwPDQ4MDwwPDA4mDiYPDA4mDyUPDA4NDiYPJQ4ADQU=" + }, + "turbo":{ + "16":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "17":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "18":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "19":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "20":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "21":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "22":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "23":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "24":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "25":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "26":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "27":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "28":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "29":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "30":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=", + "31":"JgDMAV82DyUPJQ8MDwwODQ4mDg0ODQ4mDiYODQ4mDg0ODQ4lECQPDA8lDyUPDA8MDyUPDA8MDwwOJg8MDg0ODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8NDQ0ODQ4NDgwPJQ8MEAsPDA8MDg0ODQ4NDiUPJg4NDgwPDA8MDwwODQ4NDg0OJg4mDg0PCw8MDwwPChEMDg0ODQ4NDgwPDA8MDwkSDA4NDg0ODQ4NDgwPDA8MDwwOChENDg0ODA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwPDA4NDg0ODA8lDwwPJQ8MDwwPAAkAYjUPJQ8mDgwPDA8MDyUPDA4NDiYOJg4NDiYODQ4NDiYOJg8MDiYOJg4NDgwQJQ4NDgwQJA8MDwwQCw8MDg0ODQ4MDwwPDA8MDwwODQ4NDg0ODA8MDwwPJQ8MDwwPJQ8MDg0PJQ4mDyUPDA4NDg0OJg8MDgwPDA8MDyUPDA8MDg0ODQ8lDg0OJg4NDgwPDA8MDwwODQ4NDg0OChEMDwwPDA8KEQwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwQCw8MDwwODQ4NDg0ODQ8LDwwPDA8lDyUPDA8lDyUPDA4NDg0ODQ4ADQU=" + }, + "auto":{ + "16":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "17":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "18":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "19":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "20":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "21":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "22":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "23":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "24":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "25":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "26":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "27":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "28":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "29":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "30":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=", + "31":"JgDMAV82DyUPJQ8MDwwODQ4mDwwODQ4mDiYPDA4mDgwPDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg8MDg0ODQ4NDwsPChANDg4NDQ4ODgwODA8MDw0ODA4NDg4NDQ4NDgwPJRALDwwPDA4NDg0ODQ4mDwwODQ8LDwwPDA8MDw0NDQ4NDgwPJQ8lDw0ODA8NDg0NDQ4NDg0ODQ4MDwwPDA4ODQ0ODQ4NDg0ODQ8MDgwPDA8KEA4NDg4MDg0ODQ4MEAsPDA4ODQ0OChENDwsPDA8MDg0ODg0NDg0PCw8lEAsPDA8MDyUPAAj+YzcOJg8lDwwPCw8MDyUPDA8MDyUPJQ8MDyUPDQ4MDiYOJg4ODSYOJg8MDg0OJg4NDg0OJRALEAsPDA8MDwwODQ8NDgwPDA4NDg0ODA8MDg0ODQ4NDg0OJg8JEgwOJRAMDg0OJQ8lDyUPDA8MDwwODQ4NDwwPCw8MDyUPDA8MDg0ODQ4NDg0PDA4MDwwPDA8MDg0PDA8MDgwPDRALDg0VBRMIDwwODQ4MEAsPDA8NDg0NDQ4NDg0PDA4MDwwODQ4NDg0ODQ4MDwwPDQ4MDg0ODQ4mDwwPCxAMDyQQCxALDyUPJQ8ADQU=" + } + }, + "dry":{ + "silent":{ + "16":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "17":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "18":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "19":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "20":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "21":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "22":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "23":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "24":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "25":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "26":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "27":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "28":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "29":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "30":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "31":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=" + }, + "low":{ + "16":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "17":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "18":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "19":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "20":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "21":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "22":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "23":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "24":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "25":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "26":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "27":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "28":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "29":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "30":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "31":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=" + }, + "mid":{ + "16":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "17":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "18":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "19":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "20":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "21":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "22":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "23":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "24":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "25":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "26":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "27":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "28":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "29":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "30":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "31":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=" + }, + "high":{ + "16":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "17":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "18":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "19":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "20":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "21":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "22":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "23":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "24":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "25":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "26":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "27":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "28":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "29":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "30":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "31":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=" + }, + "turbo":{ + "16":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "17":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "18":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "19":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "20":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "21":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "22":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "23":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "24":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "25":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "26":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "27":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "28":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "29":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "30":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "31":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=" + }, + "auto":{ + "16":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "17":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "18":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "19":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "20":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "21":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "22":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "23":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "24":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "25":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "26":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "27":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "28":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "29":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "30":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=", + "31":"JgDMAV42DiYOJg8MDg0ODQ4mDg0ODQ4mDiYODA8lDwoRDA8lDyUPDA8lDyUPDA8MDyUPDA8MDg0OJg4NDgwPDA8MDwwODQ4NDg0OBxQMDwwPDA4NDg0ODQ4NDg0ODA8MDwwPJQ8MDg0ODQ4NDgwPDA8MDyUPDA4NDg0ODA8MDwwPDA8MDwwOJg8lDwwODQ4NDg0ODA8MDwwPDA4NDg0ODQ8LDwwPDA8MDwwPDA4NDg0ODA8MDwwPDA4NDg0ODQ4MDwwPDA8MDg0ODQ4KEQwPDA8MDwwPDA8MDg0ODQ4mDgwPJQ8MDyUQAAj9ZDYOJg4mDg0ODQ4MDyUPDA8MDyUOJg4NDiYODQ4NDiYOJg4NDiYOJg4NDg0OJQ8MDwwPJQ8MDwwPDA8MDg0ODQ4KEQwPDA8MDgoRDQ4NDg0ODA8MDwwPJQ8MDwwOJg4NDg0ODQ4mDg0ODA8MDwwPDA4KEQ0ODQ4MDyUPDA8MDwwODQ4NDiYODQ4NDgwPDA8MDwwPDA8MDg0ODQ4NDgwPDA8MDwwODQ4NDgwPDA8MDwwODQ4NDg0ODQ4MDwwPDA8MDwoRDA4NDg0ODQ4MDwwPCRIlDyUPDA8lDwwODQ4NDiYOJg4ADQU=" + } + } + } +} \ No newline at end of file diff --git a/appdaemon/settings/apps/test/test.py b/appdaemon/settings/apps/test/test.py new file mode 100644 index 0000000..e886ce4 --- /dev/null +++ b/appdaemon/settings/apps/test/test.py @@ -0,0 +1,20 @@ +import appdaemon.plugins.hass.hassapi as hass +import os + +""" +Monitor events and output changes to the verbose_log. Nice for debugging purposes. +Arguments: + - events: List of events to monitor +""" +class Test(hass.Hass): + notify = None + + def initialize(self) -> None: + self.listen_state(self.sensor_trigger, 'sensor.0x00158d0003a361d8_action') + self.listen_state(self.sensor_trigger, 'binary_sensor.0x00158d0003f26d61_occupancy') + # self.listen_state(self.sensor_trigger, 'sensor.remote20_action') + + def sensor_trigger(self, entity, attribute, old, new, kwargs): + if new != "" and new != "unknown": + self.log("doorbell call") + self.toggle("switch.0x00158d00020f3a29_switch") \ No newline at end of file diff --git a/appdaemon/settings/apps/test/test.yaml b/appdaemon/settings/apps/test/test.yaml new file mode 100644 index 0000000..be7c2c4 --- /dev/null +++ b/appdaemon/settings/apps/test/test.yaml @@ -0,0 +1,5 @@ +test: + module: test + class: Test + dependencies: + - notify \ No newline at end of file diff --git a/appdaemon/settings/apps/test/test_ac.py b/appdaemon/settings/apps/test/test_ac.py new file mode 100644 index 0000000..10b2340 --- /dev/null +++ b/appdaemon/settings/apps/test/test_ac.py @@ -0,0 +1,37 @@ +import appdaemon.plugins.hass.hassapi as hass +import os +import glob +import json +import time + +""" +Monitor events and output changes to the verbose_log. Nice for debugging purposes. +Arguments: + - events: List of events to monitor +""" +class TestAc(hass.Hass): + notify = None + + def initialize(self) -> None: + self.log('start') + #for i in range(256): + # pkt = "AA AA AA {} 00 00 C8 87 1B 40 55".format(self.int_to_hex(i)) + # self.log(pkt) + # self.call_service("esphome/coffee_philips_5400_send_custom_command_disp", command=pkt) + # time.sleep(2) + + # for file in glob.glob("/conf/apps/test/ir/*.json"): + # f = open(file) + # data = json.load(f) + # if (data['supportedController'] != 'Broadlink'): + # continue + # manufacturer = data['manufacturer'] + # supportedModels = data['supportedModels'][0] + # self.log('file: {}, manufacturer: {}, supportedModels: {}'.format(file, manufacturer, supportedModels)) + # key1 = data['operationModes'][0] + # key2 = data['fanModes'][0] + # minTemp = data['minTemperature']+6 + # off = data['commands']['fan_only'][key2]["{}".format(minTemp)] + # off = data['commands']['off'] + # command = "b64:{}".format(off) + # self.call_service("remote/send_command", entity_id = "remote.rm_mini", command = command) diff --git a/appdaemon/settings/apps/test/test_ac.yaml b/appdaemon/settings/apps/test/test_ac.yaml new file mode 100644 index 0000000..d3f8c3b --- /dev/null +++ b/appdaemon/settings/apps/test/test_ac.yaml @@ -0,0 +1,5 @@ +test_ac: + module: test_ac + class: TestAc + dependencies: + - notify \ 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/Dockerfile b/certbot/docker/Dockerfile new file mode 100644 index 0000000..dbcf865 --- /dev/null +++ b/certbot/docker/Dockerfile @@ -0,0 +1,7 @@ +FROM certbot/certbot:latest + +ADD ./update_certs.sh /update_certs.sh +RUN chmod +x /update_certs.sh + +CMD "" +ENTRYPOINT "/update_certs.sh" diff --git a/certbot/docker/Dockerfile-rpi b/certbot/docker/Dockerfile-rpi new file mode 100644 index 0000000..e021ef1 --- /dev/null +++ b/certbot/docker/Dockerfile-rpi @@ -0,0 +1,7 @@ +FROM certbot/certbot:arm32v6-latest + +ADD ./update_certs.sh /update_certs.sh +RUN chmod +x /update_certs.sh + +CMD "" +ENTRYPOINT "/update_certs.sh" \ No newline at end of file diff --git a/certbot/docker/update_certs.sh b/certbot/docker/update_certs.sh new file mode 100755 index 0000000..bd65e5b --- /dev/null +++ b/certbot/docker/update_certs.sh @@ -0,0 +1,6 @@ +#!/bin/sh +while [ 1 ]; +do + certbot renew --agree-tos --standalone + sleep 1d +done diff --git a/ddns/docker/Dockerfile b/ddns/docker/Dockerfile new file mode 100644 index 0000000..0c1d52a --- /dev/null +++ b/ddns/docker/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:latest as builder +RUN mkdir -p /src && cd /src && git clone https://github.com/thekvs/yandex-ddns . +WORKDIR /src +RUN go get -d +#GOOS=linux GOARCH=amd64 +RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o app . + + +FROM alpine:latest +WORKDIR / +ADD ./run.sh /run.sh +RUN chmod +x /run.sh +COPY --from=builder /src/app . +CMD ["/run.sh"] \ No newline at end of file diff --git a/ddns/docker/run.sh b/ddns/docker/run.sh new file mode 100644 index 0000000..038509b --- /dev/null +++ b/ddns/docker/run.sh @@ -0,0 +1,14 @@ +#!/bin/sh +while true +do + echo "token=$TOKEN" > /yandex-ddns.toml + echo "domain=$DOMAIN" >> /yandex-ddns.toml + if [ -n "$SUBDOMAIN" ]; then + echo "subdomain=$SUBDOMAIN" >> /yandex-ddns.toml + fi + echo "ttl=$TTL" >> /yandex-ddns.toml + chmod 600 /yandex-ddns.toml + #cat /yandex-ddns.toml + ./app + sleep 5m +done diff --git a/ddns/settings/token.env.sample b/ddns/settings/token.env.sample new file mode 100644 index 0000000..90a595a --- /dev/null +++ b/ddns/settings/token.env.sample @@ -0,0 +1,3 @@ +TOKEN=xxxxxxxxx +DOMAIN=xxxxxxxxx +TTL=xxxxxxxxx diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..ab1bc20 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,231 @@ +version: '3' +services: + ddns: + build: + context: ./ddns/docker + env_file: ./ddns/settings/token.env + environment: + - SUBDOMAIN="home-fnv" + restart: always + + mosquitto: + build: ./mosquitto/docker + container_name: mosquitto + env_file: ./mosquitto/settings/mqtt.env + ports: + - "1883:1883" + restart: always + logging: + driver: json-file + options: + max-size: 50m + + postgres: + image: postgres:16.1 + restart: always + volumes: + - ./postgres/settings/data:/var/lib/postgresql/data + env_file: ./postgres/settings/postgres.env + logging: + driver: json-file + options: + max-size: 50m + + hass: + image: "homeassistant/home-assistant:stable" #stable + #image: ghcr.io/home-assistant/home-assistant:stable + container_name: hass + volumes: + - ./hass/settings:/config + - /dev/hass-wolpipe:/dev/hass-wolpipe + - ./secrets/hass-fnv/settings/secrets.yaml:/config/secrets.yaml:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "8123:8123" + # - "29898:9898/udp" + # - "24321:4321/udp" + # - "251827:51827" #Homekit + devices: + - "/dev/ttyUSB0:/dev/ttyUSB0" + depends_on: + - mosquitto + - postgres + #network_mode: "host" + #privileged: true + restart: always + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8123"] + interval: 30s + timeout: 10s + retries: 6 + logging: + driver: json-file + options: + max-size: 50m + + appdaemon: + build: + context: ./appdaemon/docker + container_name: appdaemon + depends_on: + - hass + volumes: + - ./appdaemon/settings:/conf + - ./secrets/hass-fnv/settings/secrets.yaml:/conf/secrets.yaml:ro + - /etc/localtime:/etc/localtime:ro + restart: always + links: + - "hass" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + node-red: + build: + context: ./node-red/docker + #image: nodered/node-red:latest + environment: + - TZ=Europe/Moscow + ports: + - "1880:1880" + depends_on: + - hass + restart: always + volumes: + - ./node-red/settings/data:/data + + esphome: + image: esphome/esphome:latest #2025.6 #esphome-armv7 + container_name: esphome + ports: + - "6052:6052" + volumes: + - ./esphome/settings:/config + - ./secrets/hass-fnv/settings/secrets.yaml:/config/secrets.yaml:ro + #devices: + # - "/dev/ttyUSB0:/dev/ttyUSB0" + #network_mode: host + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + zigbee2mqtt: + image: koenkk/zigbee2mqtt:latest #тут отваливается замок, поэтому не latest + container_name: zigbee2mqtt + ports: + - "8080:8080" + volumes: + - ./zigbee2mqtt/settings:/app/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080"] + interval: 30s + timeout: 10s + retries: 6 + restart: always + logging: + driver: json-file + options: + max-size: 50m + + neptun2mqtt: + build: + context: ./neptun2mqtt/docker + dockerfile: Dockerfile + restart: always + container_name: neptun2mqtt + env_file: ./neptun2mqtt/settings/neptun.env + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + nginx: + image: nginx + hostname: nginx + container_name: nginx + links: + - "hass" + - "certbot" + volumes: + - ./nginx/settings/site-confs:/etc/nginx/conf.d + - ./certbot/settings/data:/config/keys + ports: + - "80:80" + - "443:443" + restart: always + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + certbot: + build: + context: ./certbot/docker + dockerfile: Dockerfile + restart: always + container_name: certbot + volumes: + - ./certbot/docker/update_certs.sh:/update_certs.sh:ro + - ./certbot/settings/data:/etc/letsencrypt + - /etc/localtime:/etc/localtime:ro + ports: + - "8081:80" + #entrypoint: certbot + #command: certonly --standalone --agree-tos --non-interactive --email admin@yandex5.ru -d gitea.yandex5.ru + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + homeconnect2mqtt: + build: + context: ./homeconnect2mqtt/docker + dockerfile: Dockerfile + restart: always + container_name: homeconnect2mqtt + ports: + - "5001:5000" + env_file: ./homeconnect2mqtt/settings/homeconnect2mqtt.env + volumes: + - ./homeconnect2mqtt/settings/config.json:/app/config.json:ro + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + matter-bridge: + image: ghcr.io/t0bst4r/home-assistant-matter-hub:latest + container_name: matter-bridge + ports: + - "8482:8482" + - "5353:5353" + - "5540:5540" + - "5541:5541" + volumes: + - ./matter-bridge/settings/data:/data + - ./secrets/hass-fnv/settings/secrets.yaml:/config/secrets.yaml:ro + env_file: ./matter-bridge/settings/matter-bridge.env + network_mode: host + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + keenetic: + build: + context: ./keenetic/docker + dockerfile: Dockerfile + container_name: keenetic + restart: unless-stopped + ports: + - "8001:8000" + env_file: ./keenetic/settings/keenetic.env diff --git a/docs/appdaemon.md b/docs/appdaemon.md new file mode 100644 index 0000000..47ee2cc --- /dev/null +++ b/docs/appdaemon.md @@ -0,0 +1,176 @@ +# AppDaemon приложения + +## Общая конфигурация + +### Основные файлы +- `appdaemon.yaml` - Основная конфигурация +- `apps.yaml` - Конфигурация приложений +- `globals.py` - Глобальные переменные и функции +- `automation.py` - Базовые функции автоматизации + +### Глобальные модули +- `globals` - Общие функции и переменные +- `automation` - Автоматизация +- `notify` - Уведомления + +## Структура приложений + +### Освещение (light/) + +#### lightcontrol.py +**Функционал**: Основное управление освещением +**Особенности**: +- Автоматическое управление по времени +- Интеграция с датчиками движения +- Сценарии освещения + +#### lightcontrol_kitchenset.py +**Функционал**: Управление освещением кухни +**Особенности**: +- Автоматическое включение при входе +- Режимы освещения для разных задач +- Интеграция с датчиками присутствия + +#### lightcontrol_bath.py +**Функционал**: Управление освещением ванной +**Особенности**: +- Автоматическое включение при входе +- Таймеры для экономии энергии +- Интеграция с датчиками движения + +#### lightcontrol_hall.py +**Функционал**: Управление освещением прихожей +**Особенности**: +- Автоматическое включение при входе в дом +- Режим дежурного освещения +- Интеграция с системой безопасности + +#### light_mainroom.py +**Функционал**: Управление освещением гостиной +**Особенности**: +- Сценарии для просмотра ТВ +- Автоматическое затемнение вечером +- Интеграция с датчиками присутствия + +#### light_balcony.py +**Функционал**: Управление освещением балкона +**Особенности**: +- Автоматическое включение при открытии двери +- Таймеры для экономии энергии +- Интеграция с датчиками движения + +#### lightbutton.py +**Функционал**: Управление кнопками освещения +**Особенности**: +- Обработка нажатий на физические кнопки +- Двойные и длинные нажатия +- Интеграция с системой автоматизации + +### Безопасность (security/) + +#### Система сигнализации +**Функционал**: +- Мониторинг состояния датчиков +- Автоматическая постановка/снятие с охраны +- Интеграция с системой уведомлений + +### Присутствие (presence/) + +#### Датчики присутствия +**Функционал**: +- Определение присутствия в комнатах +- Автоматическое управление освещением +- Интеграция с системой безопасности + +### Замки (locks/) + +#### Управление замками +**Функционал**: +- Автоматическое запирание/отпирание +- Интеграция с системой безопасности +- Уведомления о состоянии замков + +### Домофоны (doorbells/) + +#### Система домофонов +**Функционал**: +- Обработка вызовов домофона +- Автоматические уведомления +- Интеграция с системой безопасности + +### Энергетика (power/) + +#### Мониторинг энергопотребления +**Функционал**: +- Отслеживание потребления электроэнергии +- Автоматическое отключение неиспользуемых устройств +- Анализ энергоэффективности + +### Климат (climate/) + +#### Управление климатом +**Функционал**: +- Автоматическое управление кондиционерами +- Поддержание комфортной температуры +- Энергосберегающие режимы + +### Сенсоры (sensors/) + +#### Обработка данных сенсоров +**Функционал**: +- Агрегация данных с различных сенсоров +- Автоматические уведомления при превышении порогов +- Логирование и анализ данных + +### Группы (groups/) + +#### Управление группами устройств +**Функционал**: +- Группировка устройств по комнатам +- Массовые операции с устройствами +- Сценарии для групп устройств + +### Алиса (alice/) + +#### Интеграция с Яндекс.Алиса +**Функционал**: +- Голосовое управление устройствами +- Обработка голосовых команд +- Интеграция с системой автоматизации + +### Уведомления (notifiers/) + +#### Система уведомлений +**Функционал**: +- Отправка уведомлений в различные каналы +- Автоматические уведомления о событиях +- Настройка приоритетов уведомлений + +## Дашборды + +### Веб-интерфейс +- `dashboards/` - Конфигурация дашбордов +- `www/` - Веб-ресурсы +- `web/` - Веб-приложения + +## Функционал + +### Автоматизация +- **Временные триггеры**: Автоматические действия по расписанию +- **Событийные триггеры**: Реакция на изменения состояния устройств +- **Условная логика**: Сложные сценарии с множественными условиями + +### Интеграции +- **Home Assistant**: Основная интеграция через API +- **MQTT**: Обмен сообщениями с другими сервисами +- **HTTP**: Внешние API и веб-хуки + +### Мониторинг +- **Логирование**: Детальное логирование всех операций +- **Метрики**: Сбор статистики работы приложений +- **Алерты**: Уведомления о критических событиях + +### Безопасность +- **Аутентификация**: Проверка прав доступа +- **Шифрование**: Защита конфиденциальных данных +- **Аудит**: Логирование всех действий пользователей \ No newline at end of file diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000..8362804 --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,109 @@ +# Docker контейнеры и настройки + +## Обзор сервисов + +### Основные сервисы + +#### Home Assistant (hass) +- **Образ**: `homeassistant/home-assistant:stable` +- **Порт**: 8123 +- **Зависимости**: mosquitto, postgres +- **Особенности**: + - Интеграция с USB устройствами (`/dev/ttyUSB0`) + - Health check каждые 30 секунд + - Логирование с ограничением размера 50MB + +#### AppDaemon +- **Сборка**: Кастомный образ из `./appdaemon/docker` +- **Зависимости**: hass +- **Особенности**: + - Автоматизация и скрипты + - Логирование с ограничением 10MB + +#### ESPHome +- **Образ**: `esphome/esphome:2025.6` +- **Порт**: 6052 +- **Функционал**: Управление ESP устройствами + +#### Zigbee2MQTT +- **Образ**: `koenkk/zigbee2mqtt:latest` +- **Порт**: 8080 +- **Особенности**: Health check каждые 30 секунд + +### Инфраструктурные сервисы + +#### Mosquitto (MQTT брокер) +- **Сборка**: Кастомный образ из `./mosquitto/docker` +- **Порт**: 1883 +- **Конфигурация**: `./mosquitto/settings/mqtt.env` + +#### PostgreSQL +- **Образ**: `postgres:16.1` +- **Данные**: `./postgres/settings/data` +- **Конфигурация**: `./postgres/settings/postgres.env` + +#### Nginx +- **Образ**: `nginx` +- **Порты**: 80, 443 +- **SSL**: Интеграция с certbot +- **Конфигурация**: `./nginx/settings/site-confs` + +#### Certbot +- **Сборка**: Кастомный образ из `./certbot/docker` +- **Порт**: 8081 +- **Функционал**: Автоматическое обновление SSL сертификатов + +### Специализированные сервисы + +#### Matter Bridge +- **Образ**: `ghcr.io/t0bst4r/home-assistant-matter-hub:latest` +- **Порты**: 8482, 5353, 5540, 5541 +- **Режим сети**: host +- **Интеграция**: Home Assistant через API токен + +#### HomeConnect2MQTT +- **Сборка**: Кастомный образ из `./homeconnect2mqtt/docker` +- **Порт**: 5001 +- **Функционал**: Интеграция бытовой техники Bosch/Siemens + +#### Neptun2MQTT +- **Сборка**: Кастомный образ из `./neptun2mqtt/docker` +- **Конфигурация**: `./neptun2mqtt/settings/neptun.env` + +#### DDNS +- **Сборка**: Кастомный образ из `./ddns/docker` +- **Конфигурация**: `./ddns/settings/token.env` +- **Субдомен**: home-fnv + +#### Node-RED +- **Сборка**: Кастомный образ из `./node-red/docker` +- **Порт**: 1880 +- **Зависимости**: hass +- **Функционал**: Визуальное программирование автоматизации + +## Переменные окружения + +### Общие настройки +- **TZ**: Europe/Moscow (для Node-RED) +- **Логирование**: JSON формат с ограничением размера файлов + +### Специфичные настройки +- **HC_MQTT_HOST**: 192.168.1.6 (HomeConnect) +- **HC_MQTT_USER**: hass +- **HC_MQTT_PASSWORD**: mqttpass +- **HAMH_HOME_ASSISTANT_URL**: http://192.168.1.6:8123/ +- **HAMH_LOG_LEVEL**: info + +## Мониторинг и логирование + +Все сервисы используют JSON логирование с ограничением размера: +- **Основные сервисы**: 50MB +- **Вспомогательные**: 10MB +- **Health checks**: Для hass и zigbee2mqtt + +## Сеть и безопасность + +- **Привилегированный режим**: Отключен для большинства сервисов +- **Network mode**: host только для matter-bridge +- **Порты**: Минимально необходимые для каждого сервиса +- **SSL**: Автоматическое управление через certbot \ No newline at end of file diff --git a/docs/home-assistant.md b/docs/home-assistant.md new file mode 100644 index 0000000..abe149c --- /dev/null +++ b/docs/home-assistant.md @@ -0,0 +1,150 @@ +# Home Assistant конфигурация + +## Общая структура + +### Основные файлы +- `configuration.yaml` - Основная конфигурация +- `automations.yaml` - Автоматизации +- `scripts.yaml` - Скрипты +- `scenes.yaml` - Сцены +- `ui-lovelace.yaml` - Пользовательский интерфейс + +### Директории +- `packages/` - Пакеты конфигурации по категориям +- `custom_components/` - Пользовательские компоненты +- `blueprints/` - Шаблоны автоматизаций +- `www/` - Веб-ресурсы + +## Устройства в packages + +### Кофемашина (coffee_maker.yaml) +**Производитель**: Philips +**Модель**: 5400 Series + +#### Сенсоры +- `coffee_machine_power_status` - Статус питания +- `coffee_philips_5400_status` - Общий статус +- `coffee_philips_5400_coffee_grounds_container` - Контейнер кофейной гущи +- `coffee_philips_5400_coffee_pallet` - Поддон +- `coffee_philips_5400_grain_tray` - Кофейные зерна в лотке +- `coffee_philips_5400_system_status` - Системный статус +- `coffee_philips_5400_making_coffee` - Приготовление кофе +- `coffee_philips_5400_internal_temperature` - Температура ESP32 +- `coffee_philips_5400_error_code` - Системные ошибки +- `coffee_philips_5400_free_mem_size` - Размер свободной памяти + +#### Управление +- `select.coffee_philips_5400_drink` - Выбор кофейных напитков +- `select.coffee_philips_5400_cups` - Количество порций +- `select.coffee_philips_5400_grind` - Крепость (зерно/молотый) +- `number.coffee_philips_5400_soffee` - Объем кофе +- `number.coffee_philips_5400_milk` - Объем молока +- `button.coffee_philips_5400_prepare` - Показать рецепт +- `button.coffee_philips_5400_build_coffee` - Приготовить кофе + +#### Автоматизация +- `input_boolean.kitchen_coffee_machine_auto_coffee_making` - Авто приготовление +- `counter.coffee_machine_making_coffee` - Счетчик авто приготовления + +### Посудомоечная машина (dishwasher.yaml) +**Интеграция**: HomeConnect через MQTT + +#### Сенсоры +- `hc_dishwasher_state` - Статус работы +- `hc_dishwasher_remaining` - Время до конца программы +- `hc_dishwasher_running` - Признак работы + +#### Бинарные сенсоры +- `hc_dishwasher_door` - Состояние дверцы +- `hc_dishwasher_power` - Статус питания + +#### Счетчики +- `dishwasher_door_open_count` - Счетчик открытий дверцы + +### Система отопления (climate/) +**Тип**: Кондиционеры и управление температурой + +### Освещение (light/) +**Тип**: Управление освещением по комнатам + +### Безопасность (alarm_and_presence/) +**Тип**: Система сигнализации и присутствия + +### Уведомления (notifications/) +**Тип**: Система уведомлений + +### Энергетика (power/) +**Тип**: Мониторинг энергопотребления + +### Телевизоры (tv/) +**Тип**: Управление телевизорами + +### Водоснабжение (watercontrol.yaml) +**Тип**: Контроль водоснабжения + +### Замок (aqara_lock.yaml) +**Производитель**: Aqara +**Тип**: Умный замок + +### Счетчик тепла (heat_counter.yaml) +**Тип**: Счетчик тепловой энергии + +## ESPHome устройства + +### Кондиционеры +- `ac_mainroom.yaml` - Гостиная +- `ac_kitchen.yaml` - Кухня +- `ac_childroom.yaml` - Детская +- `ac_bedroom.yaml` - Спальня + +### Освещение +- `svetlana_bedside_lamp.yaml` - Прикроватная лампа Светланы +- `danil_bedside_lamp.yaml` - Прикроватная лампа Данила + +### Датчики присутствия +- `presence_bedroom.yaml` - Спальня +- `presence_kladovka.yaml` - Кладовка + +### Специализированные устройства +- `coffee-philips-5400.yaml` - Кофемашина Philips +- `senseair.yaml` - Датчик качества воздуха + +## Интеграции + +### MQTT +- **Брокер**: Mosquitto (порт 1883) +- **Топики**: homeconnect/*, neptun/* + +### Zigbee +- **Координатор**: Zigbee2MQTT +- **Порт**: 8080 +- **Устройства**: Датчики, выключатели, замки + +### Matter +- **Мост**: Matter Bridge +- **Порты**: 8482, 5353, 5540, 5541 +- **Режим**: host + +### HomeConnect +- **Интеграция**: HomeConnect2MQTT +- **Порт**: 5001 +- **Устройства**: Посудомойка, стиральная машина + +### Neptun +- **Интеграция**: Neptun2MQTT +- **Функционал**: Контроль водоснабжения + +## Автоматизации + +### Основные категории +- Управление освещением +- Контроль климата +- Безопасность и присутствие +- Уведомления +- Энергосбережение + +### Примеры автоматизаций +- Автоматическое приготовление кофе +- Управление освещением по присутствию +- Контроль температуры в комнатах +- Мониторинг состояния бытовой техники \ No newline at end of file diff --git a/esphome/settings/.gitignore b/esphome/settings/.gitignore new file mode 100644 index 0000000..0bbf89c --- /dev/null +++ b/esphome/settings/.gitignore @@ -0,0 +1,10 @@ +# Gitignore settings for ESPHome +# This is an example and may include too much for your use-case. +# You can modify this file to suit your needs. +/.esphome/ +**/.pioenvs/ +**/.piolibdeps/ +**/lib/ +**/src/ +**/platformio.ini +/secrets.yaml diff --git a/esphome/settings/ac_bedroom.yaml b/esphome/settings/ac_bedroom.yaml new file mode 100644 index 0000000..34b82fc --- /dev/null +++ b/esphome/settings/ac_bedroom.yaml @@ -0,0 +1,67 @@ +substitutions: +# Уникальное имя этой конфигурации, стоит использовать только латинские буквы и цифры, никаких пробелов или +# спецсимволов, например, "tclac". Если будет несколько модулей кондиционера, то device_name надо изменить, +# например, добавляя цифры: tclac1, tclac2, tclac3... + device_name: ac_bedroom +# Человеческое имя для кондиционера, для интерфейса: + humanly_name: "Кондиционер Спальня" +# Настройки подключения к WiFI, данные могут храниться в файле "secret" в ESPHome, если нет- редактируем: +# Имя точки доступа WiFI: + wifi_ssid: !secret wifi_24_name +# Пароль точки доступа WiFI: + wifi_password: !secret wifi_24_pass +# Пароль для точки доступа для восстановления, имя точки доступа будет: "{device_name} Fallback Hotspot" + recovery_pass: !secret esphome_captive_pass +# Пароль для обновления по воздуху, генерируется один раз и потом используется всегда на автомате: + ota_pass: !secret api_encryption_key +# Ключ подключения к Home Assistant API, новый ключ можно получить здесь: https://esphome.io/components/api.html: + api_key: !secret api_encryption_key +# Выводы модуля, к которым подключен UART: + uart_rx: GPIO3 + uart_tx: GPIO1 +# Выводы модуля для подключения сигнальных светодиодов, для работы надо раскомментировать "leds" в "packages" +# иначе работать не будет совсем и можно писать сюда вообще что угодною. Для тех, кто просил их удалить. + receive_led: GPIO6 + transmit_led: GPIO4 + device_host: ac-bedroom.lan + +# Подгружаемые файлы- загружаются и обновляются автоматически из интернета, раскомментировать нужные +# Описание опций: +# - packages/leds.yaml - светодиоды для индикации приема/передачи, пины указываются в receive_led / transmit_led +# +# Выравнивать раскомментированные строки обязательно по указанной позиции! +packages: + remote_package: + url: https://github.com/I-am-nightingale/tclac.git + ref: master + files: + - packages/core.yaml # Ядро всего сущего + # - packages/leds.yaml + refresh: 30s + +wifi: + use_address: ${device_host} + +esp8266: + board: d1_mini + +web_server: + port: 80 + auth: + username: admin + password: superLongP4ss + +sensor: + - platform: template + name: "Текущая точная температура" + id: current_temperature_precise + unit_of_measurement: "°C" + accuracy_decimals: 2 # Настраиваем точность до сотых + update_interval: 30s # Интервал обновления, можно настроить по необходимости + lambda: |- + // Возвращаем значение переменной current_temperature из компонента tclacClimate + return id(${device_name}climate).current_temperature; + +button: + - platform: restart + name: "Restart" \ No newline at end of file diff --git a/esphome/settings/ac_childroom.yaml b/esphome/settings/ac_childroom.yaml new file mode 100644 index 0000000..5461aea --- /dev/null +++ b/esphome/settings/ac_childroom.yaml @@ -0,0 +1,67 @@ +substitutions: +# Уникальное имя этой конфигурации, стоит использовать только латинские буквы и цифры, никаких пробелов или +# спецсимволов, например, "tclac". Если будет несколько модулей кондиционера, то device_name надо изменить, +# например, добавляя цифры: tclac1, tclac2, tclac3... + device_name: ac_childroom +# Человеческое имя для кондиционера, для интерфейса: + humanly_name: "Кондиционер Детская" +# Настройки подключения к WiFI, данные могут храниться в файле "secret" в ESPHome, если нет- редактируем: +# Имя точки доступа WiFI: + wifi_ssid: !secret wifi_24_name +# Пароль точки доступа WiFI: + wifi_password: !secret wifi_24_pass +# Пароль для точки доступа для восстановления, имя точки доступа будет: "{device_name} Fallback Hotspot" + recovery_pass: !secret esphome_captive_pass +# Пароль для обновления по воздуху, генерируется один раз и потом используется всегда на автомате: + ota_pass: !secret api_encryption_key +# Ключ подключения к Home Assistant API, новый ключ можно получить здесь: https://esphome.io/components/api.html: + api_key: !secret api_encryption_key +# Выводы модуля, к которым подключен UART: + uart_rx: GPIO3 + uart_tx: GPIO1 +# Выводы модуля для подключения сигнальных светодиодов, для работы надо раскомментировать "leds" в "packages" +# иначе работать не будет совсем и можно писать сюда вообще что угодною. Для тех, кто просил их удалить. + receive_led: GPIO6 + transmit_led: GPIO4 + device_host: ac-childroom.lan + +# Подгружаемые файлы- загружаются и обновляются автоматически из интернета, раскомментировать нужные +# Описание опций: +# - packages/leds.yaml - светодиоды для индикации приема/передачи, пины указываются в receive_led / transmit_led +# +# Выравнивать раскомментированные строки обязательно по указанной позиции! +packages: + remote_package: + url: https://github.com/I-am-nightingale/tclac.git + ref: master + files: + - packages/core.yaml # Ядро всего сущего + # - packages/leds.yaml + refresh: 30s + +esp8266: + board: d1_mini + +wifi: + use_address: ${device_host} + +web_server: + port: 80 + auth: + username: admin + password: superLongP4ss + +sensor: + - platform: template + name: "Текущая точная температура" + id: current_temperature_precise + unit_of_measurement: "°C" + accuracy_decimals: 2 # Настраиваем точность до сотых + update_interval: 30s # Интервал обновления, можно настроить по необходимости + lambda: |- + // Возвращаем значение переменной current_temperature из компонента tclacClimate + return id(${device_name}climate).current_temperature; + +button: + - platform: restart + name: "Restart" \ No newline at end of file diff --git a/esphome/settings/ac_kitchen.yaml b/esphome/settings/ac_kitchen.yaml new file mode 100644 index 0000000..4415a18 --- /dev/null +++ b/esphome/settings/ac_kitchen.yaml @@ -0,0 +1,88 @@ +#=============================================================================== +# Контроллер кондиционера в гостиной +# Wemos D1 mini +#=============================================================================== +substitutions: + node_name: ac_kitchen # Use a unique name. + hostname: ac-kitchen.lan + friendly_node_name: "Кондиционер Кухня" + +#=============================================================================== +# Общий конфиг +#=============================================================================== +esphome: + name: ${node_name} + friendly_name: ${friendly_node_name} + +esp8266: + board: esp12e + +web_server: + port: 80 + auth: + username: admin + password: superLongP4ss + +# Enable Home Assistant API +api: + encryption: + key: !secret api_encryption_key + +ota: + - platform: esphome + password: !secret api_encryption_key + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + use_address: 192.168.1.127 #${hostname} + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "${node_name} Fallback Hotspot" + password: !secret esphome_captive_pass + +captive_portal: + +# Enable logging +logger: + baud_rate: 0 # disable LOG output on UART as UART is used for Sinclair AC unit +# level: VERBOSE + +uart: + tx_pin: 1 + rx_pin: 3 + baud_rate: 4800 + parity: EVEN + +time: + - platform: sntp + +external_components: + - source: github://gekkehenkie11/esphome_gree_ac + components: [sinclair_ac] + refresh: 0s + +climate: + - platform: sinclair_ac + name: ${node_name} + horizontal_swing_select: + name: ${node_name} Horizontal Swing Mode + vertical_swing_select: + name: ${node_name} Vertical Swing Mode + display_select: + name: ${node_name} Display Mode + display_unit_select: + name: ${node_name} Display Unit + plasma_switch: + name: ${node_name} Plasma + sleep_switch: + name: ${node_name} Sleep + xfan_switch: + name: ${node_name} X-fan + save_switch: + name: ${node_name} Save/8 Heat + +button: + - platform: restart + name: "Restart" \ No newline at end of file diff --git a/esphome/settings/ac_mainroom.yaml b/esphome/settings/ac_mainroom.yaml new file mode 100644 index 0000000..e4cb508 --- /dev/null +++ b/esphome/settings/ac_mainroom.yaml @@ -0,0 +1,91 @@ +#=============================================================================== +# Контроллер кондиционера в гостиной +# Wemos D1 mini +#=============================================================================== +substitutions: + node_name: ac_mainroom # Use a unique name. + node_id: ac_mainroom # Use a unique id. + deviceid: ac_mainroom + devicename: ac_mainroom + friendly_node_name: "Кондиционер Гостиная" + device_host: ac-mainroom.lan + +#=============================================================================== +# Общий конфиг +#=============================================================================== +esphome: + name: ${node_name} + friendly_name: ${friendly_node_name} + +esp8266: + board: esp12e + +web_server: + port: 80 + auth: + username: admin + password: !secret wifi_24_pass + +# Enable Home Assistant API +api: + encryption: + key: !secret api_encryption_key + +ota: + - platform: esphome + password: !secret api_encryption_key + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + use_address: ${device_host} + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "${devicename} Fallback Hotspot" + password: !secret esphome_captive_pass + +captive_portal: + +# Enable logging +logger: + baud_rate: 0 # disable LOG output on UART as UART is used for Sinclair AC unit +# level: VERBOSE + +uart: + tx_pin: 1 + rx_pin: 3 + baud_rate: 4800 + parity: EVEN + +time: + - platform: sntp + +external_components: + - source: github://gekkehenkie11/esphome_gree_ac + components: [sinclair_ac] + refresh: 0s + +climate: + - platform: sinclair_ac + name: ${devicename} + horizontal_swing_select: + name: ${devicename} Horizontal Swing Mode + vertical_swing_select: + name: ${devicename} Vertical Swing Mode + display_select: + name: ${devicename} Display Mode + display_unit_select: + name: ${devicename} Display Unit + plasma_switch: + name: ${devicename} Plasma + sleep_switch: + name: ${devicename} Sleep + xfan_switch: + name: ${devicename} X-fan + save_switch: + name: ${devicename} Save/8 Heat + +button: + - platform: restart + name: "Restart" \ No newline at end of file diff --git a/esphome/settings/bkp/ac_bedroom.yaml b/esphome/settings/bkp/ac_bedroom.yaml new file mode 100644 index 0000000..c58b972 --- /dev/null +++ b/esphome/settings/bkp/ac_bedroom.yaml @@ -0,0 +1,116 @@ +#=============================================================================== +# Контроллер кондиционера в гостиной +# Wemos D1 mini +#=============================================================================== +substitutions: + node_name: ac_bedroom # Use a unique name. + node_id: ac_bedroom # Use a unique id. + deviceid: ac_bedroom + devicename: ac_bedroom + friendly_node_name: "Кондиционер Спальня" + +#=============================================================================== +# Общий конфиг +#=============================================================================== +esphome: + name: ${node_name} + friendly_name: ${friendly_node_name} + includes: + - ac_tcl_climate.h + +esp8266: + board: d1_mini + +external_components: + - source: + type: git + url: https://github.com/robertklep/esphome-custom-component + components: [ custom, custom_component ] + +web_server: + port: 80 + auth: + username: admin + password: superLongP4ss + +# Enable Home Assistant API +api: + encryption: + key: !secret api_encryption_key + +ota: + - platform: esphome + password: !secret api_encryption_key + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "${devicename} Fallback Hotspot" + password: !secret esphome_captive_pass + +captive_portal: + +uart: + id: uart_bus + tx_pin: 1 + rx_pin: 3 + baud_rate: 9600 + parity: EVEN + +logger: + #level: VERBOSE + baud_rate: 0 + +climate: +- platform: custom + lambda: |- + auto my_custom_climate = new MyCustomClimate(id(uart_bus)); + App.register_component(my_custom_climate); + return {my_custom_climate}; + + climates: + - name: "TCL a/c" + id: my_custom_climate + +select: + - platform: template + name: Vertical swing + id: vswing + entity_category: config + options: + - "Last position" + - "Fix top" + - "Fix upper" + - "Fix mid" + - "Fix lower" + - "Fix bottom" + - "Move full" + - "Move upper" + - "Move lower" + set_action: + then: + lambda: |- + static_cast(id(my_custom_climate))->control_vertical_swing(x); + + - platform: template + name: Horizontal swing + id: hswing + entity_category: config + options: + - "Last position" + - "Fix left" + - "Fix mid left" + - "Fix mid" + - "Fix mid right" + - "Fix right" + - "Move full" + - "Move left" + - "Move mid" + - "Move right" + set_action: + then: + lambda: |- + static_cast(id(my_custom_climate))->control_horizontal_swing(x); \ No newline at end of file diff --git a/esphome/settings/bkp/ac_childroom.yaml b/esphome/settings/bkp/ac_childroom.yaml new file mode 100644 index 0000000..34e6ff0 --- /dev/null +++ b/esphome/settings/bkp/ac_childroom.yaml @@ -0,0 +1,117 @@ +#=============================================================================== +# Контроллер кондиционера в гостиной +# Wemos D1 mini +#=============================================================================== +substitutions: + node_name: ac_childroom # Use a unique name. + node_id: ac_childroom # Use a unique id. + deviceid: ac_childroom + devicename: ac_childroom + friendly_node_name: "Кондиционер Детская" + +#=============================================================================== +# Общий конфиг +#=============================================================================== +esphome: + name: ${node_name} + friendly_name: ${friendly_node_name} + includes: + - ac_tcl_climate.h + +esp8266: + board: d1_mini + +external_components: + - source: + type: git + url: https://github.com/robertklep/esphome-custom-component + components: [ custom, custom_component ] + +web_server: + port: 80 + auth: + username: admin + password: superLongP4ss + +# Enable Home Assistant API +api: + encryption: + key: !secret api_encryption_key + +ota: + - platform: esphome + password: !secret api_encryption_key + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + use_address: 192.168.1.188 + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "${devicename} Fallback Hotspot" + password: !secret esphome_captive_pass + +captive_portal: + +uart: + id: uart_bus + tx_pin: 1 + rx_pin: 3 + baud_rate: 9600 + parity: EVEN + +logger: + level: VERBOSE + baud_rate: 0 + +climate: +- platform: custom + lambda: |- + auto my_custom_climate = new MyCustomClimate(id(uart_bus)); + App.register_component(my_custom_climate); + return {my_custom_climate}; + + climates: + - name: "TCL a/c" + id: my_custom_climate + +select: + - platform: template + name: Vertical swing + id: vswing + entity_category: config + options: + - "Last position" + - "Fix top" + - "Fix upper" + - "Fix mid" + - "Fix lower" + - "Fix bottom" + - "Move full" + - "Move upper" + - "Move lower" + set_action: + then: + lambda: |- + static_cast(id(my_custom_climate))->control_vertical_swing(x); + + - platform: template + name: Horizontal swing + id: hswing + entity_category: config + options: + - "Last position" + - "Fix left" + - "Fix mid left" + - "Fix mid" + - "Fix mid right" + - "Fix right" + - "Move full" + - "Move left" + - "Move mid" + - "Move right" + set_action: + then: + lambda: |- + static_cast(id(my_custom_climate))->control_horizontal_swing(x); \ No newline at end of file diff --git a/esphome/settings/coffee-philips-5400.yaml b/esphome/settings/coffee-philips-5400.yaml new file mode 100644 index 0000000..8d7e0f3 --- /dev/null +++ b/esphome/settings/coffee-philips-5400.yaml @@ -0,0 +1,190 @@ +sensor: [] +substitutions: + board_name: Coffee Philips 5400 + device_name: coffee-philips-5400 + +esphome: + name: ${device_name} + friendly_name: ${board_name} + comment: ${board_name} + +esp32: + board: esp32dev + framework: + type: arduino + +# Enable logging +#Журналирование +logger: + level: INFO #NONE + baud_rate: 0 + +# Enable Home Assistant API +api: + +web_server: + port: 80 + +ota: + platform: esphome + password: "S/6VwLrSJ+vRfPKWYzbg+iasPO6h3sXm3jY9Ko1YCOo=" + +#Учетные данные Wi-Fi для подключения платы к домашней сети +wifi: + ssid: "MonsterFNV" + password: "7065485qwe" + use_address: ${device_name}.lan + +#Если не будет связи с WiFi, то поднимется точка доступа + ap: + ssid: ESP SmartCoffeePhilips + password: ap_esp_password + +uart: +#UART подключение к панели управления. Считываем данные которые приходят от материнской плате в панель управления + - id: uart_display + rx_pin: GPIO16 #Считываем данные с панели управления + tx_pin: GPIO17 #Посылаем данные в панель управления + baud_rate: 115200 + stop_bits: 1 + data_bits: 8 + parity: NONE + rx_buffer_size: 256 + + - id: uart_mainboard + rx_pin: GPIO14 #Считываем данные с материнской платы + tx_pin: GPIO5 #Посылаем данные в материнскую плату + baud_rate: 115200 + stop_bits: 1 + data_bits: 8 + parity: NONE + rx_buffer_size: 256 + +external_components: + - source: + type: local + path: ./components + +saeco_series_6000: + id: saeco_series_6000_component + uart_display: uart_display + uart_mainboard: uart_mainboard + debug: true + state_sensor: + name: Work State + id: idControlSensor + internal: True #Скрыть - True \показать - False + on_value: + then: + - lambda: |- + //Статус состояния кофемашины, включена или выключена + if(x==2){ + id(idPowerStatus).publish_state(false); + } else if(id(idPowerStatus).state==false){ + id(idPowerStatus).publish_state(true); + } + + //AA:AA:AA:B0 "Системные сенсоры" + if(x==21){ + id(idStatus).publish_state("Выберите напиток"); + id(idWater).publish_state("Вода есть"); + id(idPallet).publish_state("Вставлен"); + id(idGrainTray).publish_state("Зерна есть"); + id(idCoffeeGroundsContainer).publish_state("Пустой"); + } + + //AA:AA:AA:B0 "Статусы" + else if(x==1){id(idStatus).publish_state("Что-то (07 01 00)");} + else if(x==2){id(idStatus).publish_state("Выключено");} + else if(x==3){id(idStatus).publish_state("Наливаем молоко");} + else if(x==4){id(idStatus).publish_state("Наливаем кофе");} + else if(x==5){id(idStatus).publish_state("Предварительное дозирование");} + else if(x==6){id(idStatus).publish_state("Создание пара для молока");} + else if(x==7){id(idStatus).publish_state("Заварочный узел в положение заваривания");} + else if(x==8){id(idStatus).publish_state("Наслаждайтесь");} + else if(x==9){id(idStatus).publish_state("Нагрев воды");} + else if(x==10){id(idStatus).publish_state("Перемалываем зерна");} + else if(x==11){id(idStatus).publish_state("Промывка");} + else if(x==12){id(idGrainTray).publish_state("Зерна закончились");} + else if(x==13){id(idStatus).publish_state("Что-то (07 08 14)");} + else if(x==14){id(idStatus).publish_state("Нагревание");} + else if(x==15){id(idStatus).publish_state("Наслаждайтесь");} + else if(x==16){id(idStatus).publish_state("Что-то (07 0C 02)");} + else if(x==17){id(idWater).publish_state("Вода есть");} + else if(x==18){id(idPallet).publish_state("Вставлен");} + else if(x==20){id(idCoffeeGroundsContainer).publish_state("Опорож. контейнер для коф. гущи");} + else if(x==22){id(idWater).publish_state("Воды нет");} + else if(x==23){id(idPallet).publish_state("Извлечен");} + else if(x==24){id(idPallet).publish_state("Очистка от накипи. Шаг 1");} + else if(x==25){id(idPallet).publish_state("Очистка от накипи. Шаг 2");} + + + //AA:AA:AA:B5 "Error Code" + else if(x==30){id(idErrorCode).publish_state("00");} + else if(x==31){id(idErrorCode).publish_state("0B");} + else if(x==32){id(idErrorCode).publish_state("E6");} + else if(x==33){id(idErrorCode).publish_state("80");} + else if(x==34){id(idErrorCode).publish_state("CB");} + else if(x==35){id(idErrorCode).publish_state("FF");} + else if(x==36){id(idErrorCode).publish_state("A0");} + +##################################################################################### +################################## Бинарный сенсор ################################## +#Статус кофемашины: включена или выключена +binary_sensor: + - platform: template + name: "Power Status" + id: idPowerStatus + icon: mdi:power + +##################################################################################### +################################### Текстовый сенсор ################################ + +text_sensor: + - platform: template + name: "Status" + id: idStatus + icon: mdi:coffee-to-go + update_interval: never + - platform: template + name: "Water" + id: idWater + icon: mdi:cup-water + update_interval: never + - platform: template + name: "Coffee Pallet" + id: idPallet + icon: mdi:spirit-level + update_interval: never + - platform: template + name: "Grain Tray" + id: idGrainTray + icon: mdi:grain + update_interval: never + - platform: template + name: "Coffee Grounds Container" + id: idCoffeeGroundsContainer + icon: mdi:train-car-centerbeam-full + update_interval: never + - platform: template + name: "Error Code" + id: idErrorCode + icon: mdi:alert-circle-outline + update_interval: never + +button: + - platform: template + name: "Американо" + on_press: + then: + - lambda: |- + id(saeco_series_6000_component)->send_packets_to_mainboard( + std::vector{ + "AA AA AA 93 0E 01 01 8B BD 33 EA 55", + "AA AA AA 90 0F 0A 00 02 00 03 02 00 B4 00 00 00 67 48 E4 4B 55", + "AA AA AA 91 10 01 03 56 9A 8C B8 55" + }, 50); + +#[20:45:34][I][saeco_series_6000:079]: Packet from Display: AA AA AA 93 0A 01 01 57 15 3A ED 55 +#[20:45:34][I][saeco_series_6000:079]: Packet from Display: AA AA AA 90 0B 0A 05 03 00 02 00 00 64 00 00 00 39 BF 5B 74 55 +#[20:45:35][I][saeco_series_6000:079]: Packet from Display: AA AA AA 91 0C 01 03 42 C0 B0 AD 55" \ No newline at end of file diff --git a/esphome/settings/components/saeco_series_6000/__init__.py b/esphome/settings/components/saeco_series_6000/__init__.py new file mode 100644 index 0000000..dcb1e69 --- /dev/null +++ b/esphome/settings/components/saeco_series_6000/__init__.py @@ -0,0 +1,98 @@ +DEPENDENCIES = ["uart", "sensor"] + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart, sensor, button +from esphome.const import CONF_ID, CONF_DEBUG +import esphome.automation as automation + +CONF_STATE_SENSOR = "state_sensor" +CONF_B0_STATUS_SENSOR = "b0_status_sensor" +CONF_B5_STATUS_SENSOR = "b5_status_sensor" +CONF_BA_STATUS_SENSOR = "ba_status_sensor" +CONF_S90_STATUS_SENSOR = "s90_status_sensor" +CONF_S91_STATUS_SENSOR = "s91_status_sensor" +CONF_S93_STATUS_SENSOR = "s93_status_sensor" +CONF_WATER_SENSOR = "water_sensor" +CONF_COFFEE_GROUNDS_CONTAINER_SENSOR = "coffee_grounds_container_sensor" +CONF_PALLET_SENSOR = "pallet_sensor" +CONF_ERROR_CODE_SENSOR = "error_code_sensor" +CONF_GRAIN_TRAY_SENSOR = "grain_tray_sensor" +CONF_STATUS_TEXT_SENSOR = "status_text_sensor" +CONF_PACKETS = "packets" +CONF_SAECO_ID = "saeco_id" + +saeco_series_6000_ns = cg.esphome_ns.namespace('saeco_series_6000') +SaecoSeries6000 = saeco_series_6000_ns.class_('SaecoSeries6000', cg.Component) + +CONFIG_SCHEMA = cv.Schema({ + cv.Required(cv.CONF_ID): cv.declare_id(SaecoSeries6000), + cv.Required('uart_display'): cv.use_id(uart.UARTComponent), + cv.Required('uart_mainboard'): cv.use_id(uart.UARTComponent), + cv.Optional(CONF_DEBUG, default=False): cv.boolean, + cv.Optional(CONF_STATE_SENSOR): sensor.sensor_schema(), +}).extend(cv.COMPONENT_SCHEMA) + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + + uart_display = yield cg.get_variable(config['uart_display']) + cg.add(var.set_uart_display(uart_display)) + + uart_mainboard = yield cg.get_variable(config['uart_mainboard']) + cg.add(var.set_uart_mainboard(uart_mainboard)) + + if config[CONF_DEBUG]: + cg.add(var.set_debug(True)) + + if CONF_STATE_SENSOR in config: + sens = yield sensor.new_sensor(config[CONF_STATE_SENSOR]) + cg.add(var.set_status_sensor(sens)) + +def validate_packet(value): + if isinstance(value, str): + return [int(x, 16) for x in value.split()] + return [cv.hex_uint8_t(x) for x in value] + +SEND_PACKETS_ACTION_SCHEMA = cv.Schema({ + cv.Required(CONF_ID): cv.use_id(SaecoSeries6000), + cv.Required(CONF_PACKETS): [cv.All(validate_packet)], + cv.Optional("delay_ms", default=10): cv.positive_int, +}) + +SendPacketsAction = saeco_series_6000_ns.class_("SendPacketsAction", automation.Action) + +@automation.register_action( + "saeco_series_6000.send_packets", + SendPacketsAction, + SEND_PACKETS_ACTION_SCHEMA, +) +def send_packets_to_mainboard_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id) + parent = yield cg.get_variable(config[CONF_ID]) + var.set_parent(parent) + var.set_packets(config[CONF_PACKETS]) + var.set_delay_ms(config.get("delay_ms", 10)) + return var + +SaecoSendPacketsButton = saeco_series_6000_ns.class_("SaecoSendPacketsButton", button.Button, cg.Component) + +SAECO_SEND_PACKETS_BUTTON_SCHEMA = button.BUTTON_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(SaecoSendPacketsButton), + cv.Required(CONF_ID): cv.use_id(SaecoSeries6000), + cv.Required(CONF_PACKETS): [cv.All(validate_packet)], + cv.Optional("delay_ms", default=10): cv.positive_int, +}).extend(cv.COMPONENT_SCHEMA) + +async def saeco_send_packets_button_to_code(config): + parent = await cg.get_variable(config[CONF_SAECO_ID]) + var = cg.new_Pvariable(config["id"]) + await cg.register_component(var, config) + await button.register_button(var, config) + cg.add(var.set_parent(parent)) + cg.add(var.set_packets(config[CONF_PACKETS])) + cg.add(var.set_delay_ms(config.get("delay_ms", 10))) + return var + +button.register_button("saeco_series_6000", saeco_send_packets_button_to_code) \ No newline at end of file diff --git a/esphome/settings/components/saeco_series_6000/saeco_series_6000.cpp b/esphome/settings/components/saeco_series_6000/saeco_series_6000.cpp new file mode 100644 index 0000000..de9bfb9 --- /dev/null +++ b/esphome/settings/components/saeco_series_6000/saeco_series_6000.cpp @@ -0,0 +1,343 @@ +#include "saeco_series_6000.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace saeco_series_6000 { + +static const char *TAG = "saeco_series_6000"; +static uint32_t last_activity_time = 0; + +void SaecoSeries6000::loop() { + uint8_t byte; + + // Bridge from uart_display (display) to uart_mainboard (mainboard) + while (uart_display_->available()) { + uart_display_->read_byte(&byte); + uart_mainboard_->write_byte(byte); // Mirror byte immediately + process_display_byte(byte); // Process the "sniffed" byte + } + + // Bridge from uart_mainboard (mainboard) to uart_display (display) + while (uart_mainboard_->available()) { + uart_mainboard_->read_byte(&byte); + uart_display_->write_byte(byte); // Mirror byte immediately + process_mainboard_byte(byte); // Process the "sniffed" byte + } + // Контроль активности UART (выключение через 15 сек) + static uint32_t last_check = 0; + uint32_t now = millis(); + if (now - last_check > 1000) { // проверяем каждую секунду + last_check = now; + if (now - last_activity_time > 15000) { + pub_status(2); // публикуем статус "выключено" + last_activity_time = now; // чтобы не публиковать повторно + } + } +} + +void SaecoSeries6000::process_display_byte(uint8_t byte) { + // Если получили первый 0xAA — начинаем новый пакет + if (byte == 0xAA && display_buffer_pos_ == 0) { + display_buffer_[display_buffer_pos_++] = byte; + return; + } + // Если уже начали собирать пакет + if (display_buffer_pos_ > 0 && display_buffer_pos_ < DISPLAY_BUFFER_SIZE) { + display_buffer_[display_buffer_pos_++] = byte; + if (byte == 0x55) { + parse_display_packet(display_buffer_, display_buffer_pos_); + display_buffer_pos_ = 0; + } + } +} + +void SaecoSeries6000::process_mainboard_byte(uint8_t byte) { + // Если получили первый 0xAA — начинаем новый пакет + if (byte == 0xAA && mainboard_buffer_pos_ == 0) { + mainboard_buffer_[mainboard_buffer_pos_++] = byte; + return; + } + // Если уже начали собирать пакет + if (mainboard_buffer_pos_ > 0 && mainboard_buffer_pos_ < MAINBOARD_BUFFER_SIZE) { + mainboard_buffer_[mainboard_buffer_pos_++] = byte; + if (byte == 0x55) { + parse_mainboard_packet(mainboard_buffer_, mainboard_buffer_pos_); + mainboard_buffer_pos_ = 0; + } + } +} + +void SaecoSeries6000::update_display_counter_from_packet(const uint8_t* buffer, uint16_t size) { + if (size > 4 && buffer[3] != 0xFF) { + last_display_counter_ = buffer[4]; + } +} + +void SaecoSeries6000::parse_display_packet(const uint8_t* buffer, uint16_t size) { + if (debug_) { + std::vector hex_buffer; + hex_buffer.resize(size * 3 + 1); + for (uint16_t i = 0; i < size; i++) { + sprintf(hex_buffer.data() + i * 3, "%02X ", buffer[i]); + } + if (size > 4 && buffer[3] != 0xFF){ + ESP_LOGI(TAG, "Packet from Display: %s", hex_buffer.data()); + } + } + update_display_counter_from_packet(buffer, size); + // Здесь больше не парсим B0-пакеты, только debug +} + +// Разбор пакета B0 (системные статусы кофемашины) +// buffer[6], buffer[7], buffer[9] содержат коды состояния +void SaecoSeries6000::parse_b0_packet(const uint8_t* buffer, uint16_t size) { + if (size < 10) return; + if (buffer[6] == 0x0E) { + if (buffer[9] == 0x00) { + pub_status(20); // 20: "Опорож. контейнер для коф. гущи" + } else { + if ((buffer[9] & 0x40) != 0) { + pub_status(22); // 22: "Воды нет" + } else { + pub_status(17); // 17: "Вода есть" + } + if ((buffer[9] & 0x80) != 0) { + pub_status(23); // 23: "Извлечен контейнер для жмыха" + } else { + pub_status(18); // 18: "Вставлен контейнер для жмыха" + } + } + } else if (buffer[6] == 0x06) { + pub_status(21); // 21: "Готово/Выберите напиток" + } else if (buffer[6] == 0x0C) { + if (buffer[7] == 0x01) { + pub_status(8); // 8: "Наслаждайтесь" + } else if (buffer[7] == 0x02) { + pub_status(15); // 15: "Что-то (07 0C 02)" + } + } else if (buffer[6] == 0x07) { + if (buffer[7] == 0x0E) { + pub_status(9); // 9: "Нагрев воды" + } else if (buffer[7] == 0x0D) { + pub_status(10); // 10: "Перемалываем зерна" + } else if (buffer[7] == 0x10) { + pub_status(3); // 3: "Наливаем молоко" + } else if (buffer[7] == 0x11) { + pub_status(4); // 4: "Наливаем кофе" + } else if (buffer[7] == 0x12) { + pub_status(5); // 5: "Предварительное дозирование" + } else if (buffer[7] == 0x13) { + pub_status(6); // 6: "Создание пара для молока" + } else if (buffer[7] == 0x14) { + pub_status(7); // 7: "Заварочный узел в положение заваривания" + } else if (buffer[7] == 0x15) { + pub_status(15); // 15: "Наслаждайтесь" + } + } else if (buffer[6] == 0x08) { + if (buffer[7] == 0x0E) { + pub_status(14); // 14: "Нагревание" + } else if (buffer[7] == 0x02) { + pub_status(11); // 11: "Промывка" + } else if (buffer[7] == 0x14) { + pub_status(13); // 13: "Что-то (07 08 14)" + } else if (buffer[7] == 0x05) { + pub_status(12); // 12: "Зерна закончились" + } else if (buffer[7] == 0x16) { + pub_status(24); // 24: "Удаление накипи стадия 1" + } else if (buffer[7] == 0x18) { + pub_status(25); // 25: "Удаление накипи стадия 2" + } + } else if (buffer[7] == 0x00) { + if (buffer[6] == 0x01) { + pub_status(1); // 1: "Что-то (07 01 00)" + } else if (buffer[6] == 0x05) { + pub_status(2); // 2: "Выключено" + } + } +} + +// Разбор пакета B5 (коды ошибок кофемашины) +// buffer[10], buffer[11] содержат коды ошибок +void SaecoSeries6000::parse_b5_packet(const uint8_t* buffer, uint16_t size) { + if (size <= 12) return; + if (buffer[10] == 0x00) { + if (buffer[11] == 0x00) pub_status(30); // 30: "Ошибка 0x00" + else if (buffer[11] == 0x0B) pub_status(31); // 31: "Ошибка 0x0B" + else if (buffer[11] == 0xE6) pub_status(32); // 32: "Ошибка 0xE6" + else if (buffer[11] == 0x80) pub_status(33); // 33: "Ошибка 0x80" + else if (buffer[11] == 0xCB) pub_status(34); // 34: "Ошибка 0xCB" + else if (buffer[11] == 0xFF) pub_status(35); // 35: "Ошибка 0xFF" + else if (buffer[11] == 0xA0) pub_status(36); // 36: "Ошибка 0xA0" + } else if (buffer[10] == 0x01) { + pub_status(37); // 37: "Статус2 0x01" + } +} + +void SaecoSeries6000::parse_mainboard_packet(const uint8_t* buffer, uint16_t size) { + last_activity_time = millis(); // сбрасываем таймер активности при любом пакете + if (debug_) { + // std::vector hex_buffer; + // hex_buffer.resize(size * 3 + 1); + // for (uint16_t i = 0; i < size; i++) { + // sprintf(hex_buffer.data() + i * 3, "%02X ", buffer[i]); + // } + //ESP_LOGD(TAG, "Packet from Mainboard: %s", hex_buffer.data()); + //ESP_LOGD(TAG, "parse_mainboard_packet: size=%u", size); + } + // Парсим B0 + if (size > 4 && buffer[3] == 0xB0) { + parse_b0_packet(buffer, size); + } else if (size > 12 && buffer[3] == 0xB5) { + parse_b5_packet(buffer, size); + } +} + +void SaecoSeries6000::pub_status(uint8_t status) { + if (status_sensor_ != nullptr) { + status_sensor_->publish_state(status); + } +} + +void SaecoSeries6000::dump_config() { + ESP_LOGCONFIG(TAG, "Saeco Series 6000 UART Bridge"); + ESP_LOGCONFIG(TAG, " UART Display (uart_display): %p", uart_display_); + ESP_LOGCONFIG(TAG, " UART Mainboard (uart_mainboard): %p", uart_mainboard_); + ESP_LOGCONFIG(TAG, " Debug mode: %s", ONOFF(this->debug_)); +} + +void SaecoSeries6000::send_packet_to_mainboard(const std::vector &data) { + ESP_LOGI(TAG, "Send to Mainboard pressed."); + for (auto b : data) { + uart_mainboard_->write_byte(b); + } +} + +const uint8_t SaecoSeries6000::BitReverse[256] = { + 0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0, + 0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8, + 0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4, + 0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC, + 0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2, + 0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA, + 0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6, + 0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE, + 0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1, + 0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9, + 0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5, + 0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD, + 0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3, + 0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB, + 0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7, + 0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF +}; + +uint32_t SaecoSeries6000::calc_crc(const std::vector& data, size_t start, size_t end) { + uint32_t crc = 0xFFFFFFFF; + const uint32_t POLY = 0x04C11DB7; + if (start >= end) return 0; + // start + uint8_t val = data[start]; + ((uint8_t*)(&crc))[3] ^= BitReverse[val]; + for (uint8_t j = 0; j < 8; j++) { + if (crc & 0x80000000) { + crc = (crc << 1) ^ POLY; + } else { + crc <<= 1; + } + } + // остальные байты + for (size_t i = start + 1; i < end; i++) { + uint8_t v = data[i]; + ((uint8_t*)(&crc))[3] ^= BitReverse[v]; + for (uint8_t j = 0; j < 8; j++) { + if (crc & 0x80000000) { + crc = (crc << 1) ^ POLY; + } else { + crc <<= 1; + } + } + } + uint32_t t32 = 0; + ((uint8_t*)(&t32))[0] = BitReverse[((uint8_t*)(&crc))[3]]; + ((uint8_t*)(&t32))[1] = BitReverse[((uint8_t*)(&crc))[2]]; + ((uint8_t*)(&t32))[2] = BitReverse[((uint8_t*)(&crc))[1]]; + ((uint8_t*)(&t32))[3] = BitReverse[((uint8_t*)(&crc))[0]]; + crc = t32 ^ 0xFFFFFFFF; + return crc; +} + +std::vector SaecoSeries6000::parse_hex_string_to_bytes(const std::string& hex_string) { + std::vector pkt; + size_t pos = 0; + while (pos < hex_string.size()) { + while (pos < hex_string.size() && isspace(hex_string[pos])) ++pos; + if (pos >= hex_string.size()) break; + char* endptr = nullptr; + uint8_t byte = static_cast(std::strtoul(hex_string.c_str() + pos, &endptr, 16)); + pkt.push_back(byte); + if (endptr == nullptr || endptr == hex_string.c_str() + pos) break; + pos = endptr - hex_string.c_str(); + } + return pkt; +} + +void SaecoSeries6000::send_packets_to_mainboard(const std::string& hex_packet, uint32_t delay_ms) { + std::vector> pkts = {parse_hex_string_to_bytes(hex_packet)}; + send_packets_to_mainboard(pkts, delay_ms); +} + +void SaecoSeries6000::send_packets_to_mainboard(const std::vector& hex_packets, uint32_t delay_ms) { + std::vector> pkts; + for (const auto& s : hex_packets) { + pkts.push_back(parse_hex_string_to_bytes(s)); + } + send_packets_to_mainboard(pkts, delay_ms); +} + +void SaecoSeries6000::send_packets_to_mainboard(const std::vector> &packets, uint32_t delay_ms) { + uint8_t counter = (last_display_counter_ + 1) & 0xFF; + for (size_t i = 0; i < packets.size(); ++i) { + std::vector pkt = packets[i]; + // Заменяем байт счетчика (4-й байт) + if (pkt.size() > 4) { + pkt[4] = counter; + } + // Пересчёт CRC: ищем преамбулу 0xAA 0xAA 0xAA, считаем CRC по данным между преамбулой и последними 5 байтами + if (pkt.size() > 8) { + size_t preamble = 0; + if (pkt[0] == 0xAA && pkt[1] == 0xAA && pkt[2] == 0xAA) { + preamble = 3; + } + size_t crc_start = preamble; + size_t crc_end = pkt.size() - 5; // не включаем 4 CRC и 0x55 + uint32_t crc = calc_crc(pkt, crc_start, crc_end); + // Заменяем последние 4 байта перед 0x55 на новую CRC + pkt[pkt.size()-2] = (crc >> 24) & 0xFF; + pkt[pkt.size()-3] = (crc >> 16) & 0xFF; + pkt[pkt.size()-4] = (crc >> 8) & 0xFF; + pkt[pkt.size()-5] = crc & 0xFF; + // pkt[pkt.size()-5] = (crc >> 24) & 0xFF; + // pkt[pkt.size()-4] = (crc >> 16) & 0xFF; + // pkt[pkt.size()-3] = (crc >> 8) & 0xFF; + // pkt[pkt.size()-2] = (crc) & 0xFF; + } + // Логируем пакет перед отправкой + std::string log_str; + char buf[8]; + for (auto b : pkt) { + snprintf(buf, sizeof(buf), "%02X ", b); + log_str += buf; + } + ESP_LOGI(TAG, "Send to Mainboard: %s", log_str.c_str()); + send_packet_to_mainboard(pkt); + counter = (counter + 1) & 0xFF; + delay(delay_ms); + } + last_display_counter_ = (counter - 1) & 0xFF; +} + +// Реализация SendPacketsAction полностью в header (saeco_series_6000.h) + +} // namespace saeco_series_6000 +} // namespace esphome \ No newline at end of file diff --git a/esphome/settings/components/saeco_series_6000/saeco_series_6000.h b/esphome/settings/components/saeco_series_6000/saeco_series_6000.h new file mode 100644 index 0000000..fe73935 --- /dev/null +++ b/esphome/settings/components/saeco_series_6000/saeco_series_6000.h @@ -0,0 +1,81 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/automation.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace saeco_series_6000 { + +// Buffer sizes for packet sniffing +constexpr size_t DISPLAY_BUFFER_SIZE = 128; +constexpr size_t MAINBOARD_BUFFER_SIZE = 128; + +class SaecoSeries6000 : public Component { + public: + void set_uart_display(uart::UARTComponent *uart_display) { uart_display_ = uart_display; } + void set_uart_mainboard(uart::UARTComponent *uart_mainboard) { uart_mainboard_ = uart_mainboard; } + void set_debug(bool debug) { debug_ = debug; } + void set_status_sensor(sensor::Sensor *sensor) { status_sensor_ = sensor; } + void loop() override; + void dump_config() override; + void send_packet_to_mainboard(const std::vector &data); + void send_packets_to_mainboard(const std::vector> &packets, uint32_t delay_ms = 10); + void send_packets_to_mainboard(const std::vector& hex_packets, uint32_t delay_ms = 10); + void send_packets_to_mainboard(const std::string& hex_packet, uint32_t delay_ms = 10); + + protected: + uart::UARTComponent *uart_display_; + uart::UARTComponent *uart_mainboard_; + bool debug_{false}; + sensor::Sensor *status_sensor_{nullptr}; + + private: + uint8_t display_buffer_[DISPLAY_BUFFER_SIZE]; + uint16_t display_buffer_pos_{0}; + + uint8_t mainboard_buffer_[MAINBOARD_BUFFER_SIZE]; + uint16_t mainboard_buffer_pos_{0}; + + uint8_t display_last_bytes_[3] = {0, 0, 0}; + uint8_t mainboard_last_bytes_[3] = {0, 0, 0}; + + uint8_t display_sync_count_{0}; + uint8_t mainboard_sync_count_{0}; + + uint8_t last_display_counter_{0}; + void update_display_counter_from_packet(const uint8_t* buffer, uint16_t size); + + void process_display_byte(uint8_t byte); + void process_mainboard_byte(uint8_t byte); + + void parse_display_packet(const uint8_t* buffer, uint16_t size); + void parse_mainboard_packet(const uint8_t* buffer, uint16_t size); + void parse_b0_packet(const uint8_t* buffer, uint16_t size); + void parse_b5_packet(const uint8_t* buffer, uint16_t size); + void pub_status(uint8_t status); + static const uint8_t BitReverse[256]; + uint32_t calc_crc(const std::vector& data, size_t start, size_t end); + std::vector parse_hex_string_to_bytes(const std::string& hex_string); +}; + +class SaecoSendPacketsButton : public esphome::button::Button, public esphome::Component { + public: + void set_parent(SaecoSeries6000 *parent) { parent_ = parent; } + void set_packets(const std::vector> &packets) { packets_ = packets; } + void set_delay_ms(uint32_t delay_ms) { delay_ms_ = delay_ms; } + void press_action() override { + if (parent_ != nullptr) { + parent_->send_packets_to_mainboard(packets_, delay_ms_); + } + } + private: + SaecoSeries6000 *parent_{nullptr}; + std::vector> packets_; + uint32_t delay_ms_{10}; +}; + +} // namespace saeco_series_6000 +} // namespace esphome \ No newline at end of file diff --git a/esphome/settings/danil_bedside_lamp.yaml b/esphome/settings/danil_bedside_lamp.yaml new file mode 100644 index 0000000..f831eb6 --- /dev/null +++ b/esphome/settings/danil_bedside_lamp.yaml @@ -0,0 +1,58 @@ +# -------------------------------------------------------------------------- +# Substitutions +# +# These are substitutions as used by the configuration packages from below. +# You can uncomment and update the ones that you want to modify. +# -------------------------------------------------------------------------- + +substitutions: + name: danil-bedside-lamp + friendly_name: Danil Bedside Lamp + #light_name: ${friendly_name} RGBWW Light + #light_mode_text_sensor_name: ${friendly_name} Light Mode + default_transition_length: 800ms + +# -------------------------------------------------------------------------- +# Load configuration packages +# +# These provide a convenient way to compose your device configuration from +# some functional building blocks. Pick and mix the blocks that you need. +# +# For customization you can override options in your config or you can +# copy the contents of these packages directly in your config file as +# an example for your own customizations. +# +# Available packages are: +# - core.yaml : core components & hardware setup +# - behavior_default.yaml : default device behavior +# - ota_feedback.yaml : enable visual feedback during OTA updates +# - activate_preset_svc.yaml : 'activate_preset' service for Home Assistant +# -------------------------------------------------------------------------- + +packages: + bslamp2: + url: https://github.com/mmakaay/esphome-xiaomi_bslamp2 + ref: dev + files: + - packages/core.yaml + - packages/behavior_default.yaml + - packages/ota_feedback.yaml + - packages/activate_preset_svc.yaml + refresh: 0s + +# -------------------------------------------------------------------------- +# Use your own preferences for these components. +# -------------------------------------------------------------------------- + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + use_address: ${name}.lan + +api: + encryption: + key: !secret api_encryption_key + +ota: + platform: esphome + password: !secret esphome_pass diff --git a/esphome/settings/jkbms-monitor.yml b/esphome/settings/jkbms-monitor.yml new file mode 100644 index 0000000..57cbc41 --- /dev/null +++ b/esphome/settings/jkbms-monitor.yml @@ -0,0 +1,387 @@ +substitutions: + name: jk-bms + device_description: "Monitor and control a JK-BMS v11 via bluetooth" + external_components_source: github://syssi/esphome-jk-bms@main + mac_address: C8:47:80:39:31:5D + # Please use "JK02_24S" if you own a old JK-BMS < hardware version 11.0 (hardware version >= 6.0 and < 11.0) + # Please use "JK02_32S" if you own a new JK-BMS >= hardware version 11.0 (f.e. JK-B2A8S20P hw 11.XW, sw 11.26) + # Please use "JK04" if you have some old JK-BMS <= hardware version 3.0 (f.e. JK-B2A16S hw 3.0, sw. 3.3.0) + protocol_version: JK02_32S + device_host: jk-bms.lan + +esphome: + name: ${name} + comment: ${device_description} + min_version: 2024.6.0 + project: + name: "syssi.esphome-jk-bms" + version: 2.3.0 + +esp32: + variant: esp32c3 + board: esp32-c3-devkitm-1 + framework: + type: esp-idf + +external_components: + - source: ${external_components_source} + refresh: 0s + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + use_address: ${device_host} + +logger: + level: DEBUG + logs: + esp32_ble_tracker: INFO + esp32_ble_client: INFO + +web_server: + port: 80 + auth: + username: admin + password: superLongP4ss + +# If you don't use Home Assistant please remove this `api` section and uncomment the `mqtt` component! +api: + encryption: + key: !secret api_encryption_key + +ota: + - platform: esphome + password: !secret api_encryption_key + on_begin: + then: + - switch.turn_off: ble_client_switch0 + - logger.log: "BLE connection suspended for OTA update" + +# mqtt: +# broker: !secret mqtt_host +# username: !secret mqtt_username +# password: !secret mqtt_password +# id: mqtt_client + +esp32_ble_tracker: + scan_parameters: + active: false + +ble_client: + - mac_address: ${mac_address} + id: client0 + +jk_bms_ble: + - ble_client_id: client0 + protocol_version: ${protocol_version} + throttle: 5s + id: bms0 + +binary_sensor: + - platform: jk_bms_ble + balancing: + name: "${name} balancing" + charging: + name: "${name} charging" + discharging: + name: "${name} discharging" + heating: + name: "${name} heating" + online_status: + name: "${name} online status" + dry_contact_1: + name: "${name} dry contact 1" + dry_contact_2: + name: "${name} dry contact 2" + +button: + - platform: jk_bms_ble + retrieve_settings: + name: "${name} retrieve settings" + retrieve_device_info: + name: "${name} retrieve device info" + +number: + - platform: jk_bms_ble + jk_bms_ble_id: bms0 + balance_trigger_voltage: + name: "${name} balance trigger voltage" + cell_count: + name: "${name} cell count" + total_battery_capacity: + name: "${name} total battery capacity" + cell_voltage_overvoltage_protection: + name: "${name} cell voltage overvoltage protection" + cell_voltage_overvoltage_recovery: + name: "${name} cell voltage overvoltage recovery" + cell_voltage_undervoltage_protection: + name: "${name} cell voltage undervoltage protection" + cell_voltage_undervoltage_recovery: + name: "${name} cell voltage undervoltage recovery" + balance_starting_voltage: + name: "${name} balance starting voltage" + voltage_calibration: + name: "${name} voltage calibration" + current_calibration: + name: "${name} current calibration" + power_off_voltage: + name: "${name} power off voltage" + max_balance_current: + name: "${name} max balance current" + max_charge_current: + name: "${name} max charge current" + max_discharge_current: + name: "${name} max discharge current" + smart_sleep_voltage: + name: "${name} smart sleep voltage" + cell_soc100_voltage: + name: "${name} cell soc100 voltage" + cell_soc0_voltage: + name: "${name} cell soc0 voltage" + cell_request_charge_voltage: + name: "${name} cell request charge voltage" + cell_request_float_voltage: + name: "${name} cell request float voltage" + cell_request_charge_voltage_time: + name: "${name} cell request charge voltage time" + cell_request_float_voltage_time: + name: "${name} cell request float voltage time" + charge_overcurrent_protection_delay: + name: "${name} charge overcurrent protection delay" + charge_overcurrent_protection_recovery_time: + name: "${name} charge overcurrent protection recovery time" + discharge_overcurrent_protection_delay: + name: "${name} discharge overcurrent protection delay" + discharge_overcurrent_protection_recovery_time: + name: "${name} discharge overcurrent protection recovery time" + short_circuit_protection_delay: + name: "${name} short circuit protection delay" + short_circuit_protection_recovery_time: + name: "${name} short circuit protection recovery time" + charge_overtemperature_protection: + name: "${name} charge overtemperature protection" + charge_overtemperature_protection_recovery: + name: "${name} charge overtemperature protection recovery" + discharge_overtemperature_protection: + name: "${name} discharge overtemperature protection" + discharge_overtemperature_protection_recovery: + name: "${name} discharge overtemperature protection recovery" + charge_undertemperature_protection: + name: "${name} charge undertemperature protection" + charge_undertemperature_protection_recovery: + name: "${name} charge undertemperature protection recovery" + power_tube_overtemperature_protection: + name: "${name} power tube overtemperature protection" + power_tube_overtemperature_protection_recovery: + name: "${name} power tube overtemperature protection recovery" + discharge_precharge_time: + name: "${name} discharge precharge time" + heating_start_temperature: + name: "${name} heating start temperature" + heating_stop_temperature: + name: "${name} heating stop temperature" + +sensor: + - platform: jk_bms_ble + jk_bms_ble_id: bms0 + min_cell_voltage: + name: "${name} min cell voltage" + max_cell_voltage: + name: "${name} max cell voltage" + min_voltage_cell: + name: "${name} min voltage cell" + max_voltage_cell: + name: "${name} max voltage cell" + delta_cell_voltage: + name: "${name} delta cell voltage" + average_cell_voltage: + name: "${name} average cell voltage" + cell_voltage_1: + name: "${name} cell voltage 1" + cell_voltage_2: + name: "${name} cell voltage 2" + cell_voltage_3: + name: "${name} cell voltage 3" + cell_voltage_4: + name: "${name} cell voltage 4" + cell_voltage_5: + name: "${name} cell voltage 5" + cell_voltage_6: + name: "${name} cell voltage 6" + cell_voltage_7: + name: "${name} cell voltage 7" + cell_voltage_8: + name: "${name} cell voltage 8" + cell_voltage_9: + name: "${name} cell voltage 9" + cell_voltage_10: + name: "${name} cell voltage 10" + cell_voltage_11: + name: "${name} cell voltage 11" + cell_voltage_12: + name: "${name} cell voltage 12" + cell_voltage_13: + name: "${name} cell voltage 13" + cell_voltage_14: + name: "${name} cell voltage 14" + cell_voltage_15: + name: "${name} cell voltage 15" + cell_voltage_16: + name: "${name} cell voltage 16" + cell_voltage_17: + name: "${name} cell voltage 17" + cell_voltage_18: + name: "${name} cell voltage 18" + cell_voltage_19: + name: "${name} cell voltage 19" + cell_voltage_20: + name: "${name} cell voltage 20" + cell_voltage_21: + name: "${name} cell voltage 21" + cell_voltage_22: + name: "${name} cell voltage 22" + cell_voltage_23: + name: "${name} cell voltage 23" + cell_voltage_24: + name: "${name} cell voltage 24" + cell_resistance_1: + name: "${name} cell resistance 1" + cell_resistance_2: + name: "${name} cell resistance 2" + cell_resistance_3: + name: "${name} cell resistance 3" + cell_resistance_4: + name: "${name} cell resistance 4" + cell_resistance_5: + name: "${name} cell resistance 5" + cell_resistance_6: + name: "${name} cell resistance 6" + cell_resistance_7: + name: "${name} cell resistance 7" + cell_resistance_8: + name: "${name} cell resistance 8" + cell_resistance_9: + name: "${name} cell resistance 9" + cell_resistance_10: + name: "${name} cell resistance 10" + cell_resistance_11: + name: "${name} cell resistance 11" + cell_resistance_12: + name: "${name} cell resistance 12" + cell_resistance_13: + name: "${name} cell resistance 13" + cell_resistance_14: + name: "${name} cell resistance 14" + cell_resistance_15: + name: "${name} cell resistance 15" + cell_resistance_16: + name: "${name} cell resistance 16" + cell_resistance_17: + name: "${name} cell resistance 17" + cell_resistance_18: + name: "${name} cell resistance 18" + cell_resistance_19: + name: "${name} cell resistance 19" + cell_resistance_20: + name: "${name} cell resistance 20" + cell_resistance_21: + name: "${name} cell resistance 21" + cell_resistance_22: + name: "${name} cell resistance 22" + cell_resistance_23: + name: "${name} cell resistance 23" + cell_resistance_24: + name: "${name} cell resistance 24" + total_voltage: + name: "${name} total voltage" + current: + name: "${name} current" + heating_current: + name: "${name} heating current" + power: + name: "${name} power" + charging_power: + name: "${name} charging power" + discharging_power: + name: "${name} discharging power" + temperature_sensor_1: + name: "${name} temperature sensor 1" + temperature_sensor_2: + name: "${name} temperature sensor 2" + temperature_sensor_3: + name: "${name} temperature sensor 3" + temperature_sensor_4: + name: "${name} temperature sensor 4" + temperature_sensor_5: + name: "${name} temperature sensor 5" + power_tube_temperature: + name: "${name} power tube temperature" + balancing: + name: "${name} balancing" + state_of_charge: + name: "${name} state of charge" + state_of_health: + name: "${name} state of health" + capacity_remaining: + name: "${name} capacity remaining" + total_battery_capacity_setting: + name: "${name} total battery capacity setting" + charging_cycles: + name: "${name} charging cycles" + total_charging_cycle_capacity: + name: "${name} total charging cycle capacity" + total_runtime: + name: "${name} total runtime" + balancing_current: + name: "${name} balancing current" + errors_bitmask: + name: "${name} errors bitmask" + emergency_time_countdown: + name: "${name} emergency time countdown" + charge_status_id: + name: "${name} charge status id" + charge_status_time_elapsed: + name: "${name} charge status time elapsed" + +switch: + - platform: jk_bms_ble + charging: + name: "${name} charging" + discharging: + name: "${name} discharging" + balancer: + name: "${name} balancer" + emergency: + name: "${name} emergency" + heating: + name: "${name} heating" + disable_temperature_sensors: + name: "${name} disable temperature sensors" + display_always_on: + name: "${name} display always on" + smart_sleep: + name: "${name} smart sleep" + disable_pcl_module: + name: "${name} disable pcl module" + timed_stored_data: + name: "${name} timed stored data" + charging_float_mode: + name: "${name} charging float mode" + + - platform: ble_client + ble_client_id: client0 + name: "${name} enable bluetooth connection" + id: ble_client_switch0 + +text_sensor: + - platform: jk_bms_ble + errors: + name: "${name} errors" + total_runtime_formatted: + name: "${name} total runtime formatted" + charge_status: + name: "${name} charge status" + software_version: + name: "${name} software version" + hardware_version: + name: "${name} hardware version" diff --git a/esphome/settings/old/ac_childroom_gree.yaml b/esphome/settings/old/ac_childroom_gree.yaml new file mode 100644 index 0000000..512c0b1 --- /dev/null +++ b/esphome/settings/old/ac_childroom_gree.yaml @@ -0,0 +1,85 @@ +#=============================================================================== +# Контроллер кондиционера в гостиной +# Wemos D1 mini +#=============================================================================== +substitutions: + node_name: ac_childroom # Use a unique name. + node_id: ac_childroom # Use a unique id. + deviceid: ac_childroom + devicename: ac_childroom + friendly_node_name: "Кондиционер Детская Старый" + +#=============================================================================== +# Общий конфиг +#=============================================================================== +esphome: + name: ${node_name} + friendly_name: ${friendly_node_name} + +esp8266: + board: esp12e + +web_server: + port: 80 + auth: + username: admin + password: superLongP4ss + +# Enable Home Assistant API +api: + encryption: + key: !secret api_encryption_key + +ota: + - platform: esphome + password: !secret api_encryption_key + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "${devicename} Fallback Hotspot" + password: !secret esphome_captive_pass + +captive_portal: + +# Enable logging +logger: + baud_rate: 0 # disable LOG output on UART as UART is used for Sinclair AC unit +# level: VERBOSE + +uart: + tx_pin: 1 + rx_pin: 3 + baud_rate: 4800 + parity: EVEN + +time: + - platform: sntp + +external_components: + - source: github://gekkehenkie11/esphome_gree_ac + components: [sinclair_ac] + refresh: 0s + +climate: + - platform: sinclair_ac + name: ${devicename} + horizontal_swing_select: + name: ${devicename} Horizontal Swing Mode + vertical_swing_select: + name: ${devicename} Vertical Swing Mode + display_select: + name: ${devicename} Display Mode + display_unit_select: + name: ${devicename} Display Unit + plasma_switch: + name: ${devicename} Plasma + sleep_switch: + name: ${devicename} Sleep + xfan_switch: + name: ${devicename} X-fan + save_switch: + name: ${devicename} Save/8 Heat diff --git a/esphome/settings/old/ac_childroom_midea.yaml b/esphome/settings/old/ac_childroom_midea.yaml new file mode 100644 index 0000000..8972dc7 --- /dev/null +++ b/esphome/settings/old/ac_childroom_midea.yaml @@ -0,0 +1,69 @@ +esphome: + name: ac_childroom + +esp8266: + board: d1_mini + +# Disable logging over UART (required) +logger: + baud_rate: 0 + +# Enable Home Assistant API +api: + +ota: + - platform: esphome + password: !secret api_encryption_key + +web_server: + port: 80 + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "Childroom AC Fallback" + password: !secret esphome_captive_pass + +# UART settings for Midea dongle (required) +uart: + tx_pin: GPIO1 # hardware dependant + rx_pin: GPIO3 # hardware dependant + baud_rate: 9600 + debug: + +# Main settings +climate: + - platform: midea + name: childroom_ac # Use a unique name. +# transmitter_id: # Optional. Add this option to use IR transmitter. + period: 1s # Optional + timeout: 2s # Optional + num_attempts: 3 # Optional + autoconf: true # Autoconfigure most options. + beeper: true # Beep on commands. + visual: # Optional. Example of visual settings override. + min_temperature: 17 °C # min: 17 + max_temperature: 30 °C # max: 30 + temperature_step: 1 °C # min: 0.5 + supported_modes: # All capabilities in this section detected by autoconf. + - FAN_ONLY # This capability is always used. + - HEAT_COOL + - COOL + - HEAT + - DRY + custom_fan_modes: + - SILENT + - TURBO + supported_presets: # All capabilities in this section detected by autoconf. + - ECO + - BOOST + - SLEEP # This capability is always used. + custom_presets: # All capabilities in this section detected by autoconf. + - FREEZE_PROTECTION + supported_swing_modes: + - VERTICAL # This capability is always used. + - HORIZONTAL + - BOTH \ No newline at end of file diff --git a/esphome/settings/old/ac_mainroom_old.yaml b/esphome/settings/old/ac_mainroom_old.yaml new file mode 100644 index 0000000..7388a9d --- /dev/null +++ b/esphome/settings/old/ac_mainroom_old.yaml @@ -0,0 +1,66 @@ +esphome: + name: mainroom_ac + +esp8266: + board: esp12e + +# Disable logging over UART (required) +logger: + baud_rate: 0 + +# Enable Home Assistant API +api: + +ota: + +web_server: + port: 80 + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "Mainroom AC Fallback" + password: !secret esphome_captive_pass + +# UART settings for Midea dongle (required) +uart: + tx_pin: GPIO1 # hardware dependant + rx_pin: GPIO3 # hardware dependant + baud_rate: 9600 + +# Main settings +climate: + - platform: midea + name: mainroom_ac # Use a unique name. +# transmitter_id: # Optional. Add this option to use IR transmitter. + period: 1s # Optional + timeout: 2s # Optional + num_attempts: 3 # Optional + autoconf: true # Autoconfigure most options. + beeper: true # Beep on commands. + visual: # Optional. Example of visual settings override. + min_temperature: 17 °C # min: 17 + max_temperature: 30 °C # max: 30 + temperature_step: 1 °C # min: 0.5 + supported_modes: # All capabilities in this section detected by autoconf. + - FAN_ONLY # This capability is always used. + - HEAT_COOL + - COOL + - HEAT + - DRY + custom_fan_modes: + - SILENT + - TURBO + supported_presets: # All capabilities in this section detected by autoconf. + - ECO + - BOOST + - SLEEP # This capability is always used. + custom_presets: # All capabilities in this section detected by autoconf. + - FREEZE_PROTECTION + supported_swing_modes: + - VERTICAL # This capability is always used. + - HORIZONTAL + - BOTH \ No newline at end of file diff --git a/esphome/settings/presence.h b/esphome/settings/presence.h new file mode 100644 index 0000000..c434595 --- /dev/null +++ b/esphome/settings/presence.h @@ -0,0 +1,336 @@ +#include "esphome.h" + +#define BLE_PIN D1 + +class HappyBubblesDevice : public Component, public CustomMQTTDevice { +protected: + // char hostname[20] = "hall"; + const char* hostname = "hall"; + String buf = ""; + int txPower = -59; + char ble_mac_addr[13]; + char ble_rssi [5]; + char ble_is_scan_resp [2]; + char ble_type [2]; + char ble_data[100]; + + char tx_power [3]; + char beacon_uuid [33]; + char beacon_major [5]; + char beacon_minor [5]; + char instance_id [13]; + char json_ble_send[500]; + char topic [130]; + + typedef struct { + uint8_t type; + uint8_t start; + uint8_t end; + uint8_t size; + } ble_tok_t; + +public: + HappyBubblesDevice(const char* host_param) { + hostname = host_param; + } + void setup() override + { + Serial.begin(115200); + } + void loop() override + { + while (Serial.available() > 0) { + char inChar = Serial.read(); + + if (inChar == '\n') { + //если буфер наполнен + // parseData(); + char charBuf[120]; + buf.toCharArray(charBuf, 120); + process_serial(charBuf); + buf = ""; + } + else if (inChar != '\r') { + buf += inChar; + } + } + } + + String getValue(String data, char separator, int index) + { + int found = 0; + int strIndex[] = { 0, -1 }; + int maxIndex = data.length() - 1; + + for (int i = 0; i <= maxIndex && found <= index; i++) { + if (data.charAt(i) == separator || i == maxIndex) { + found++; + strIndex[0] = strIndex[1] + 1; + strIndex[1] = (i == maxIndex) ? i + 1 : i; + } + } + + return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; + } + + float calcDistance(int rssi, int txPower) + { + if (rssi == 0) { + return -1.0; + } + + float ratio = rssi * 1.0 / txPower; + if (ratio < 1.0) { + return pow(ratio, 10); + } + else { + return (0.89976) * pow(ratio, 7.7095) + 0.111; + } + } + + String floatToString(float x, byte precision = 2) + { + char tmp[50]; + dtostrf(x, 0, precision, tmp); + return String(tmp); + } + + void process_serial(char *buf) + { + os_printf("********* got serial to process %d ** %s \n\r", strlen(buf), buf); + char * pch; + pch = strtok(buf, "|"); + int i = 0; + + memset(ble_mac_addr, 0, 12); + memset(ble_rssi, 0, 5); + memset(ble_is_scan_resp, 0, 1); + memset(ble_type, 0, 1); + memset(ble_data, 0, 100); + + while(pch != NULL) + { + switch(i) + { + case 0: + { + strcpy(ble_mac_addr, pch); + break; + } + case 1: + { + strcpy(ble_rssi, pch); + break; + } + case 2: + { + strcpy(ble_is_scan_resp, pch); + break; + } + case 3: + { + strcpy(ble_type, pch); + break; + } + case 4: + { + strcpy(ble_data, pch); + break; + } + } + i++; + //os_printf("token: %s\n", pch); + pch = strtok(NULL, "|"); + } + + //basic filters + //check mac is 12 chars + int should_send = 0; + if(strlen(ble_mac_addr) != 12) + { + os_printf("bad mac, not 12, %d, %s\n", strlen(ble_mac_addr), ble_mac_addr); + should_send = 1; + } + //check mac is all hex + if (!ble_mac_addr[strspn(ble_mac_addr, "0123456789abcdefABCDEF")] == 0) + { + os_printf("bad mac, not all hex %s\n", ble_mac_addr); + should_send = 2; + } + //check data is all hex + if (strlen(ble_data) < 8 || !ble_data[strspn(ble_data, "0123456789abcdefABCDEF")] == 0 ) + { + os_printf("bad data %s\n", ble_data); + should_send = 3; + } + //check rssi is all numbers + if (strlen(ble_rssi) < 1 || !ble_rssi[strspn(ble_rssi, "-0123456789")] == 0) + { + os_printf("bad rssi %s\n", ble_rssi); + should_send = 4; + } + //check scan_response is 0 or 1 + if (strlen(ble_is_scan_resp) < 1 || !ble_is_scan_resp[strspn(ble_is_scan_resp, "01")] == 0) + { + os_printf("bad scanresp %s\n", ble_is_scan_resp); + should_send = 5; + } + //check ble_type is hex + if (strlen(ble_type) < 1 || !ble_type[strspn(ble_type, "0123456789abcdefABCDEF")] == 0) + { + os_printf("bad type %s\n", ble_type); + should_send = 6; + } + if(should_send == 0) + { + //os_printf("^^^^^^^^^GOT THROUGH FILTERS!\r\n"); + } + else + { + //os_printf("xxxxxxxxxxFAIL FILTERS! %d \r\n", should_send); + //os_printf("=====%s || %s || %s || %s || %s ===DONE\n", ble_mac_addr, ble_rssi, ble_is_scan_resp, ble_type, ble_data); + return; + } + + //what type of beacon is this? + int beacon_type = 99; + + // iterate through the beacon data, but easier if it is bytes + uint8_t adv_bytes[strlen(ble_data)/2]; + + for(int i=0; i<(strlen(ble_data)/2); i++) { + uint8_t d1 = 0; + + if(ble_data[i*2] > '9') { + d1 += ((ble_data[i*2] - 'a' + 10) * 0x10); + } + else { + d1 += ((ble_data[i*2] - '0') * 0x10); + } + + if(ble_data[i*2+1] > '9') { + d1 += ble_data[i*2+1] - 'a' + 10; + } + else { + d1 += ble_data[i*2+1] - '0'; + } + + adv_bytes[i] = d1; + } + + // start with 6 tokens + ble_tok_t tokens[6]; + + uint8_t token_i = 0; + int stop = 0; + int pos = 0; + while(!stop) { + tokens[token_i].size = adv_bytes[pos]; + tokens[token_i].type = adv_bytes[pos+1]; + tokens[token_i].start = pos+2; + tokens[token_i].end = pos+tokens[token_i].size; + + /* + printf("tok: %d, size: %02hhx, type: %02hhx, start: %d end: %d\n", token_i, tokens[token_i].size, tokens[token_i].type, tokens[token_i].start, tokens[token_i].end); + for(int j=tokens[token_i].start; j<=tokens[token_i].end; j++) { + printf("%02hhx", adv_bytes[j]); + } + printf("\n\n"); + */ + pos = tokens[token_i].end + 1; + if(pos >= sizeof(adv_bytes)) { + stop = 1; + } + token_i++; + } + + // iterate through the tokens and determine what kind of beacon it is + for(int i = 0; i <= token_i; i++) { + // is iBeacon? + if(tokens[i].type == 0xff + && adv_bytes[tokens[i].start] == 0x4c + && adv_bytes[tokens[i].start+1] == 0x00 + && adv_bytes[tokens[i].start+2] == 0x02 + && adv_bytes[tokens[i].start+3] == 0x15) { + + beacon_type = 1; //ibeacon + + //now extract uuid + memset(beacon_uuid, 0, 33); + memcpy(beacon_uuid, &ble_data[(tokens[i].start+4)*2], 32); + beacon_uuid[32] = '\0'; + + //extract major + memset(beacon_major, 0, 5); + memcpy(beacon_major, &ble_data[(tokens[i].start+20)*2], 4); + beacon_major[4] = '\0'; + + //extract minor + memset(beacon_minor, 0, 5); + memcpy(beacon_minor, &ble_data[(tokens[i].start+22)*2], 4); + beacon_minor[4] = '\0'; + + //extract tx_power + memset(tx_power, 0, 3); + memcpy(tx_power, &ble_data[(tokens[i].start+24)*2], 2); + tx_power[3] = '\0'; + + os_printf("^^^^^ ibeacon %s %s %s %s\n", beacon_uuid, beacon_major, beacon_minor, tx_power); + } + } + + //add some filters to + if(beacon_type == 0) + { + //os_printf("send some mqtt for eddystone %s ========\n", beacon_uuid); + // sendMQTTeddystone(ble_mac_addr, beacon_uuid, instance_id, tx_power, ble_rssi); + } + else if(beacon_type == 1) + { + //os_printf("send some mqtt for ibeacon %s ========\n", beacon_uuid); + sendMQTTibeacon(ble_mac_addr, beacon_uuid, beacon_major, beacon_minor, tx_power, ble_rssi); + } + + //os_printf("send some mqtt for mac %s =====%d===\n", ble_mac_addr, strlen(ble_mac_addr)); + // sendMQTTraw(ble_mac_addr, ble_rssi, ble_is_scan_resp, ble_type, ble_data); + } + + // void sendMQTTeddystone(char *mac, char *namespace, char *instance_id, char *tx_power, char *rssi) + // { + // return; + // } + void sendMQTTibeacon(char* mac, char* uuid, char* major, char* minor, char* tx_power, char* rssi) + { + float distance = calcDistance(atoi(rssi), 0x50); + String distanceStr = floatToString(distance, 10); + + memset(json_ble_send, 0, 500); + os_sprintf(json_ble_send, "{\"hostname\": \"%s\",\"beacon_type\": \"ibeacon\",\"id\": \"%s\",\"rssi\": %s,\"uuid\": \"%s\",\"major\": \"%s\",\"minor\": \"%s\",\"tx_power\": \"%s\", \"distance\": \"%s\"}", hostname, mac, rssi, uuid, major, minor, tx_power, distanceStr.c_str()); + memset(topic, 0, 130); + os_sprintf(topic, "home/presence/%s", hostname); + publish(topic, json_ble_send); + } + // void sendMQTTraw(char * mac, char * rssi, char * is_scan, char * type, char * data) + // { + // memset(json_ble_send, 0, 500); + // os_sprintf(json_ble_send, "{\"hostname\": \"%s\",\"mac\": \"%s\",\"rssi\": %s,\"is_scan_response\": \"%s\",\"type\": \"%s\",\r\n\"data\": \"%s\"}", hostname, mac, rssi, is_scan, type, data); + // memset(topic, 0, 130); + // os_sprintf(topic, "happy-bubbles/ble/%s/raw/%s", hostname, mac); + // // publish(topic, json_ble_send); + // } +}; + +class BleSwitch : public Component, public Switch { +public: + void setup() override + { + // This will be called by App.setup() + pinMode(BLE_PIN, OUTPUT); + write_state(true); + } + void write_state(bool state) override + { + digitalWrite(BLE_PIN, !state); + publish_state(state); + } +}; \ No newline at end of file diff --git a/esphome/settings/presence_bedroom.yaml b/esphome/settings/presence_bedroom.yaml new file mode 100644 index 0000000..d1dd096 --- /dev/null +++ b/esphome/settings/presence_bedroom.yaml @@ -0,0 +1,62 @@ +#=============================================================================== +# Счетчик питьевой воды +# NodeMCU ESP8266 +#=============================================================================== +substitutions: + device_name: presence_bedroom + upper_devicename: PresenceBedroom + room: bedroom +#=============================================================================== +esphome: + name: ${device_name} + platform: ESP8266 + board: d1_mini + includes: + - presence.h + +# Настройки Wi-Fi +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "$upper_devicename Fallback" + password: !secret esphome_captive_pass + +captive_portal: + + +api: # Enable Home Assistant API + password: !secret esphome_pass + reboot_timeout: 0s + +ota: + password: !secret esphome_pass + +web_server: + port: 80 + +logger: + baud_rate: 0 + +mqtt: + broker: !secret mqtt_ip + username: !secret mqtt_user + password: !secret mqtt_password + +custom_component: +- lambda: |- + auto hb = new HappyBubblesDevice("${room}"); + App.register_component(hb); + return {hb}; + +switch: +- platform: custom + lambda: |- + auto ble_switch = new BleSwitch(); + App.register_component(ble_switch); + return {ble_switch}; + + switches: + name: "Ble scan enable" diff --git a/esphome/settings/presence_kladovka.yaml b/esphome/settings/presence_kladovka.yaml new file mode 100644 index 0000000..cb60388 --- /dev/null +++ b/esphome/settings/presence_kladovka.yaml @@ -0,0 +1,62 @@ +#=============================================================================== +# Прошивка для пат Happy Bubbles +# NodeMCU ESP8266 +#=============================================================================== +substitutions: + device_name: presence_kladovka + upper_devicename: PresenceKladovka + room: kladovka +#=============================================================================== +esphome: + name: ${device_name} + platform: ESP8266 + board: d1_mini + includes: + - presence.h + +# Настройки Wi-Fi +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "$upper_devicename Fallback" + password: !secret esphome_captive_pass + +captive_portal: + + +api: # Enable Home Assistant API + password: !secret esphome_pass + reboot_timeout: 0s + +ota: + password: !secret esphome_pass + +web_server: + port: 80 + +logger: + baud_rate: 0 + +mqtt: + broker: !secret mqtt_ip + username: !secret mqtt_user + password: !secret mqtt_password + +custom_component: +- lambda: |- + auto hb = new HappyBubblesDevice("${room}"); + App.register_component(hb); + return {hb}; + +switch: +- platform: custom + lambda: |- + auto ble_switch = new BleSwitch(); + App.register_component(ble_switch); + return {ble_switch}; + + switches: + name: "Ble scan enable" diff --git a/esphome/settings/presence_radar_bedroom.yaml b/esphome/settings/presence_radar_bedroom.yaml new file mode 100644 index 0000000..3e7cb94 --- /dev/null +++ b/esphome/settings/presence_radar_bedroom.yaml @@ -0,0 +1,250 @@ +substitutions: + # Set the device name + name: presence_radar_bedroom + # Set the friendly name + friendly_name: Presence Radar Bedroom + # Provide the wifi SSID + wifi: !secret wifi_24_name + #Provide the wifi password + wifi_pass: !secret wifi_24_pass + #Provide an OTA password: + ota_pass: !secret api_encryption_key + device_host: presence-radar-bedroom.lan + +### No modifications needed below here ### +#################################################################################### + +esphome: + name: $name + friendly_name: $friendly_name + platformio_options: + board_build.flash_mode: dio + board_build.f_cpu: 80000000L + name_add_mac_suffix: false + +wifi: + ssid: $wifi + password: $wifi_pass + power_save_mode: LIGHT + use_address: 192.168.1.109 #${device_host} + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + +captive_portal: + +web_server: + port: 80 + +esp32: + board: lolin_c3_mini + framework: + type: esp-idf + +# Enable Home Assistant API +api: + encryption: + key: $ota_pass + +ota: + - platform: esphome + password: $ota_pass + +logger: + level: DEBUG + +uart: + tx_pin: GPIO4 + rx_pin: GPIO3 + baud_rate: 256000 + parity: NONE + stop_bits: 1 + +ld2410: + +binary_sensor: + - platform: ld2410 + has_target: + name: Presence + has_moving_target: + name: Moving Target + has_still_target: + name: Still Target + out_pin_presence_status: + name: Out Pin Presence Status + +sensor: + - platform: ld2410 + light: + name: Light + moving_distance: + name : Moving Distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Detection Distance + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + - platform: internal_temperature + name: ESP Temperature + unit_of_measurement: °C + device_class: TEMPERATURE + update_interval: 30s + entity_category: "diagnostic" + - platform: uptime + name: Uptime + id: sys_uptime + update_interval: 10s + - platform: wifi_signal + name: WiFi RSSI + id: wifi_signal_db + update_interval: 30s + entity_category: "diagnostic" + +switch: + - platform: ld2410 + engineering_mode: + name: "LD2410 Engineering Mode" + bluetooth: + name: "LD2410 Bluetooth" + +number: + - platform: ld2410 + timeout: + name: Timeout + light_threshold: + name: Light Threshold + max_move_distance_gate: + name: Max Move Distance Gate + max_still_distance_gate: + name: Max Still Distance Gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +button: + - platform: ld2410 + factory_reset: + name: "LD2410 Factory Reset" + restart: + name: "LD2410 Reboot" + query_params: + name: "LD2410 Query Params" + - platform: restart + icon: mdi:power-cycle + name: "ESP Reboot" + +text_sensor: + - platform: ld2410 + version: + name: "LD2410 Firmware Version" + mac_address: + name: "LD2410 MAC Address" + +select: + - platform: ld2410 + distance_resolution: + name: "Distance Resolution" + baud_rate: + name: "Baud Rate" + light_function: + name: Light Function + out_pin_level: + name: Out Pin Level + +light: + - platform: status_led + name: sys_status + pin: + number: GPIO8 + inverted: True + internal: True + restore_mode: ALWAYS_OFF \ No newline at end of file diff --git a/esphome/settings/radar_esphome.yaml b/esphome/settings/radar_esphome.yaml new file mode 100644 index 0000000..8803445 --- /dev/null +++ b/esphome/settings/radar_esphome.yaml @@ -0,0 +1,225 @@ +esphome: + name: presence_bedroom + friendly_name: Presence Bedroom + +esp32: + board: esp32-c3-devkitm-1 + framework: + type: arduino + +# Enable logging +logger: + +# Enable Home Assistant API +api: + encryption: + key: !secret esphome_pass + +ota: + platform: esphome + password: !secret esphome_pass + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "Radar Hotspot" + password: !secret esphome_captive_pass + +captive_portal: + +web_server: + port: 80 + include_internal: true + +# --------------------------------------------------------------------------------- + +light: + - platform: binary + name: "Blue Status Light" + output: light_output + id: led_light + restore_mode: ALWAYS_OFF + +output: + - platform: gpio + pin: GPIO2 + id: light_output + +uart: + id: ld2410_uart + tx_pin: GPIO21 # TX + rx_pin: GPIO20 # RX + baud_rate: 256000 + parity: NONE + stop_bits: 1 + +ld2410: + uart_id: ld2410_uart + throttle: 1500ms + id: ld2410_comp + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out pin level + +button: + - platform: ld2410 + factory_reset: + name: "factory reset" + restart: + name: "restart" + query_params: + name: query params + +number: + - platform: ld2410 + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + light_threshold: + name: light threshold + +text_sensor: + - platform: ld2410 + version: + name: "presenece sensor version" + mac_address: + name: "presenece sensor mac address" + +switch: + - platform: ld2410 + engineering_mode: + name: "engineering mode" + bluetooth: + name: control Bluetooth + +sensor: + - platform: internal_temperature + name: "Internal Temperature" + + - platform: ld2410 + moving_distance: + name: "Moving distance (cm)" + still_distance: + name: "Still Distance (cm)" + moving_energy: + name: "Move Energy (%)" + still_energy: + name: "Still Energy (%)" + detection_distance: + name: "Distance Detection (cm)" + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + light: + name: light + +binary_sensor: + + - platform: ld2410 + has_target: + name: Presence + has_moving_target: + name: Moving Target + has_still_target: + name: Still Target diff --git a/esphome/settings/senseair.yaml b/esphome/settings/senseair.yaml new file mode 100644 index 0000000..0ccc150 --- /dev/null +++ b/esphome/settings/senseair.yaml @@ -0,0 +1,81 @@ +# Need to turn off logging because senseair uses uart and have conflicts with logging +logger: +# baud_rate: 0 + +esphome: + name: senseair + platform: ESP8266 + board: d1_mini +font: + - file: "OpenSans-Light.ttf" + id: open_sans_10 + size: 10 + - file: "OpenSans-Light.ttf" + id: open_sans_14 + size: 14 + - file: "OpenSans-Light.ttf" + id: open_sans_18 + size: 28 + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "Senseair Fallback Hotspot" + password: !secret esphome_captive_pass + +captive_portal: + +# Enable Home Assistant API +api: + password: !secret esphome_pass + +ota: + password: !secret esphome_pass + +# hass sensor =) +time: + - platform: homeassistant + id: homeassistant_time + timezone: Europe/Moscow + +sensor: + - platform: homeassistant + id: co2_mainroom_bed + entity_id: sensor.co2_mainroom_bed + + - platform: homeassistant + id: temperature_outside + entity_id: sensor.temperature_outside + +i2c: + scl: D1 + sda: D2 + scan: False + +# +display: + - platform: ssd1306_i2c + model: "SSD1306 128x64" + address: 0x3C + lambda: |- + it.strftime(90, 0, id(open_sans_14), "%H:%M", id(homeassistant_time).now()); + + if (id(temperature_outside).has_state()) { + it.printf(0, 0, id(open_sans_14), TextAlign::TOP_LEFT , "%.1f°C", id(temperature_outside).state); + } + + it.printf(0, 20, id(open_sans_14), TextAlign::TOP_LEFT , "CO"); + it.printf(22, 27, id(open_sans_10), TextAlign::TOP_LEFT , "2"); + + int value = 0; + if (id(co2_mainroom_bed).has_state() && !isnan(id(co2_mainroom_bed).state)) { + it.printf(35, 18, id(open_sans_18), TextAlign::TOP_LEFT , "%.0f", id(co2_mainroom_bed).state); + + value = (id(co2_mainroom_bed).state*100/2500)*128/100; + } + + it.rectangle(0, 57, 127, 7); + it.filled_rectangle(0, 57, value, 7); diff --git a/esphome/settings/svetlana_bedside_lamp.yaml b/esphome/settings/svetlana_bedside_lamp.yaml new file mode 100644 index 0000000..99b431a --- /dev/null +++ b/esphome/settings/svetlana_bedside_lamp.yaml @@ -0,0 +1,58 @@ +# -------------------------------------------------------------------------- +# Substitutions +# +# These are substitutions as used by the configuration packages from below. +# You can uncomment and update the ones that you want to modify. +# -------------------------------------------------------------------------- + +substitutions: + name: svetlana-bedside-lamp + friendly_name: Svetlana Bedside Lamp + #light_name: ${friendly_name} RGBWW Light + #light_mode_text_sensor_name: ${friendly_name} Light Mode + #default_transition_length: 800ms + +# -------------------------------------------------------------------------- +# Load configuration packages +# +# These provide a convenient way to compose your device configuration from +# some functional building blocks. Pick and mix the blocks that you need. +# +# For customization you can override options in your config or you can +# copy the contents of these packages directly in your config file as +# an example for your own customizations. +# +# Available packages are: +# - core.yaml : core components & hardware setup +# - behavior_default.yaml : default device behavior +# - ota_feedback.yaml : enable visual feedback during OTA updates +# - activate_preset_svc.yaml : 'activate_preset' service for Home Assistant +# -------------------------------------------------------------------------- + +packages: + bslamp2: + url: https://github.com/mmakaay/esphome-xiaomi_bslamp2 + ref: dev + files: + - packages/core.yaml + - packages/behavior_default.yaml + - packages/ota_feedback.yaml + - packages/activate_preset_svc.yaml + refresh: 0s + +# -------------------------------------------------------------------------- +# Use your own preferences for these components. +# -------------------------------------------------------------------------- + +wifi: + ssid: !secret wifi_24_name + password: !secret wifi_24_pass + use_address: ${name}.lan + +api: + encryption: + key: !secret api_encryption_key + +ota: + platform: esphome + password: !secret esphome_pass diff --git a/hass/settings/.HA_VERSION b/hass/settings/.HA_VERSION new file mode 100644 index 0000000..fcba00b --- /dev/null +++ b/hass/settings/.HA_VERSION @@ -0,0 +1 @@ +2026.2.3 \ No newline at end of file diff --git a/hass/settings/.ha_run.lock b/hass/settings/.ha_run.lock new file mode 100644 index 0000000..642bdda --- /dev/null +++ b/hass/settings/.ha_run.lock @@ -0,0 +1 @@ +{"pid": 106582, "version": 1, "ha_version": "2026.2.3", "start_ts": 1773083280.3675456} \ No newline at end of file diff --git a/hass/settings/automations.yaml b/hass/settings/automations.yaml new file mode 100644 index 0000000..43ec25c --- /dev/null +++ b/hass/settings/automations.yaml @@ -0,0 +1,368 @@ +- id: '1673173451954' + alias: Doorbell to Alice + description: '' + trigger: + - platform: state + entity_id: + - binary_sensor.0x00158d0003a5d05d_lock + - sensor.0x00158d0003a5d05d_action + to: doorbell + condition: [] + action: + - service: media_player.play_media + data: + media_content_type: text + media_content_id: Звонок в дверь! + extra: + volume_level: 0.6 + target: + device_id: + - b7ae13cff2fb04a56f1611a7bd9fdb5d + - e600d40f953494c41ec919d2148fd570 + - 49e5a1a4d554d924f61ecaa8968630bf + - 644968e9ab73b2930d141deb2aaec43f + - 37e299b4ee90a2e39ae7e04328439ff4 + - 8883de9fc71bef0b663c61faa4fbefb4 + - f6dbda8398f290149bc8cb48a858452c + - service: notify.telegram + data: + message: Звонок в дверь! + mode: single +- id: '1694942205257' + alias: Unavailable entity detection & notification + description: '' + use_blueprint: + path: gmlupatelli/unavailable_entities_notification.yaml + input: + actions: + - service: notify.telegram + data: + message: 'У вас есть недоступные устройства: {{entities}}' + data: + parse_mode: html + exclude: + device_id: + - fd4ed129390c68716ee6b5ca4ee6abe5 + - a5ddb672314ab72275816e22b91c1e0a +- id: '1696509659566' + alias: Turn On Mirror When Heater Is On + description: '' + trigger: + - platform: device + type: turned_on + device_id: 156bb319cb4e575d641e681a1775d672 + entity_id: c3ec9a6bd6247cdf3771e968416f7a1d + domain: switch + condition: [] + action: + - type: turn_on + device_id: a86a73f9c0cded2becbaea4b58a0c3ca + entity_id: ca2d574ba695471945a76eb9fbd14f7c + domain: switch + mode: single +- id: '1700337065799' + alias: Turn_on_AC_heater_evening + description: '' + trigger: + - platform: time + at: '23:00:00' + condition: + - condition: state + entity_id: sensor.tag_danil_iphone + state: mainroom + action: + - service: remote.send_command + target: + device_id: 3493f23712217b8085d5e05870b37fb4 + data: + command: power_on + device: ac + mode: single +- id: '1700337256838' + alias: Turn_off_AC_at_morning + description: '' + triggers: + - at: 07:00:00 + trigger: time + conditions: [] + actions: + - action: climate.set_hvac_mode + metadata: {} + target: + entity_id: climate.ac_bedroom + data: + hvac_mode: 'off' + - action: fan.turn_off + metadata: {} + data: {} + target: + entity_id: fan.ventiliatsionnaia_sistema + mode: single +- id: '1703092205692' + alias: Turn on curtain light when opened + description: '' + trigger: + - platform: device + device_id: 4b8180d9d908e128920565c0bd31e661 + domain: cover + entity_id: 03d25d0c4f844c78905fa58906f7a383 + type: opened + condition: [] + action: + - type: turn_on + device_id: b446ed5b21362d52ea95df7a716274cc + entity_id: 0ff56edad96924c2438d3df1fa7b27f7 + domain: switch + mode: single +- id: '1703092227298' + alias: Turn off curtain light when closed + description: '' + trigger: + - platform: device + device_id: 4b8180d9d908e128920565c0bd31e661 + domain: cover + entity_id: 03d25d0c4f844c78905fa58906f7a383 + type: closed + condition: [] + action: + - type: turn_off + device_id: b446ed5b21362d52ea95df7a716274cc + entity_id: 0ff56edad96924c2438d3df1fa7b27f7 + domain: switch + mode: single +- id: '1706383112067' + alias: Control Washer+Dishwasher + description: '' + trigger: + - platform: state + entity_id: + - switch.dishwasher + - switch.washer + from: 'on' + to: 'off' + condition: [] + action: + - service: switch.turn_on + metadata: {} + data: {} + target: + entity_id: + - switch.washer + - switch.dishwasher + mode: single +- id: '1722417271264' + alias: Bath ventilation timer + description: '' + trigger: + - entity_id: fan.ventilation_bath + for: + minutes: 15 + platform: state + to: 'on' + condition: [] + action: + - data: {} + entity_id: fan.ventilation_bath + service: homeassistant.turn_off + mode: single +- id: '1722417292820' + alias: Shower ventilation timer + description: '' + trigger: + - entity_id: fan.ventilation_shower + for: + minutes: 15 + platform: state + to: 'on' + condition: [] + action: + - data: {} + entity_id: fan.ventilation_shower + service: homeassistant.turn_off + mode: single +- id: '1728058650984' + alias: Childroom Turn Off Ac in the morning + description: '' + trigger: + - platform: time + at: 07:00:00 + condition: [] + action: + - repeat: + count: 2 + sequence: + - data: + command: power_off_2 + device: ac + action: remote.send_command + target: + device_id: d217abb5dd76093411769c4677b6b002 + mode: single +- id: '1728058703218' + alias: Childroom Turn On Ac in the evening + description: '' + trigger: + - platform: time + at: '23:00:00' + condition: + - condition: state + entity_id: sensor.tag_danil_iphone + state: mainroom + action: + - data: + command: power_on + device: ac + action: remote.send_command + target: + device_id: d217abb5dd76093411769c4677b6b002 + mode: single +- id: '1752861666697' + alias: Громкость по умолчанию Спальня + description: '' + use_blueprint: + path: gist.githubusercontent.com/yandex_station_default_volume.yaml + input: + yandex_station: media_player.yandex_station_m00fhe2003seqr + number_volume: 5 +- id: '1752930144657' + alias: Громкость по умолчанию Кухня + description: '' + use_blueprint: + path: gist.githubusercontent.com/yandex_station_default_volume.yaml + input: + yandex_station: media_player.yandex_station_m00r46000m53db + number_volume: 5 +- id: '1752930171971' + alias: Громкость по умолчанию Душевая + description: '' + use_blueprint: + path: gist.githubusercontent.com/yandex_station_default_volume.yaml + input: + yandex_station: media_player.yandex_station_ff98f029ab48468d28c557be + number_volume: 5 +- id: '1752930196035' + alias: Громкость по умолчанию Детская + description: '' + use_blueprint: + path: gist.githubusercontent.com/yandex_station_default_volume.yaml + input: + yandex_station: media_player.yandex_station_940050344110102101cb +- id: '1752930212912' + alias: Громкость по умолчанию Ванна + description: '' + use_blueprint: + path: gist.githubusercontent.com/yandex_station_default_volume.yaml + input: + yandex_station: media_player.yandex_station_ly00000000000008674600007209d3f5 + number_volume: 5 +- id: '1753212993227' + alias: Low Battery Notifications & Actions + description: '' + use_blueprint: + path: Blackshome/low-battery-notifications-and-actions.yaml + input: + include_time: time_enabled + time: '10:00:00' + battery_level: 10 + exclude_sensors: + entity_id: + - sensor.monsterphone13_battery_level + include_easy_notify: disabled_easy_notify + notify_device: [] + include_persistent_notification: disabled_persistent_notification + include_custom_actions: enable_custom_actions + custom_actions: + - action: notify.telegram_monster + data: + message: 'Садится батарея в датчиках: + + + {{all_sensors}}' + data: + parse_mode: html +- id: '1767036452762' + alias: Дверь в гостиную - закрыта + description: '' + triggers: + - trigger: state + entity_id: + - binary_sensor.door_mainroom_slider_contact + to: + - 'off' + from: + - 'on' + conditions: [] + actions: + - action: light.turn_off + metadata: {} + target: + entity_id: light.plug_yolka + data: {} + - action: input_boolean.turn_on + metadata: {} + target: + entity_id: input_boolean.guest_mode + data: {} + mode: single +- id: '1767036484608' + alias: Дверь в гостиную - открыта + description: '' + triggers: + - trigger: state + entity_id: + - binary_sensor.door_mainroom_slider_contact + to: + - 'on' + from: + - 'off' + conditions: [] + actions: + - action: light.turn_on + metadata: {} + target: + entity_id: light.plug_yolka + data: {} + - action: input_boolean.turn_off + metadata: {} + target: + entity_id: input_boolean.guest_mode + data: {} + mode: single +- id: '1768899857603' + alias: Детская - Утром отключаем отопление + description: '' + triggers: + - trigger: time + at: 08:00:00 + conditions: + - condition: state + entity_id: input_boolean.radiator_enabled + state: + - 'on' + actions: + - action: climate.set_temperature + metadata: {} + target: + entity_id: climate.radiator_childroom + data: + temperature: 22 + mode: single +- id: '1768899913660' + alias: Детская - Вечером включаем отопление + description: '' + triggers: + - trigger: time + at: '22:00:00' + conditions: + - condition: state + entity_id: input_boolean.radiator_enabled + state: + - 'on' + actions: + - action: climate.set_temperature + metadata: {} + target: + entity_id: climate.radiator_childroom + data: + temperature: 24 + mode: single diff --git a/hass/settings/blueprints/automation/Blackshome/low-battery-notifications-and-actions.yaml b/hass/settings/blueprints/automation/Blackshome/low-battery-notifications-and-actions.yaml new file mode 100644 index 0000000..0daeb0d --- /dev/null +++ b/hass/settings/blueprints/automation/Blackshome/low-battery-notifications-and-actions.yaml @@ -0,0 +1,1072 @@ +blueprint: + name: Low Battery Notifications & Actions + description: "# \U0001FAAB Low Battery Notifications & Actions\n**Version: 2.9**\n\n\U0001F680 + Stay Charged, Stay Smart! Let's automate and take charge of your battery maintenance!\U0001F50B⚡\n\n**If + you like my blueprints, and would like to show your support or just say thank + you?** [Click Here](https://www.paypal.com/donate/?hosted_button_id=WAZS3QSDTPGA8) + \U0001F642\n\n
The Automation Process: \U0001F448\n\n + \ - **Trigger Options:**\n - **Button Helper:** Press a button in the user + interface (UI) to check battery levels.\n - **Time & Day:** Automatically periodically + checks battery levels on any given time and day.\n\n - **Battery Sensors:**\n + \ - Automatically detects all battery sensors used in Home Assistant.\n - + Set a battery low warning percentage to identify all batteries below this level.\n + \ - Detects Low, unavailable & unknown batteries.\n - Excludes specific battery + sensors from notifications.\n - Create a custom group of battery sensors with + its own independent low battery warning percentage.\n\n - **Notification Options:**\n + \ - Opt to send messages to one or multiple devices.\n - Opt to display your + notifications directly within the Home Assistant user interface (UI).\n - Choose + which battery sensors to use, and then select their states from one of three preset + messages:\n - Battery Low % Level + Low Battery Level + Unavailable + Unknown.\n + \ - Battery Low % Level & Low Battery Level.\n - Unavailable or Unknown + Sensors.\n - Opt to receive a confirmation message if all batteries are okay.\n + \ - Opt to use action buttons in your device notification that allows you to + add your low batteries to a To-Do list.\n - Compatible with Apple iOS and Android + devices.\n\n - **Custom Actions:**\n - Customize actions to suit your preferences + and needs, enhancing the automations functionality.\n - Utilize 10 custom predetermined + battery sensors within the blueprint for more tailored and precise actions.\n + \ - Configure custom actions to include features like customizing notifications + or playing announcements on platforms like The Voice (HA), Google, Alexa, TTS, + etc.\n\n - **Custom Conditions:**\n - Enter any custom conditions to further + customize the automation process.\n
\n\nNeed help? \n- FAQ: [Click Here](https://community.home-assistant.io/t/653754/2?u=blacky)\n- + Community Support Including Updates: [Click Here](https://community.home-assistant.io/t/653754)\n\nRequired + = *\n" + domain: automation + input: + trigger_settings: + name: Triggers * + icon: mdi:cog-outline + description: 'You must select at least one trigger * + + ' + collapsed: true + input: + include_button: + name: Use The Button Helper Trigger (Optional) + description: 'Select if you would like a button helper to trigger the automation. + This is useful if you would like to check the battery levels by pressing + a button in the UI. + + + For more information on how to create a button helper [Click Here](https://community.home-assistant.io/t/653754/4) + + ' + default: disable_button_trigger + selector: + select: + options: + - label: Use a button trigger + value: enable_button_trigger + - label: Dont use a button trigger + value: disable_button_trigger + custom_value: false + multiple: false + sort: false + button_entity: + name: Button Helper + description: 'Input your button helper. + + ' + default: [] + selector: + entity: + filter: + - domain: + - input_button + multiple: false + include_time: + name: Use The Time Trigger (Optional) + description: 'Select if you would like to use the time trigger. This is + useful if you would like to periodically check the battery levels on any + given time and day. + + ' + default: time_disabled + selector: + select: + options: + - label: Enable the time options + value: time_enabled + - label: Disable the time options + value: time_disabled + custom_value: false + multiple: false + sort: false + time: + name: Time + description: 'Set the time you would like to run the automation. + + ' + default: 00:00:00 + selector: + time: {} + weekday_options: + name: Weekdays + description: 'Select the days of the week you would like the automation + to run. + + ' + default: + - mon + - tue + - wed + - thu + - fri + - sat + - sun + selector: + select: + multiple: true + mode: list + options: + - label: Monday + value: mon + - label: Tuesday + value: tue + - label: Wednesday + value: wed + - label: Thursday + value: thu + - label: Friday + value: fri + - label: Saturday + value: sat + - label: Sunday + value: sun + custom_value: false + sort: false + battery_settings: + name: Battery Settings + icon: mdi:battery-alert-variant-outline + collapsed: true + input: + battery_level: + name: Battery Low Warning Level + description: 'Set the battery low warning percentage to identify all batteries + below this level. + + ' + default: 20 + selector: + number: + min: 1.0 + max: 100.0 + step: 1.0 + unit_of_measurement: '%' + mode: slider + exclude_sensors: + name: Excluded Battery Sensors (Optional) + description: 'Choose the battery sensors to be excluded when the automation + runs. The automation will attempt to detect all battery sensors used in + Home Assistant, including those of your phones, tablets, etc. However, + it may not always be necessary or desirable to monitor these batteries. + Selecting these batteries here will exclude them from your notifications. + + + **NOTE** - You can only use entities and labels. Areas and devices are + not supported. + + ' + default: + entity_id: [] + selector: + target: + entity: + - device_class: + - battery + custom_group: + name: Battery Sensors - Custom Group (Optional) + description: 'You can select a custom group of battery sensors to be included + in your notifications. This custom group has its own independent ''Battery + Low Warning Level'' setting below. When using all batteries along with + the custom group in a single automation, it is recommended to exclude + the custom group of batteries specified above in the ''Excluded Battery + Sensors'' section. + + + Excluded battery sensors are not linked to this custom group. + + + **NOTE** - You can only use entities and labels. Areas and devices are + not supported. + + ' + default: + entity_id: [] + selector: + target: + entity: + - device_class: + - battery + custom_group_battery_level: + name: Custom Group - Battery Low Warning Level + description: 'Set the battery low warning percentage to identify all batteries + below this level. + + ' + default: 20 + selector: + number: + min: 1.0 + max: 100.0 + step: 1.0 + unit_of_measurement: '%' + mode: slider + easy_notify_settings: + name: Easy Notify + icon: mdi:bell-check-outline + collapsed: true + input: + include_easy_notify: + name: Use The Easy Notify - Device Notification Options (Optional) + description: 'Enabling this option will send notifications to the selected + devices below. It allows you to choose which battery sensors to use, and + then select their states from one of three preset messages. You can also + opt to receive a confirmation message if all batteries are okay. For further + customization or to integrate with voice assistants like HA, Google, Alexa, + TTS, etc., please explore the custom action options below. + + ' + default: disabled_easy_notify + selector: + select: + options: + - label: Enable Device Notification + value: enable_easy_notify + - label: Enable Device Notification + Okay Confirmation Message + value: enable_easy_okay_notify + - label: Disable Device Notification + value: disabled_easy_notify + custom_value: false + multiple: false + sort: false + include_persistent_notification: + name: Use The Easy Notify - UI Notification Options (Optional) + description: 'Enabling this option will display notifications directly within + the Home Assistant user interface (UI). It allows you to choose which + battery sensors to use, and then select their states from one of three + preset messages. You can also opt to receive a confirmation message in + the UI if all batteries are okay. For further customization or to integrate + with voice assistants like HA, Google, Alexa, TTS, etc., please explore + the custom action options below. + + ' + default: disabled_persistent_notification + selector: + select: + options: + - label: Enable UI Notification + value: enable_persistent_notification + - label: Enable UI Notification + Okay Confirmation Message + value: enable_persistent_okay_notification + - label: Disable UI Notification + value: disabled_persistent_notification + custom_value: false + multiple: false + sort: false + sensor_selection: + name: Sensor Selection + description: 'Please select which sensors you would like to include in your + notifications. + + ' + default: enable_all_sensors + selector: + select: + options: + - label: Use Battery Sensors - Custom Group + value: enable_battery_sensors_custom_group + - label: Use All Battery Sensors Minus Excluded Battery Sensors + value: enable_all_sensors + - label: Use All Battery Sensors Minus Excluded Battery Sensors + Custom + Group + value: enable_all_and_custom_group_sensors + custom_value: false + multiple: false + sort: false + notify_device: + name: Devices Notified (Optional) + description: 'If you''ve enabled device notifications above, please select + the devices to receive the notifications. + + ' + default: [] + selector: + device: + filter: + - integration: mobile_app + multiple: true + notify_title: + name: Title + description: 'Enter in the notification title of your message. + + ' + default: Low Battery Notification + selector: + text: {} + notify_message: + name: Message + description: 'Choose one of the three preset messages from the dropdown + menu. + + + **1 - Battery Low % Level + Low Battery Level + Unavailable + Unknown** + Includes sensors with battery percentages below the low warning level, + in a low battery state, or marked as unavailable or unknown. + + + **2 - Battery Low % Level & Low Battery Level** Includes sensors with + battery percentages below the low warning level or in a low battery state. + + + **3 - Unavailable or Unknown Sensors** Includes sensors that are unavailable + or have an unknown state. + + ' + default: all_sensors + selector: + select: + mode: dropdown + options: + - label: 1 - Battery Low % Level + Low Battery Level + Unavailable + + Unknown + value: all_sensors + - label: 2 - Battery Low % Level & Low Battery Level + value: sensors + - label: 3 - Unavailable or Unknown Sensors + value: unavailable_sensors + custom_value: false + multiple: false + sort: false + notify_okay_message: + name: Okay Confirmation Message + description: 'Enter in the notification message you would like to receive + when all your batteries are okay. + + ' + default: YES! All batteries are okay :) + selector: + text: {} + notify_interruption_level: + name: Interruption Level - iOS Only + description: 'On devices running iOS 15 and later, you can configure the + interruption level for your notifications to ensure they are delivered + according to your preferences. Choose the desired interruption level from + the dropdown list. Critical and time-sensitive notifications must be turned + ON within the Home Assistant App, and time-sensitive notifications must + be allowed in your Focus settings. + + + For more information on interruption levels [Click Here](https://community.home-assistant.io/t/653754/192?u=blacky) + + ' + default: active + selector: + select: + mode: dropdown + options: + - label: Default + value: active + - label: Critical Notifications + value: critical + - label: Time Sensitive Notifications + value: time-sensitive + - label: Quiet Notifications Without Waking Screen + value: passive + custom_value: false + multiple: false + sort: false + notify_sound: + name: Notification Sound - iOS Only + description: 'The Home Assistant app for iOS includes built-in notification + sounds, and you can import sounds from your iOS device into the app. Custom + sounds can also be added via iTunes or from your cloud storage. When entering + a sound, ensure you use the full filename, including the extension. + + + For more information on using sounds in the Home Assistant app, [Click + Here](https://community.home-assistant.io/t/653754/192?u=blacky) + + ' + default: [] + selector: + text: {} + notify_data: + name: Android Only Options (Optional) + description: '**High Priority** notifications ensure that you receive important + alerts immediately. These notifications bypass normal delivery delays + and are delivered instantly to your device. + + + **Sticky Notification** ensure that important alerts are not dismissed + when selected. They remain on your notification screen until you actively + clear or swipe them away, ensuring that critical information stays visible + until addressed. + + + **Notification Channel** allow you to easily organize different notification + settings, including notification sounds, vibrations, and other device-specific + features. If you choose this option, please enter your desired channel + name below. + + ' + default: [] + selector: + select: + multiple: true + options: + - label: High Priority + value: high_priority + - label: Sticky Notification + value: sticky + - label: Notification Channel + value: channel + custom_value: false + sort: false + notify_channel: + name: Notification Channel - Android Only + description: 'If you have chosen to use a notification channel, simply enter + the name of an existing channel to apply its pre-configured settings. + If you don''t have a channel yet, you can create a new one by entering + a name here. When your automation sends the first notification, it will + create the channel on your device. After the channel is created, you can + customize its notification settings to your preference directly on your + device. + + + For more information on notification channels [Click Here](https://community.home-assistant.io/t/653754/192?u=blacky) + + ' + default: [] + selector: + text: {} + action_buttons_settings: + name: Action Buttons + icon: mdi:gesture-tap-button + collapsed: true + input: + notify_action_buttons: + name: Easy Notify - Action Buttons (Optional) + description: 'Enhance your device notifications with an actionable To-Do + list button feature. With just a hold, release and tap, effortlessly add + selected items from your low battery notification to any of your chosen + To-Do lists. Plus, for added assurance, you can opt to receive a confirmation + message confirming the action. + + ' + default: disabled_notify_action_buttons + selector: + select: + options: + - label: Add To-Do List Action Buttons + value: enable_to_do_list_button + - label: Add To-Do List Action Buttons + Confirmation Message + value: enable_to_do_list_button_and_confirmation + - label: Disable Action Buttons + value: disabled_notify_action_buttons + custom_value: false + multiple: false + sort: false + to_do_list: + name: Easy Notify Action Buttons - To-Do List Selection + description: 'Choose the To-Do list where you''d like to add your task. + + + **Note** - The default shopping list isn''t compatible due to its lack + of description capability. However, all other To-Do lists are fully supported. + + ' + default: [] + selector: + entity: + filter: + - domain: + - todo + multiple: false + to_do_task_title: + name: Easy Notify Action Buttons - Task Title + description: 'When the action button is pressed, it will add a task to your + chosen To-Do list. Please enter the title you would like for your To-Do + task + + ' + default: Batteries To Be Replaced + selector: + text: {} + action_button_to_do: + name: Easy Notify Action Buttons - Action Button + description: 'Please specify the name for the action button to be displayed + in the notification. When pressed this button effortlessly adds a task + to your chosen To-Do list. + + ' + default: Add To To-Do List! + selector: + text: {} + action_button_cancel: + name: Easy Notify Action Buttons - Cancel Button + description: 'Please specify the name for the cancel button to be displayed + in the notification. This button allows you to cancel the task addition + process. + + ' + default: Cancel + selector: + text: {} + action_button_confirmation_title: + name: Easy Notify Action Buttons - Action & Cancel Button Confirmation Title + description: 'Please enter in a title for the confirmation notification + message when an action button or a cancel button is pressed. + + ' + default: Low Battery Confirmation + selector: + text: {} + action_button_confirmation_message: + name: Easy Notify Action Buttons - Action Button Confirmation Message + description: 'Please enter in the message for the confirmation notification + message when the action button is pressed. + + ' + default: Your low batteries have been added to your To-Do list + selector: + text: {} + cancel_action_button_confirmation_message: + name: Easy Notify Action Buttons - Cancel Button Confirmation Message + description: 'Please enter in the message for the confirmation notification + message when the cancel button is pressed. + + ' + default: Cancelled - Nothing was added to your To-Do list + selector: + text: {} + custom_actions_settings: + name: Custom Actions + icon: mdi:code-tags + collapsed: true + input: + include_custom_actions: + name: Use The Custom Action Options (Optional) + description: 'You can add any custom actions you''d like to run when the + automation is triggered. This can include customizing notifications or + playing announcements on platforms like The Voice (HA), Google, Alexa, + TTS, etc. + + ' + default: disabled_custom_actions + selector: + select: + options: + - label: Enable Custom Actions + value: enable_custom_actions + - label: Disable Custom Actions + value: disabled_custom_actions + custom_value: false + multiple: false + sort: false + custom_actions: + name: Custom Actions + description: 'Add your custom actions that you''d like to run when the automation + is triggered. Below are 10 sensors you can include in your actions, depending + on your preferences. + + + **All Battery Sensors Minus Excluded** + + 1 - Use "{{all_sensors}}" for - Battery Low % Level + Low Battery Level + + Unavailable + Unknown. + + 2 - Use "{{sensors}}" for - Battery Low % Level & Low Battery Level. + + 3 - Use "{{unavailable_sensors}}" for - Unavailable or Unknown Sensors. + + 4 - Use "{{sensors_names}}" for - Battery Low % Level & Low Battery Level + - Sensor name only. + + 5 - Use "{{unavailable_sensors_names}}" for - Unavailable or Unknown Sensors + - Sensor name only. + + + **Battery Sensors - Custom Group** + + 1 - Use "{{all_sensors_custom_group}}" for - Battery Low % Level + Low + Battery Level + Unavailable + Unknown. + + 2 - Use "{{sensors_custom_group}}" for - Battery Low % Level & Low Battery + Level. + + 3 - Use "{{unavailable_sensors_custom_group}}" for - Unavailable or Unknown + Sensors. + + 4 - Use "{{sensors_names_custom_group}}" for - Battery Low % Level & Low + Battery Level - Sensor name only. + + 5 - Use "{{unavailable_sensors_names_custom_group}}" for - Unavailable + or Unknown Sensors - Sensor name only. + + + For more information on how to use these sensors in your actions [Click + Here](https://community.home-assistant.io/t/653754/21) + + ' + default: [] + selector: + action: {} + global_conditions_settings: + name: Global Conditions + icon: mdi:earth + collapsed: true + input: + global_conditions: + name: Global Conditions + description: 'Enter any global conditions you would like to apply to the + automation. + + ' + default: [] + selector: + condition: {} + source_url: https://gist.github.com/Blackshome/4010fb83bb8c19b5fa1425526c6ff0e2 +mode: restart +max_exceeded: silent +variables: + include_button: !input include_button + button_entity: !input button_entity + include_time: !input include_time + time: !input time + weekday_options: !input weekday_options + battery_level: !input battery_level + exclude_sensors: !input exclude_sensors + custom_group: !input custom_group + custom_group_battery_level: !input custom_group_battery_level + include_easy_notify: !input include_easy_notify + include_persistent_notification: !input include_persistent_notification + sensor_selection: !input sensor_selection + notify_device: !input notify_device + notify_title: !input notify_title + notify_message: !input notify_message + notify_okay_message: !input notify_okay_message + notify_interruption_level: !input notify_interruption_level + notify_sound: !input notify_sound + notify_data: !input notify_data + notify_channel: !input notify_channel + notify_action_buttons: !input notify_action_buttons + to_do_list: !input to_do_list + to_do_task_title: !input to_do_task_title + action_button_to_do: !input action_button_to_do + action_button_cancel: !input action_button_cancel + action_button_confirmation_title: !input action_button_confirmation_title + action_button_confirmation_message: !input action_button_confirmation_message + cancel_action_button_confirmation_message: !input cancel_action_button_confirmation_message + include_custom_actions: !input include_custom_actions + global_conditions: !input global_conditions + all_exclude_sensors: "{% set label_entities_list = label_entities(exclude_sensors.label_id) + if exclude_sensors.label_id is defined and label_entities(exclude_sensors.label_id) + is not none else [] %} {% set entity_list = exclude_sensors.entity_id if exclude_sensors.entity_id + is defined else [] %} {% if entity_list is string %}\n {% set entity_list = [entity_list] + %}\n{% endif %} {% set all_exclude_sensors = label_entities_list + entity_list + %} {{ all_exclude_sensors }}" + custom_group_sensors: "{% set label_entities_list = label_entities(custom_group.label_id) + if custom_group.label_id is defined and label_entities(custom_group.label_id) + is not none else [] %} {% set entity_list = custom_group.entity_id if custom_group.entity_id + is defined else [] %} {% if entity_list is string %}\n {% set entity_list = [entity_list] + %}\n{% endif %} {% set custom_group_sensors = label_entities_list + entity_list + %} {{ custom_group_sensors }}" + device_message_data: "{% set message = namespace(data={}) %} {% set push = namespace(data={}) + %} {% if notify_interruption_level in ['active', 'critical', 'time-sensitive', + 'passive'] %}\n {% set push.data = dict(push.data, **{ 'interruption-level': + notify_interruption_level }) %}\n{% endif %} {% if notify_sound != [] %}\n {% + set push.data = dict(push.data, **{ 'sound': notify_sound }) %}\n{% endif %} {% + if push.data %}\n {% set message.data = dict(message.data, **{ 'push': push.data + }) %}\n{% endif %} {% if 'high_priority' in notify_data %}\n {% set message.data + = dict(message.data, **{ 'ttl': 0, 'priority': 'high' }) %}\n{% endif %} {% if + 'channel' in notify_data %}\n {% set message.data = dict(message.data, **{ 'channel': + notify_channel }) %}\n{% endif %} {% if 'sticky' in notify_data %}\n {% set message.data + = dict(message.data, **{ 'sticky': \"true\" }) %}\n{% endif %} {{ message.data + }}" + device_message_data_action_button: "{% set message = namespace(data={}) %} {% set + push = namespace(data={}) %} {% if notify_interruption_level in ['active', 'critical', + 'time-sensitive', 'passive'] %}\n {% set push.data = dict(push.data, **{ 'interruption-level': + notify_interruption_level }) %}\n{% endif %} {% if notify_sound != [] %}\n {% + set push.data = dict(push.data, **{ 'sound': notify_sound }) %}\n{% endif %} {% + if push.data %}\n {% set message.data = dict(message.data, **{ 'push': push.data + }) %}\n{% endif %} {% if 'high_priority' in notify_data %}\n {% set message.data + = dict(message.data, **{ 'ttl': 0, 'priority': 'high' }) %}\n{% endif %} {% if + 'channel' in notify_data %}\n {% set message.data = dict(message.data, **{ 'channel': + notify_channel }) %}\n{% endif %} {% if 'sticky' in notify_data %}\n {% set message.data + = dict(message.data, **{ 'sticky': \"true\" }) %}\n{% endif %} {% set actions + = [\n {'action': action_button_to_do, 'title': action_button_to_do},\n {'action': + action_button_cancel, 'title': action_button_cancel}\n] %}\n {% set message.data + = dict(message.data, **{ 'actions': actions }) %}\n{{ message.data }}" + device_confirmation_message_data: "{% set message = namespace(data={}) %} {% set + push = namespace(data={}) %} {% if notify_interruption_level in ['active', 'critical', + 'time-sensitive', 'passive'] %}\n {% set push.data = dict(push.data, **{ 'interruption-level': + notify_interruption_level }) %}\n{% endif %} {% if notify_sound != [] %}\n {% + set push.data = dict(push.data, **{ 'sound': notify_sound }) %}\n{% endif %} {% + if push.data %}\n {% set message.data = dict(message.data, **{ 'push': push.data + }) %}\n{% endif %} {% if 'high_priority' in notify_data %}\n {% set message.data + = dict(message.data, **{ 'ttl': 0, 'priority': 'high' }) %}\n{% endif %} {% if + 'channel' in notify_data %}\n {% set message.data = dict(message.data, **{ 'channel': + notify_channel }) %}\n{% endif %} {% if 'sticky' in notify_data %}\n {% set message.data + = dict(message.data, **{ 'sticky': \"true\" }) %}\n{% endif %}\n {% set message.data + = dict(message.data, **{ 'url': \"/todo?entity_id=\" ~ to_do_list, 'clickAction': + \"/todo?entity_id=\" ~ to_do_list }) %}\n{{ message.data }}" + all_sensors: "{% set low_battery = namespace(sensors=[]) %} {% for state in states.sensor\n + \ | rejectattr('attributes.device_class', 'undefined')\n | selectattr('attributes.device_class', + '==', 'battery') %}\n {% if 0 <= state.state | int(-1) <= battery_level | int + and state.entity_id not in all_exclude_sensors %}\n {% set low_battery.sensors + = low_battery.sensors + [state.name ~ ' @ ' ~ state.state ~ '%'] %}\n {% endif + %}\n{% endfor %} {% for state in states.binary_sensor\n | rejectattr('attributes.device_class', + 'undefined')\n | selectattr('attributes.device_class', '==', 'battery')\n | + selectattr('state', 'in', ['on', 'low']) %}\n {% if state.entity_id not in all_exclude_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name ~ ' is + low!'] %}\n {% endif %}\n{% endfor %} {% for state in states.sensor\n | rejectattr('attributes.device_class', + 'undefined')\n | selectattr('attributes.device_class', '==', 'battery')\n | + selectattr('state', 'in', ['unavailable', 'unknown']) %}\n {% if state.entity_id + not in all_exclude_sensors %}\n {% set low_battery.sensors = low_battery.sensors + + [state.name ~ ' is ' ~ state.state ~ '!'] %}\n {% endif %}\n{% endfor %} {% + for state in states.binary_sensor\n | rejectattr('attributes.device_class', 'undefined')\n + \ | selectattr('attributes.device_class', '==', 'battery')\n | selectattr('state', + 'in', ['unavailable', 'unknown']) %}\n {% if state.entity_id not in all_exclude_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name ~ ' is + ' ~ state.state ~ '!'] %}\n {% endif %}\n{% endfor %} {{ low_battery.sensors + | join(', ') }}" + sensors: "{% set low_battery = namespace(sensors=[]) %} {% for state in states.sensor\n + \ | rejectattr('attributes.device_class', 'undefined')\n | selectattr('attributes.device_class', + '==', 'battery') %}\n {% if 0 <= state.state | int(-1) <= battery_level | int + and state.entity_id not in all_exclude_sensors %}\n {% set low_battery.sensors + = low_battery.sensors + [state.name ~ ' @ ' ~ state.state ~ '%'] %}\n {% endif + %}\n{% endfor %} {% for state in states.binary_sensor\n | rejectattr('attributes.device_class', + 'undefined')\n | selectattr('attributes.device_class', '==', 'battery')\n | + selectattr('state', 'in', ['on', 'low']) %}\n {% if state.entity_id not in all_exclude_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name ~ ' is + low!'] %}\n {% endif %}\n{% endfor %} {{ low_battery.sensors | join(', ') }}" + unavailable_sensors: "{% set low_battery = namespace(sensors=[]) %} {% for state + in states.sensor\n | rejectattr('attributes.device_class', 'undefined')\n | + selectattr('attributes.device_class', '==', 'battery')\n | selectattr('state', + 'in', ['unavailable', 'unknown']) %}\n {% if state.entity_id not in all_exclude_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name ~ ' is + ' ~ state.state ~ '!'] %}\n {% endif %}\n{% endfor %} {% for state in states.binary_sensor\n + \ | rejectattr('attributes.device_class', 'undefined')\n | selectattr('attributes.device_class', + '==', 'battery')\n | selectattr('state', 'in', ['unavailable', 'unknown']) %}\n + \ {% if state.entity_id not in all_exclude_sensors %}\n {% set low_battery.sensors + = low_battery.sensors + [state.name ~ ' is ' ~ state.state ~ '!'] %}\n {% endif + %}\n{% endfor %} {{ low_battery.sensors | join(', ') }}" + sensors_names: "{% set low_battery = namespace(sensors=[]) %} {% for state in states.sensor\n + \ | rejectattr('attributes.device_class', 'undefined')\n | selectattr('attributes.device_class', + '==', 'battery') %}\n {% if 0 <= state.state | int(-1) <= battery_level | int + and state.entity_id not in all_exclude_sensors %}\n {% set low_battery.sensors + = low_battery.sensors + [state.name] %}\n {% endif %}\n{% endfor %} {% for state + in states.binary_sensor\n | rejectattr('attributes.device_class', 'undefined')\n + \ | selectattr('attributes.device_class', '==', 'battery')\n | selectattr('state', + 'in', ['on', 'low']) %}\n {% if state.entity_id not in all_exclude_sensors %}\n + \ {% set low_battery.sensors = low_battery.sensors + [state.name] %}\n {% endif + %}\n{% endfor %} {{low_battery.sensors | join(', ')}}" + unavailable_sensors_names: "{% set low_battery = namespace(sensors=[]) %} {% for + state in states.sensor\n | rejectattr('attributes.device_class', 'undefined')\n + \ | selectattr('attributes.device_class', '==', 'battery')\n | selectattr('state', + 'in', ['unavailable', 'unknown']) %}\n {% if state.entity_id not in all_exclude_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name] %}\n {% + endif %}\n{% endfor %} {% for state in states.binary_sensor\n | rejectattr('attributes.device_class', + 'undefined')\n | selectattr('attributes.device_class', '==', 'battery')\n | + selectattr('state', 'in', ['unavailable', 'unknown']) %}\n {% if state.entity_id + not in all_exclude_sensors %}\n {% set low_battery.sensors = low_battery.sensors + + [state.name] %}\n {% endif %}\n{% endfor %} {{low_battery.sensors | join(', + ')}}" + all_sensors_custom_group: "{% set low_battery = namespace(sensors=[]) %} {% for + state in states.sensor\n | rejectattr('attributes.device_class', 'undefined')\n + \ | selectattr('attributes.device_class', '==', 'battery') %}\n {% if 0 <= state.state + | int(-1) <= custom_group_battery_level | int and state.entity_id in custom_group_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name ~ ' @ ' + ~ state.state ~ '%'] %}\n {% endif %}\n{% endfor %} {% for state in states.binary_sensor\n + \ | rejectattr('attributes.device_class', 'undefined')\n | selectattr('attributes.device_class', + '==', 'battery')\n | selectattr('state', 'in', ['on', 'low']) %}\n {% if state.entity_id + in custom_group_sensors %}\n {% set low_battery.sensors = low_battery.sensors + + [state.name ~ ' is low!'] %}\n {% endif %}\n{% endfor %} {% for state in states.sensor\n + \ | rejectattr('attributes.device_class', 'undefined')\n | selectattr('attributes.device_class', + '==', 'battery')\n | selectattr('state', 'in', ['unavailable', 'unknown']) %}\n + \ {% if state.entity_id in custom_group_sensors %}\n {% set low_battery.sensors + = low_battery.sensors + [state.name ~ ' is ' ~ state.state ~ '!'] %}\n {% endif + %}\n{% endfor %} {% for state in states.binary_sensor\n | rejectattr('attributes.device_class', + 'undefined')\n | selectattr('attributes.device_class', '==', 'battery')\n | + selectattr('state', 'in', ['unavailable', 'unknown']) %}\n {% if state.entity_id + in custom_group_sensors %}\n {% set low_battery.sensors = low_battery.sensors + + [state.name ~ ' is ' ~ state.state ~ '!'] %}\n {% endif %}\n{% endfor %} {{ + low_battery.sensors | join(', ') }}" + sensors_custom_group: "{% set low_battery = namespace(sensors=[]) %} {% for state + in states.sensor\n | rejectattr('attributes.device_class', 'undefined')\n | + selectattr('attributes.device_class', '==', 'battery') %}\n {% if 0 <= state.state + | int(-1) <= custom_group_battery_level | int and state.entity_id in custom_group_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name ~ ' @ ' + ~ state.state ~ '%'] %}\n {% endif %}\n{% endfor %} {% for state in states.binary_sensor\n + \ | rejectattr('attributes.device_class', 'undefined')\n | selectattr('attributes.device_class', + '==', 'battery')\n | selectattr('state', 'in', ['on', 'low']) %}\n {% if state.entity_id + in custom_group_sensors %}\n {% set low_battery.sensors = low_battery.sensors + + [state.name ~ ' is low!'] %}\n {% endif %}\n{% endfor %} {{ low_battery.sensors + | join(', ') }}" + unavailable_sensors_custom_group: "{% set low_battery = namespace(sensors=[]) %} + {% for state in states.sensor\n | rejectattr('attributes.device_class', 'undefined')\n + \ | selectattr('attributes.device_class', '==', 'battery')\n | selectattr('state', + 'in', ['unavailable', 'unknown']) %}\n {% if state.entity_id in custom_group_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name ~ ' is + ' ~ state.state ~ '!'] %}\n {% endif %}\n{% endfor %} {% for state in states.binary_sensor\n + \ | rejectattr('attributes.device_class', 'undefined')\n | selectattr('attributes.device_class', + '==', 'battery')\n | selectattr('state', 'in', ['unavailable', 'unknown']) %}\n + \ {% if state.entity_id in custom_group_sensors %}\n {% set low_battery.sensors + = low_battery.sensors + [state.name ~ ' is ' ~ state.state ~ '!'] %}\n {% endif + %}\n{% endfor %} {{ low_battery.sensors | join(', ') }}" + sensors_names_custom_group: "{% set low_battery = namespace(sensors=[]) %} {% for + state in states.sensor\n | rejectattr('attributes.device_class', 'undefined')\n + \ | selectattr('attributes.device_class', '==', 'battery') %}\n {% if 0 <= state.state + | int(-1) <= custom_group_battery_level | int and state.entity_id in custom_group_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name] %}\n {% + endif %}\n{% endfor %} {% for state in states.binary_sensor\n | rejectattr('attributes.device_class', + 'undefined')\n | selectattr('attributes.device_class', '==', 'battery')\n | + selectattr('state', 'in', ['on', 'low']) %}\n {% if state.entity_id in custom_group_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name] %}\n {% + endif %}\n{% endfor %} {{ low_battery.sensors | join(', ') }}" + unavailable_sensors_names_custom_group: "{% set low_battery = namespace(sensors=[]) + %} {% for state in states.sensor\n | rejectattr('attributes.device_class', 'undefined')\n + \ | selectattr('attributes.device_class', '==', 'battery')\n | selectattr('state', + 'in', ['unavailable', 'unknown']) %}\n {% if state.entity_id in custom_group_sensors + %}\n {% set low_battery.sensors = low_battery.sensors + [state.name] %}\n {% + endif %}\n{% endfor %} {% for state in states.binary_sensor\n | rejectattr('attributes.device_class', + 'undefined')\n | selectattr('attributes.device_class', '==', 'battery')\n | + selectattr('state', 'in', ['unavailable', 'unknown']) %}\n {% if state.entity_id + in custom_group_sensors %}\n {% set low_battery.sensors = low_battery.sensors + + [state.name] %}\n {% endif %}\n{% endfor %} {{ low_battery.sensors | join(', + ') }}" + easy_notify_message: "{% set low_battery = namespace(sensors=[]) %} {% if sensor_selection + == 'enable_battery_sensors_custom_group' and notify_message == 'all_sensors' %}\n + \ {{all_sensors_custom_group|replace(', ', '\\n')}}\n{% elif sensor_selection + == 'enable_battery_sensors_custom_group' and notify_message == 'sensors'%}\n {{sensors_custom_group|replace(', + ', '\\n')}}\n{% elif sensor_selection == 'enable_battery_sensors_custom_group' + and notify_message == 'unavailable_sensors'%}\n {{unavailable_sensors_custom_group|replace(', + ', '\\n')}}\n{% elif sensor_selection == 'enable_all_sensors' and notify_message + == 'all_sensors' %}\n {{all_sensors|replace(', ', '\\n')}}\n{% elif sensor_selection + == 'enable_all_sensors' and notify_message == 'sensors'%}\n {{sensors|replace(', + ', '\\n')}}\n{% elif sensor_selection == 'enable_all_sensors' and notify_message + == 'unavailable_sensors'%}\n {{unavailable_sensors|replace(', ', '\\n')}}\n{% + elif sensor_selection == 'enable_all_and_custom_group_sensors' and notify_message + == 'all_sensors' %}\n {{all_sensors|replace(', ', '\\n')}}{{'\\n'}}{{all_sensors_custom_group|replace(', + ', '\\n')}}\n{% elif sensor_selection == 'enable_all_and_custom_group_sensors' + and notify_message == 'sensors'%}\n {{sensors|replace(', ', '\\n')}}{{'\\n'}}{{sensors_custom_group|replace(', + ', '\\n')}}\n{% elif sensor_selection == 'enable_all_and_custom_group_sensors' + and notify_message == 'unavailable_sensors'%}\n {{unavailable_sensors|replace(', + ', '\\n')}}{{'\\n'}}{{unavailable_sensors_custom_group|replace(', ', '\\n')}}\n{% + else %}\n []\n{% endif %}" +triggers: +- trigger: state + entity_id: !input button_entity + id: t0 +- trigger: time + at: !input time + id: t1 +condition: +- condition: or + conditions: + - condition: and + conditions: + - condition: trigger + id: t0 + - '{{ include_button == ''enable_button_trigger'' }}' + - condition: and + conditions: + - condition: trigger + id: t1 + - '{{ include_time == ''time_enabled'' }}' +- condition: or + conditions: + - '{{ include_time == ''time_disabled'' }}' + - condition: and + conditions: + - condition: time + weekday: !input weekday_options + - '{{ include_time == ''time_enabled'' }}' + - condition: trigger + id: t0 +- condition: and + conditions: !input global_conditions +action: +- choose: + - alias: Perform the custom actions + conditions: + - condition: template + value_template: '{{ include_custom_actions == ''enable_custom_actions'' }}' + sequence: !input custom_actions +- choose: + - alias: Use the easy notify options + conditions: + - condition: template + value_template: '{{ (include_easy_notify == ''enable_easy_notify'') or (include_easy_notify + == ''enable_easy_okay_notify'') }}' + - condition: template + value_template: '{{ notify_device | length > 0 }}' + sequence: + - alias: Send a notification to each device + repeat: + for_each: !input notify_device + sequence: + - choose: + - alias: Sensors have been found + conditions: + - '{{ easy_notify_message != '''' }}' + sequence: + - choose: + - alias: Easy notify with no action button + conditions: + - '{{ notify_action_buttons == ''disabled_notify_action_buttons'' + }}' + sequence: + - action: notify.mobile_app_{{ device_attr(repeat.item, 'name') | + slugify }} + data: + title: !input notify_title + message: '{{easy_notify_message}}' + data: '{{ device_message_data }}' + - alias: Easy notify with action button + conditions: + - '{{ notify_action_buttons != ''disabled_notify_action_buttons'' + }}' + sequence: + - action: notify.mobile_app_{{ device_attr(repeat.item, 'name') | + slugify }} + data: + title: !input notify_title + message: '{{easy_notify_message}}' + data: '{{ device_message_data_action_button }}' + - alias: No sensors have been found + conditions: + - '{{ easy_notify_message == '''' }}' + - '{{ include_easy_notify == ''enable_easy_okay_notify'' }}' + sequence: + - action: notify.mobile_app_{{ device_attr(repeat.item, 'name') | slugify + }} + data: + title: !input notify_title + message: !input notify_okay_message + data: '{{ device_message_data }}' +- choose: + - alias: Use the easy notify persistent notification options + conditions: + - condition: template + value_template: '{{ (include_persistent_notification == ''enable_persistent_notification'') + or (include_persistent_notification == ''enable_persistent_okay_notification'') + }}' + sequence: + - choose: + - alias: Sensors have been found + conditions: + - '{{ easy_notify_message != '''' }}' + sequence: + - action: persistent_notification.create + data: + title: "\U0001FAAB {{notify_title}}" + message: '{{easy_notify_message}}' + - alias: No sensors have been found + conditions: + - '{{ easy_notify_message == '''' }}' + - '{{ include_persistent_notification == ''enable_persistent_okay_notification'' + }}' + sequence: + - action: persistent_notification.create + data: + title: "\U0001FAAB {{notify_title}}" + message: !input notify_okay_message +- choose: + - alias: Check if the To-Do action button is enabled + conditions: + - condition: template + value_template: '{{ (include_easy_notify == ''enable_easy_notify'') or (include_easy_notify + == ''enable_easy_okay_notify'') }}' + - condition: template + value_template: '{{(notify_action_buttons == ''enable_to_do_list_button'') or + (notify_action_buttons == ''enable_to_do_list_button_and_confirmation'')}}' + - condition: template + value_template: '{{ notify_device | length > 0 }}' + sequence: + - alias: Wait for a response from the action buttons + wait_for_trigger: + - trigger: event + event_type: mobile_app_notification_action + event_data: + action: '{{ action_button_to_do }}' + - trigger: event + event_type: mobile_app_notification_action + event_data: + action: '{{ action_button_cancel }}' + - choose: + - alias: Check if confirmation message is disabled + conditions: + - condition: template + value_template: '{{ notify_action_buttons == ''enable_to_do_list_button'' + }}' + sequence: + - if: + - alias: Confirmation from the action buttons + condition: template + value_template: '{{ wait.trigger.idx is defined }}' + then: + - alias: Perform the chosen actions + choose: + - conditions: '{{ wait.trigger.event.data.action == action_button_to_do + }}' + sequence: + - alias: Add task to To-Do list + action: todo.add_item + metadata: {} + data: + item: !input to_do_task_title + description: '{{easy_notify_message}}' + target: + entity_id: !input to_do_list + - conditions: '{{ wait.trigger.event.data.action == action_button_cancel + }}' + sequence: + - stop: Stop the automation + - alias: Check if confirmation message is enabled + conditions: + - condition: template + value_template: '{{ notify_action_buttons == ''enable_to_do_list_button_and_confirmation'' + }}' + sequence: + - if: + - alias: Confirmation from the action buttons + condition: template + value_template: '{{ wait.trigger.idx is defined }}' + then: + - alias: Perform the chosen actions + choose: + - conditions: '{{ wait.trigger.event.data.action == action_button_to_do + }}' + sequence: + - alias: Add task to To-Do list + action: todo.add_item + metadata: {} + data: + item: !input to_do_task_title + description: '{{easy_notify_message}}' + target: + entity_id: !input to_do_list + - alias: Send a notification to each device + repeat: + for_each: !input notify_device + sequence: + - action: notify.mobile_app_{{ device_attr(repeat.item, 'name') + | slugify }} + data: + title: !input action_button_confirmation_title + message: !input action_button_confirmation_message + data: '{{ device_confirmation_message_data }}' + - conditions: '{{ wait.trigger.event.data.action == action_button_cancel + }}' + sequence: + - alias: Send a notification to each device + repeat: + for_each: !input notify_device + sequence: + - action: notify.mobile_app_{{ device_attr(repeat.item, 'name') + | slugify }} + data: + title: !input action_button_confirmation_title + message: !input cancel_action_button_confirmation_message + data: '{{ device_message_data }}' diff --git a/hass/settings/blueprints/automation/SirGoodenough/Zigbee2MQTT-Xiaomi_Cube_Controller_MQTT_Triggered.yaml b/hass/settings/blueprints/automation/SirGoodenough/Zigbee2MQTT-Xiaomi_Cube_Controller_MQTT_Triggered.yaml new file mode 100644 index 0000000..dfabaec --- /dev/null +++ b/hass/settings/blueprints/automation/SirGoodenough/Zigbee2MQTT-Xiaomi_Cube_Controller_MQTT_Triggered.yaml @@ -0,0 +1,919 @@ +blueprint: + name: Aqara Magic Cube Zigbee2MQTT MQTT Triggered - 2023-10-20 + author: SirGoodenough + description: "This Blueprint uses a Zigbee2MQTT built sensor to sort out the 38(+54) + unique commands available from the Xiaomi Magic Cube Remote. (Some unique commands + are available thru templating only. See the related document.)\nThe split out + of functions gives you the ability to assign local scripts or functions to do + the things you want the remote to do.\nFunctions that are left empty will simply + do nothing.\n\n### \U0001F34E There is a set of 36 event functions that will trigger + on specific actions on specific sides that are listed as **Group 1 actions \U0001F34E**.\n\n### + \U0001F34A There is a set of 6 event functions that will trigger on specific actions + on *ANY* side that are listed as **Group 2 sctions \U0001F34A**.\n\n### \U0001F350 + There is a set of 30 event functions that will trigger on cube flips to & froma + specific sides that are listed as **Group 3 sctions \U0001F350**.\n\n### \U0001F369 + There are 2 actions (shake and drop) that only occur once and are OK to be combined + with any other group.\n\nPlease be aware that ALL actions except the 2 listed + above, \U0001F369 will trigger an action in **ALL 3 groups at the same time** + every time. Therefore I suggest if you just have a couple of things you want this + remote to do that you choose the *ANY / Group 2 / \U0001F34A* events.\nIf you + want more than a few events, you should select actions in **Group 1 / \U0001F34E + OR Group 3 / \U0001F350**.\nWith careful selection you can use mixed groups, but + you run the risk of a single cube action triggering more than 1 Home Assistant + action and making a mess of things \U0001F371.\n\n#### NOTICE: This cube *can* + be triggered 124 ways, but only 38(+54) of them are unique (with templating).\n##### + This blueprint has been known to freak out when there are spaces or odd characters + in the MQTT Topic. Make sure there are no spaces, ONLY a single word, and ONLY + A thru Z, a thru z, and 0 thru 9 in the topic. (``` / ``` is ok between device + and topic) If there are, you will need to change the name of the cube to remove + those characters.\n### IF YOU SEE --> TemplateError: Must provide a device or + entity ID\nIf you get an error like that, The friendly_name in Z2M likely does + not match the friendly_name on HA. To fix go into the Z2M ```Open web UI``` and + set the friendly_name there. Setting this in just HA or in Z2M without ticking + the update HA box will cause this. See the blueprint documentation page for details.\n\nThere + is sample code to make the template sensor in the help file on GitHib named [Zigbee2MQTT + - Xiaomi Cube Controller MQTT Triggered.md](https://github.com/SirGoodenough/HA_Blueprints/blob/master/Automations/Zigbee2MQTT%20-%20Xiaomi%20Cube%20Controller%20MQTT%20Triggered.md) + & in the [community page related to this](https://community.home-assistant.io/t/zigbee2mqtt-xiaomi-cube-controller/393203).\n\nWithin + this blueprint there is an event handler that will latch the last command that + the blueprint finds and sends that to the event buss. From there a simple Template + sensor can grab it and show you the last action sent. Thie will help when setting + up new functions and to troubleshoot strange behaviours.\n\n> This was forked + from https://community.home-assistant.io/t/z2m-xiaomi-cube-controller/263006 1.2 + project authored by luckypoppy and the friends he pulled together to create the + base. I sincerely thank Him (Them) for their work. I felt there needed to be more + documentation for rookie users to properly set this up. I had quite a few questions + and when I saw a few questions in that chat from people struggling, I wanted to + help. I also had a better idea for troubleshooting info.\n\U0001F4E9 There is + not an official version control system for Blueprints. However I have found something + that comes pretty close. It is not perfect, but for **MOST** Blueprints, it does + just fine. I encourage you to check this script out and use it to easily check + if I have updated this blueprint. \U0001F517 [koter84 Blueprint Update Script](https://github.com/koter84/HomeAssistant_Blueprints_Update/)\n\n[Community + link for this blueprint](https://community.home-assistant.io/t/zigbee2mqtt-xiaomi-cube-controller/393203)\n" + source_url: https://raw.githubusercontent.com/SirGoodenough/HA_Blueprints/master/Automations/Zigbee2MQTT-Xiaomi_Cube_Controller_MQTT_Triggered.yaml + domain: automation + homeassistant: + min_version: 2023.8.0 + input: + topic: + name: Topic + description: "The main MQTT Topic for your cube. \nDetails on finding the correct + topic can be found here: \n[Find my Cube Topic](https://github.com/SirGoodenough/HA_Blueprints/blob/master/Automations/Zigbee2MQTT%20-%20Xiaomi%20Cube%20Controller%20MQTT%20Triggered.md#-to-make-the-blueprint-work-it-will-need)\n" + default: zigbee2mqtt/? + selector: + text: + multiline: false + additional_conditions: + name: Additional conditions + description: "Extra conditions you may want to add to this automation \n(Example: + Home occupied, TV on, etc)\n" + default: [] + selector: + condition: {} + slide_face_0: + name: "Group 1 actions \U0001F34E Slide the cube with face 0 up" + description: Face 0 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + doubletap_face_0: + name: "Group 1 actions \U0001F34E Double tap the cube with face 0 up" + description: Face 0 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + flipped90_face_0: + name: "Group 1 actions \U0001F34E Flip the cube 90 degrees to face 0" + description: Face 0 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + flipped180_face_0: + name: "Group 1 actions \U0001F34E Flip the cube 180 degrees to face 0" + description: '**NOTE:** This action is trick to master, and should be used sparingly... + + Face 0 is the one with the ''Aqara'' LOGO' + default: [] + selector: + action: {} + flip_from_any_to_face_0: + name: "Group 1 actions \U0001F34E Flip the cube from any to face 0" + description: Face 0 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + rotate_cw_face_0: + name: "Group 1 actions \U0001F34E Rotate cube clockwise with face 0 up" + description: Face 0 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + rotate_ccw_face_0: + name: "Group 1 actions \U0001F34E Rotate cube counter clockwise with face 0 + up" + description: Face 0 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + slide_face_1: + name: "Group 1 actions \U0001F34E Slide the cube with face 1 up" + default: [] + selector: + action: {} + doubletap_face_1: + name: "Group 1 actions \U0001F34E Double tap the cube with face 1 up" + default: [] + selector: + action: {} + flipped90_face_1: + name: "Group 1 actions \U0001F34E Flip the cube 90 degrees to face 1" + default: [] + selector: + action: {} + flipped180_face_1: + name: "Group 1 actions \U0001F34E Flip the cube 180 degrees to face 1" + description: '**NOTE:** This action is trick to master, and should be used sparingly...' + default: [] + selector: + action: {} + flip_from_any_to_face_1: + name: "Group 1 actions \U0001F34E Flip the cube from any to face 1" + description: Face 1 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + rotate_cw_face_1: + name: "Group 1 actions \U0001F34E Rotate cube clockwise with face 1 up" + default: [] + selector: + action: {} + rotate_ccw_face_1: + name: "Group 1 actions \U0001F34E Rotate cube counter clockwise with face 1 + up" + default: [] + selector: + action: {} + slide_face_2: + name: "Group 1 actions \U0001F34E Slide the cube with face 2 up" + default: [] + selector: + action: {} + doubletap_face_2: + name: "Group 1 actions \U0001F34E Double tap the cube with face 2 up" + default: [] + selector: + action: {} + flipped90_face_2: + name: "Group 1 actions \U0001F34E Flip the cube 90 degrees to face 2" + default: [] + selector: + action: {} + flipped180_face_2: + name: "Group 1 actions \U0001F34E Flip the cube 180 degrees to face 2" + description: '**NOTE:** This action is trick to master, and should be used sparingly...' + default: [] + selector: + action: {} + flip_from_any_to_face_2: + name: "Group 1 actions \U0001F34E Flip the cube from any to face 2" + description: Face 2 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + rotate_cw_face_2: + name: "Group 1 actions \U0001F34E Rotate cube clockwise with face 2 up" + default: [] + selector: + action: {} + rotate_ccw_face_2: + name: "Group 1 actions \U0001F34E Rotate cube counter clockwise with face 2 + up" + default: [] + selector: + action: {} + slide_face_3: + name: "Group 1 actions \U0001F34E Slide the cube with face 3 up" + default: [] + selector: + action: {} + doubletap_face_3: + name: "Group 1 actions \U0001F34E Double tap the cube with face 3 up" + default: [] + selector: + action: {} + flipped90_face_3: + name: "Group 1 actions \U0001F34E Flip the cube 90 degrees to face 3" + default: [] + selector: + action: {} + flipped180_face_3: + name: "Group 1 actions \U0001F34E Flip the cube 180 degrees to face 3" + description: '**NOTE:** This action is trick to master, and should be used sparingly...' + default: [] + selector: + action: {} + flip_from_any_to_face_3: + name: "Group 1 actions \U0001F34E Flip the cube from any to face 3" + description: Face 3 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + rotate_cw_face_3: + name: "Group 1 actions \U0001F34E Rotate cube clockwise with face 3 up" + default: [] + selector: + action: {} + rotate_ccw_face_3: + name: "Group 1 actions \U0001F34E Rotate cube counter clockwise with face 3 + up" + default: [] + selector: + action: {} + slide_face_4: + name: "Group 1 actions \U0001F34E Slide the cube with face 4 up" + default: [] + selector: + action: {} + doubletap_face_4: + name: "Group 1 actions \U0001F34E Double tap the cube with face 4 up" + default: [] + selector: + action: {} + flipped90_face_4: + name: "Group 1 actions \U0001F34E Flip the cube 90 degrees to face 4" + default: [] + selector: + action: {} + flipped180_face_4: + name: "Group 1 actions \U0001F34E Flip the cube 180 degrees to face 4" + description: '**NOTE:** This action is trick to master, and should be used sparingly...' + default: [] + selector: + action: {} + flip_from_any_to_face_4: + name: "Group 1 actions \U0001F34E Flip the cube from any to face 4" + description: Face 4 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + rotate_cw_face_4: + name: "Group 1 actions \U0001F34E Rotate cube clockwise with face 4 up" + default: [] + selector: + action: {} + rotate_ccw_face_4: + name: "Group 1 actions \U0001F34E Rotate cube counter clockwise with face 4 + up" + default: [] + selector: + action: {} + slide_face_5: + name: "Group 1 actions \U0001F34E Slide the cube with face 5 up" + default: [] + selector: + action: {} + doubletap_face_5: + name: "Group 1 actions \U0001F34E Double tap the cube with face 5 up" + default: [] + selector: + action: {} + flipped90_face_5: + name: "Group 1 actions \U0001F34E Flip the cube 90 degrees to face 5" + default: [] + selector: + action: {} + flipped180_face_5: + name: "Group 1 actions \U0001F34E Flip the cube 180 degrees to face 5" + description: '**NOTE:** This action is trick to master, and should be used sparingly...' + default: [] + selector: + action: {} + flip_from_any_to_face_5: + name: "Group 1 actions \U0001F34E Flip the cube from any to face 5" + description: Face 5 is the one with the 'Aqara' LOGO + default: [] + selector: + action: {} + rotate_cw_face_5: + name: "Group 1 actions \U0001F34E Rotate cube clockwise with face 5 up" + default: [] + selector: + action: {} + rotate_ccw_face_5: + name: "Group 1 actions \U0001F34E Rotate cube counter clockwise with face 5 + up" + default: [] + selector: + action: {} + shake: + name: "Shake the cube \U0001F369" + description: 'This trigger only occurs once in the set-up. + + It can be combined in any group.' + default: [] + selector: + action: {} + fall: + name: "Drop the cube \U0001F369" + description: 'This trigger only occurs once in the set-up. + + It can be combined in any group.' + default: [] + selector: + action: {} + slide_any: + name: "Group 2 actions \U0001F34A Slide the cube with any side" + description: "Side doesn't matter on this one.\n**!!Warning!!** The automations + for the specific side will also trigger. \U0001F34E\U0001F350\nI suggest using + only one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + doubletap_any: + name: "Group 2 actions \U0001F34A Double tap the cube with any side" + description: "Side doesn't matter on this one.\n**!!Warning!!** The automations + for the specific side will also trigger. \U0001F34E\U0001F350\nI suggest using + only one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + flipped90_any: + name: "Group 2 actions \U0001F34A Flip the cube 90 degrees to any side" + description: "Side doesn't matter on this one.\n**!!Warning!!** The automations + for the specific side will also trigger. \U0001F34E\U0001F350\nI suggest using + only one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + flipped180_any: + name: "Group 2 actions \U0001F34A Flip the cube 180 degrees any side" + description: "NOTE: This action is trick to master, and should be used sparingly... + Side doesn't matter on this one.\n**!!Warning!!** The automations for the + specific side will also trigger. \U0001F34E\U0001F350\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + rotate_cw_any: + name: "Group 2 actions \U0001F34A Rotate cube clockwise with any side" + description: "Side doesn't matter on this one.\n**!!Warning!!** The automations + for the specific side will also trigger. \U0001F34E\U0001F350\nI suggest using + only one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + rotate_ccw_any: + name: "Group 2 actions \U0001F34A Rotate cube counter clockwise with any side" + description: "Side doesn't matter on this one.\n**!!Warning!!** The automations + for the specific side will also trigger. \U0001F34E\U0001F350\nI suggest using + only one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 0_from_1: + name: "Group 3 actions \U0001F350 Flip the cube to side 0 from side 1" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 0 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 0_from_2: + name: "Group 3 actions \U0001F350 Flip the cube to side 0 from side 2" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 0 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 0_from_3: + name: "Group 3 actions \U0001F350 Flip the cube to side 0 from side 3" + description: "**!!Warning!!** The flip 180 automations \U0001F34E for side 0 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 0_from_4: + name: "Group 3 actions \U0001F350 Flip the cube to side 0 from side 4" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 0 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 0_from_5: + name: "Group 3 actions \U0001F350 Flip the cube to side 0 from side 5" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 0 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 1_from_0: + name: "Group 3 actions \U0001F350 Flip the cube to side 1 from side 0" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 1 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 1_from_2: + name: "Group 3 actions \U0001F350 Flip the cube to side 1 from side 2" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 1 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 1_from_3: + name: "Group 3 actions \U0001F350 Flip the cube to side 1 from side 3" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 1 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 1_from_4: + name: "Group 3 actions \U0001F350 Flip the cube to side 1 from side 4" + description: "**!!Warning!!** The flip 180 automations \U0001F34E for side 1 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 1_from_5: + name: "Group 3 actions \U0001F350 Flip the cube to side 1 from side 5" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 1 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 2_from_0: + name: "Group 3 actions \U0001F350 Flip the cube to side 2 from side 0" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 2 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 2_from_1: + name: "Group 3 actions \U0001F350 Flip the cube to side 2 from side 1" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 2 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 2_from_3: + name: "Group 3 actions \U0001F350 Flip the cube to side 2 from side 3" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 2 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 2_from_4: + name: "Group 3 actions \U0001F350 Flip the cube to side 2 from side 4" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 2 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 2_from_5: + name: "Group 3 actions \U0001F350 Flip the cube to side 2 from side 5" + description: "**!!Warning!!** The flip 180 automations \U0001F34E for side 2 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 3_from_0: + name: "Group 3 actions \U0001F350 Flip the cube to side 3 from side 0" + description: "**!!Warning!!** The flip 180 automations \U0001F34E for side 3 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 3_from_1: + name: "Group 3 actions \U0001F350 Flip the cube to side 3 from side 1" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 3 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 3_from_2: + name: "Group 3 actions \U0001F350 Flip the cube to side 3 from side 2" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 3 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 3_from_4: + name: "Group 3 actions \U0001F350 Flip the cube to side 3 from side 4" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 3 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 3_from_5: + name: "Group 3 actions \U0001F350 Flip the cube to side 3 from side 5" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 3 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 4_from_0: + name: "Group 3 actions \U0001F350 Flip the cube to side 4 from side 0" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 4 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 4_from_1: + name: "Group 3 actions \U0001F350 Flip the cube to side 4 from side 1" + description: "**!!Warning!!** The flip 180 automations \U0001F34E for side 4 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 4_from_2: + name: "Group 3 actions \U0001F350 Flip the cube to side 4 from side 2" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 4 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 4_from_3: + name: "Group 3 actions \U0001F350 Flip the cube to side 4 from side 3" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 4 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 4_from_5: + name: "Group 3 actions \U0001F350 Flip the cube to side 4 from side 5" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 4 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 5_from_0: + name: "Group 3 actions \U0001F350 Flip the cube to side 5 from side 0" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 5 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 5_from_1: + name: "Group 3 actions \U0001F350 Flip the cube to side 5 from side 1" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 5 + and the ANY \U0001F34A automations will also trigger. \U0001F34E\U0001F350\nI + suggest using only one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 5_from_2: + name: "Group 3 actions \U0001F350 Flip the cube to side 5 from side 2" + description: "**!!Warning!!** The flip 180 automations \U0001F34E for side 5 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 5_from_3: + name: "Group 3 actions \U0001F350 Flip the cube to side 5 from side 3" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 5 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} + 5_from_4: + name: "Group 3 actions \U0001F350 Flip the cube to side 5 from side 4" + description: "**!!Warning!!** The flip 90 automations \U0001F34E for side 5 + and the ANY \U0001F34A automations will also trigger.\nI suggest using only + one of the three groups of triggers, not multiple groups..." + default: [] + selector: + action: {} +trigger: +- platform: mqtt + topic: !input topic +variables: + action: '{{ trigger.payload_json.action }}' + angle: '{{ trigger.payload_json.action_angle | float(0.0) }}' + side: '{{ trigger.payload_json.side }}' + op_mode: '{{ trigger.payload_json.operation_mode }}' + topic_var: !input topic + friendly_name: '{{ topic_var.split(''/'')[1] | regex_replace("[^A-Za-z0-9_/./-]", + "") }}' + device_id: '{{ device_id( friendly_name ) }}' + ieee_id_long: '{{(( device_attr( device_id, ''identifiers'') |list)[0][1]) }}' + ieee_id: '{{ieee_id_long.split(''_'')[1] }}' + num_name: last side + num_ha_name: '{{ ''number.'' + friendly_name + ''_last_side'' }}' + num_ha_val: '{{ states(num_ha_name) | default(0) | int(0) }}' + last_side: ' {# The first time it will not be there, so make sure its 0 instead + of undefined. #} {% if not num_ha_val in (''none'', ''null'', ''unavailable'', + '''') %} {{ num_ha_val }} {% else %} 0 {% endif %}' + mdl: Number Helper for Z2M Cube Integration + mf: SirGoodenough + sa: house + sw: '2023-10-20' + hw: https://github.com/SirGoodenough/HA_Blueprints/blob/master/Automations/Zigbee2MQTT + - Xiaomi Cube Controller.yaml + cu: https://community.home-assistant.io/t/zigbee2mqtt-xiaomi-cube-controller/393203 + config_topic_2: '{{ ''homeassistant/number/'' + ieee_id + ''/last_side/config'' + }}' + topic_2: '{{ ''homeassistant/number/'' + ieee_id + ''/last_side'' }}' +condition: +- condition: template + value_template: '{{ trigger.payload_json is defined and trigger.payload_json.action + is defined and trigger.payload_json.action in (''rotate_right'', ''rotate_left'', + ''flip90'', ''flip180'', ''slide'', ''tap'', ''shake'', ''fall'') }}' +- alias: User pick + condition: !input additional_conditions +action: +- alias: Make number helper to store last side + service: mqtt.publish + data: + topic: '{{ config_topic_2 }}' + retain: true + payload: "{ \"name\": \"{{ num_name }}\",\n \"avty_t\": \"homeassistant/status\",\n + \ \"uniq_id\": \"{{ friendly_name + '-' + device_id }}\",\n \"cmd_t\": \"{{ + topic_2 }}\",\n \"sta_t\": \"{{ topic_2 }}\",\n \"min\": \"0\",\n \"max\": + \"6\",\n \"step\": \"1\",\n \"mode\": \"box\",\n \"ret\": true,\n \"dev\": + {\n \"name\": \"{{ friendly_name }}\",\n \"mdl\": \"{{ mdl }}\",\n \"mf\": + \"{{ mf }}\",\n \"sa\": \"{{ sa }}\",\n \"sw\": \"{{ sw }}\",\n \"hw\": + \"{{ hw }}\",\n \"cu\": \"{{ cu }}\",\n \"ids\": [\n \"{{ ieee_id + + '-' + num_name }}\"\n ]\n }\n}\n" +- alias: Fire Last Action event. This is for optional troubleshooting data. + event: cube_last_action + event_data: + action: '{{ action }}' + side: '{{ side }}' + last_side: '{{ last_side }}' + friendly_name: '{{ friendly_name }}' + device_id: '{{ device_id }}' + ieee_id: '{{ ieee_id }}' + angle: '{{ angle }}' +- alias: Store the current_side as the last_side for the next trigger + service: number.set_value + target: + entity_id: '{{ num_ha_name }}' + data: + value: '{{ side }}' +- alias: "Group 2 \U0001F34A and No Group \U0001F369 These are action events for ANY + Side" + choose: + - conditions: '{{ action == "slide" }}' + sequence: !input slide_any + - conditions: '{{ action == "tap" }}' + sequence: !input doubletap_any + - conditions: '{{ action == "rotate_right" }}' + sequence: !input rotate_cw_any + - conditions: '{{ action == "rotate_left" }}' + sequence: !input rotate_ccw_any + - conditions: '{{ action == "shake" }}' + sequence: !input shake + - conditions: '{{ action == "fall" }}' + sequence: !input fall + - conditions: '{{ action == "flip90" }}' + sequence: + - parallel: + - !input flipped90_any + - alias: These are flip to specific side from any side events + choose: + - conditions: '{{ side == 0 }}' + sequence: !input flip_from_any_to_face_0 + - conditions: '{{ side == 1 }}' + sequence: !input flip_from_any_to_face_1 + - conditions: '{{ side == 2 }}' + sequence: !input flip_from_any_to_face_2 + - conditions: '{{ side == 3 }}' + sequence: !input flip_from_any_to_face_3 + - conditions: '{{ side == 4 }}' + sequence: !input flip_from_any_to_face_4 + - conditions: '{{ side == 5 }}' + sequence: !input flip_from_any_to_face_5 + - conditions: '{{ action == "flip180" }}' + sequence: + - parallel: + - !input flipped180_any + - alias: These are flip to specific side from any side events + choose: + - conditions: '{{ side == 0 }}' + sequence: !input flip_from_any_to_face_0 + - conditions: '{{ side == 1 }}' + sequence: !input flip_from_any_to_face_1 + - conditions: '{{ side == 2 }}' + sequence: !input flip_from_any_to_face_2 + - conditions: '{{ side == 3 }}' + sequence: !input flip_from_any_to_face_3 + - conditions: '{{ side == 4 }}' + sequence: !input flip_from_any_to_face_4 + - conditions: '{{ side == 5 }}' + sequence: !input flip_from_any_to_face_5 +- alias: "Group 1 \U0001F34E These are action events based on the action broken down + by side" + choose: + - conditions: '{{ action == "slide" }}' + sequence: + - alias: These are slide events + choose: + - conditions: '{{ side == 0 }}' + sequence: !input slide_face_0 + - conditions: '{{ side == 1 }}' + sequence: !input slide_face_1 + - conditions: '{{ side == 2 }}' + sequence: !input slide_face_2 + - conditions: '{{ side == 3 }}' + sequence: !input slide_face_3 + - conditions: '{{ side == 4 }}' + sequence: !input slide_face_4 + - conditions: '{{ side == 5 }}' + sequence: !input slide_face_5 + - conditions: '{{ action == "tap" }}' + sequence: + - alias: These are doubletap events + choose: + - conditions: '{{ side == 0 }}' + sequence: !input doubletap_face_0 + - conditions: '{{ side == 1 }}' + sequence: !input doubletap_face_1 + - conditions: '{{ side == 2 }}' + sequence: !input doubletap_face_2 + - conditions: '{{ side == 3 }}' + sequence: !input doubletap_face_3 + - conditions: '{{ side == 4 }}' + sequence: !input doubletap_face_4 + - conditions: '{{ side == 5 }}' + sequence: !input doubletap_face_5 + - conditions: '{{ action == "flip90" }}' + sequence: + - alias: These are flipped90 events + choose: + - conditions: '{{ side == 0 }}' + sequence: !input flipped90_face_0 + - conditions: '{{ side == 1 }}' + sequence: !input flipped90_face_1 + - conditions: '{{ side == 2 }}' + sequence: !input flipped90_face_2 + - conditions: '{{ side == 3 }}' + sequence: !input flipped90_face_3 + - conditions: '{{ side == 4 }}' + sequence: !input flipped90_face_4 + - conditions: '{{ side == 5 }}' + sequence: !input flipped90_face_5 + - conditions: '{{ action == "flip180" }}' + sequence: + - alias: These are flipped180 events + choose: + - conditions: '{{ side == 0 }}' + sequence: !input flipped180_face_0 + - conditions: '{{ side == 1 }}' + sequence: !input flipped180_face_1 + - conditions: '{{ side == 2 }}' + sequence: !input flipped180_face_2 + - conditions: '{{ side == 3 }}' + sequence: !input flipped180_face_3 + - conditions: '{{ side == 4 }}' + sequence: !input flipped180_face_4 + - conditions: '{{ side == 5 }}' + sequence: !input flipped180_face_5 + - conditions: '{{ action == "rotate_right" }}' + sequence: + - alias: These are rotate CW events + choose: + - conditions: '{{ side == 0 }}' + sequence: !input rotate_cw_face_0 + - conditions: '{{ side == 1 }}' + sequence: !input rotate_cw_face_1 + - conditions: '{{ side == 2 }}' + sequence: !input rotate_cw_face_2 + - conditions: '{{ side == 3 }}' + sequence: !input rotate_cw_face_3 + - conditions: '{{ side == 4 }}' + sequence: !input rotate_cw_face_4 + - conditions: '{{ side == 5 }}' + sequence: !input rotate_cw_face_5 + - conditions: '{{ action == "rotate_left" }}' + sequence: + - alias: These are rotate CCW events + choose: + - conditions: '{{ side == 0 }}' + sequence: !input rotate_ccw_face_0 + - conditions: '{{ side == 1 }}' + sequence: !input rotate_ccw_face_1 + - conditions: '{{ side == 2 }}' + sequence: !input rotate_ccw_face_2 + - conditions: '{{ side == 3 }}' + sequence: !input rotate_ccw_face_3 + - conditions: '{{ side == 4 }}' + sequence: !input rotate_ccw_face_4 + - conditions: '{{ side == 5 }}' + sequence: !input rotate_ccw_face_5 +- alias: "Group 3 \U0001F350 Side to side jump events" + choose: + - conditions: '{{ side == 0 }}' + sequence: + - alias: To side 0 from any events + choose: + - conditions: '{{ last_side == 1 }}' + sequence: !input 0_from_1 + - conditions: '{{ last_side == 2 }}' + sequence: !input 0_from_2 + - conditions: '{{ last_side == 3 }}' + sequence: !input 0_from_3 + - conditions: '{{ last_side == 4 }}' + sequence: !input 0_from_4 + - conditions: '{{ last_side == 5 }}' + sequence: !input 0_from_5 + - conditions: '{{ side == 1 }}' + sequence: + - alias: To side 1 from any events + choose: + - conditions: '{{ last_side == 0 }}' + sequence: !input 1_from_0 + - conditions: '{{ last_side == 2 }}' + sequence: !input 1_from_2 + - conditions: '{{ last_side == 3 }}' + sequence: !input 1_from_3 + - conditions: '{{ last_side == 4 }}' + sequence: !input 1_from_4 + - conditions: '{{ last_side == 5 }}' + sequence: !input 1_from_5 + - conditions: '{{ side == 2 }}' + sequence: + - alias: To side 2 from any events + choose: + - conditions: '{{ last_side == 0 }}' + sequence: !input 2_from_0 + - conditions: '{{ last_side == 1 }}' + sequence: !input 2_from_1 + - conditions: '{{ last_side == 3 }}' + sequence: !input 2_from_3 + - conditions: '{{ last_side == 4 }}' + sequence: !input 2_from_4 + - conditions: '{{ last_side == 5 }}' + sequence: !input 2_from_5 + - conditions: '{{ side == 3 }}' + sequence: + - alias: To side 3 from any events + choose: + - conditions: '{{ last_side == 0 }}' + sequence: !input 3_from_0 + - conditions: '{{ last_side == 1 }}' + sequence: !input 3_from_1 + - conditions: '{{ last_side == 2 }}' + sequence: !input 3_from_2 + - conditions: '{{ last_side == 4 }}' + sequence: !input 3_from_4 + - conditions: '{{ last_side == 5 }}' + sequence: !input 3_from_5 + - conditions: '{{ side == 4 }}' + sequence: + - alias: To side 4 from any events + choose: + - conditions: '{{ last_side == 0 }}' + sequence: !input 4_from_0 + - conditions: '{{ last_side == 1 }}' + sequence: !input 4_from_1 + - conditions: '{{ last_side == 2 }}' + sequence: !input 4_from_2 + - conditions: '{{ last_side == 3 }}' + sequence: !input 4_from_3 + - conditions: '{{ last_side == 5 }}' + sequence: !input 4_from_5 + - conditions: '{{ side == 5 }}' + sequence: + - alias: To side 5 from any events + choose: + - conditions: '{{ last_side == 0 }}' + sequence: !input 5_from_0 + - conditions: '{{ last_side == 1 }}' + sequence: !input 5_from_1 + - conditions: '{{ last_side == 2 }}' + sequence: !input 5_from_2 + - conditions: '{{ last_side == 3 }}' + sequence: !input 5_from_3 + - conditions: '{{ last_side == 4 }}' + sequence: !input 5_from_4 +- alias: Delay ⏱ for debounce so toggle functions work. Also blueprint is in single + mode. + delay: 00:00:01 +mode: single +max_exceeded: silent diff --git a/hass/settings/blueprints/automation/gist.githubusercontent.com/yandex_station_default_volume.yaml b/hass/settings/blueprints/automation/gist.githubusercontent.com/yandex_station_default_volume.yaml new file mode 100644 index 0000000..f8b2de8 --- /dev/null +++ b/hass/settings/blueprints/automation/gist.githubusercontent.com/yandex_station_default_volume.yaml @@ -0,0 +1,68 @@ +blueprint: + name: Громкость станции по умолчанию + description: Громкость станции по умолчанию + domain: automation + input: + yandex_station: + name: Яндекс станция + selector: + entity: + domain: + - media_player + multiple: false + number_volume: + name: Громкость по умолчанию (от 1 до 10) + selector: + number: + min: 1.0 + max: 10.0 + step: 1.0 + mode: slider + default: 4 + source_url: https://gist.githubusercontent.com/kai-zer-ru/b790de73330ac58068e12b61e7c1cd0a/raw/78113275ea0a08c43fb5cca947341991ccb503b2/yandex_station_default_volume.yaml +mode: restart +max_exceeded: silent +variables: + yandex_station: !input yandex_station + number_volume: !input number_volume + number_volume_percent: '{{ number_volume | int / 10 }}' +trigger: +- platform: state + entity_id: !input yandex_station + to: paused + for: + hours: 0 + minutes: 5 + seconds: 0 +- platform: state + entity_id: !input yandex_station + to: standby + for: + hours: 0 + minutes: 5 + seconds: 0 +- platform: state + entity_id: !input yandex_station + to: 'off' + for: + hours: 0 + minutes: 5 + seconds: 0 +conditions: +- condition: not + conditions: + - condition: state + entity_id: !input yandex_station + state: playing +action: +- if: + - condition: template + value_template: '{{ state_attr(yandex_station, "volume_level")|round(1) != number_volume_percent + }}' + then: + - action: media_player.volume_set + metadata: {} + data: + volume_level: '{{ number_volume_percent }}' + target: + entity_id: !input yandex_station diff --git a/hass/settings/blueprints/automation/gmlupatelli/unavailable_entities_notification.yaml b/hass/settings/blueprints/automation/gmlupatelli/unavailable_entities_notification.yaml new file mode 100644 index 0000000..cba4582 --- /dev/null +++ b/hass/settings/blueprints/automation/gmlupatelli/unavailable_entities_notification.yaml @@ -0,0 +1,103 @@ +blueprint: + name: Unavailable entity detection & notification + description: Regularly test all entities' status to check for unavailability. + domain: automation + source_url: https://github.com/gmlupatelli/blueprints_repo/blob/master/unavailable_entities_notification/unavailable_entities_notification.yaml + input: + time: + name: Time to test on + description: Test is run at configured time + default: '10:00:00' + selector: + time: {} + monday_enabled: + name: Monday + description: Run test on Monday + default: true + selector: + boolean: {} + tuesday_enabled: + name: Tuesday + description: Run test on Tuesday + default: true + selector: + boolean: {} + wednesday_enabled: + name: Wednesday + description: Run test on Wednesday + default: true + selector: + boolean: {} + thursday_enabled: + name: Thursday + description: Run test on Thursday + default: true + selector: + boolean: {} + friday_enabled: + name: Friday + description: Run test on Friday + default: true + selector: + boolean: {} + saturday_enabled: + name: Saturday + description: Run test on Saturday + default: true + selector: + boolean: {} + sunday_enabled: + name: Sunday + description: Run test on Sunday + default: true + selector: + boolean: {} + exclude: + name: Excluded Entities + description: Entities (e.g. smartphone) to exclude from detection. Only entities + and devices are supported, areas must be expanded! + default: {} + selector: + target: {} + actions: + name: Actions + description: Notifications or similar to be run. {{entities}} is replaced with + the names of unavailable entities. + selector: + action: {} +variables: + monday_enabled: !input monday_enabled + tuesday_enabled: !input tuesday_enabled + wednesday_enabled: !input wednesday_enabled + thursday_enabled: !input thursday_enabled + friday_enabled: !input friday_enabled + saturday_enabled: !input saturday_enabled + sunday_enabled: !input sunday_enabled + current_day: '{{ now().weekday() | int }}' + exclude: !input exclude + entities: "{% set result = namespace(includedEntities=[], excludedEntities = []) + %}\n{% if exclude.entity_id is defined %} \n {% set result.excludedEntities + = result.excludedEntities + exclude.entity_id %}\n{% endif %}\n{% if exclude.device_id + is defined %}\n {% for device_id in exclude.device_id %}\n {% set result.excludedEntities + = result.excludedEntities + device_entities(device_id) %}\n {% endfor %}\n{% + endif %}\n{% for state in states | selectattr('state', 'eq', 'unavailable') %}\n + \ {% if state.state == 'unavailable' and not state.entity_id in result.excludedEntities + %}\n {% set result.includedEntities = result.includedEntities + [state.name] + %}\n {% endif %}\n{% endfor %}\n{{\"⤵ \\n- \"}}{{result.includedEntities|join('\\n- + ')}}" +trigger: +- platform: time + at: !input time +condition: +- condition: template + value_template: "{{ \n (current_day == 0 and monday_enabled) or \n (current_day + == 1 and tuesday_enabled) or \n (current_day == 2 and wednesday_enabled) or\n + \ (current_day == 3 and thursday_enabled) or \n (current_day == 4 and friday_enabled) + or\n (current_day == 5 and saturday_enabled) or\n (current_day == 6 and sunday_enabled)\n}}" +- condition: template + value_template: '{{ entities != "⤵ \n-"}}' +action: +- choose: [] + default: !input actions +mode: single +max_exceeded: silent diff --git a/hass/settings/blueprints/automation/homeassistant/motion_light.yaml b/hass/settings/blueprints/automation/homeassistant/motion_light.yaml new file mode 100644 index 0000000..54a4a4f --- /dev/null +++ b/hass/settings/blueprints/automation/homeassistant/motion_light.yaml @@ -0,0 +1,54 @@ +blueprint: + name: Motion-activated Light + description: Turn on a light when motion is detected. + domain: automation + source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/motion_light.yaml + input: + motion_entity: + name: Motion Sensor + selector: + entity: + domain: binary_sensor + device_class: motion + light_target: + name: Light + selector: + target: + entity: + domain: light + no_motion_wait: + name: Wait time + description: Time to leave the light on after last motion is detected. + default: 120 + selector: + number: + min: 0 + max: 3600 + unit_of_measurement: seconds + +# If motion is detected within the delay, +# we restart the script. +mode: restart +max_exceeded: silent + +trigger: + platform: state + entity_id: !input motion_entity + from: "off" + to: "on" + +action: + - alias: "Turn on the light" + service: light.turn_on + target: !input light_target + - alias: "Wait until there is no motion from device" + wait_for_trigger: + platform: state + entity_id: !input motion_entity + from: "on" + to: "off" + - alias: "Wait the number of seconds that has been set" + delay: !input no_motion_wait + - alias: "Turn off the light" + service: light.turn_off + target: !input light_target diff --git a/hass/settings/blueprints/automation/homeassistant/notify_leaving_zone.yaml b/hass/settings/blueprints/automation/homeassistant/notify_leaving_zone.yaml new file mode 100644 index 0000000..1dc8a0e --- /dev/null +++ b/hass/settings/blueprints/automation/homeassistant/notify_leaving_zone.yaml @@ -0,0 +1,46 @@ +blueprint: + name: Zone Notification + description: Send a notification to a device when a person leaves a specific zone. + domain: automation + source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml + input: + person_entity: + name: Person + selector: + entity: + domain: person + zone_entity: + name: Zone + selector: + entity: + domain: zone + notify_device: + name: Device to notify + description: Device needs to run the official Home Assistant app to receive notifications. + selector: + device: + integration: mobile_app + +trigger: + platform: state + entity_id: !input person_entity + +variables: + zone_entity: !input zone_entity + # This is the state of the person when it's in this zone. + zone_state: "{{ states[zone_entity].name }}" + person_entity: !input person_entity + person_name: "{{ states[person_entity].name }}" + +condition: + condition: template + # The first case handles leaving the Home zone which has a special state when zoning called 'home'. + # The second case handles leaving all other zones. + value_template: "{{ zone_entity == 'zone.home' and trigger.from_state.state == 'home' and trigger.to_state.state != 'home' or trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}" + +action: + - alias: "Notify that a person has left the zone" + domain: mobile_app + type: notify + device_id: !input notify_device + message: "{{ person_name }} has left {{ zone_state }}" diff --git a/hass/settings/blueprints/script/homeassistant/confirmable_notification.yaml b/hass/settings/blueprints/script/homeassistant/confirmable_notification.yaml new file mode 100644 index 0000000..d52e5a6 --- /dev/null +++ b/hass/settings/blueprints/script/homeassistant/confirmable_notification.yaml @@ -0,0 +1,84 @@ +blueprint: + name: Confirmable Notification + description: >- + A script that sends an actionable notification with a confirmation before + running the specified action. + domain: script + source_url: https://github.com/home-assistant/core/blob/master/homeassistant/components/script/blueprints/confirmable_notification.yaml + input: + notify_device: + name: Device to notify + description: Device needs to run the official Home Assistant app to receive notifications. + selector: + device: + integration: mobile_app + title: + name: "Title" + description: "The title of the button shown in the notification." + default: "" + selector: + text: + message: + name: "Message" + description: "The message body" + selector: + text: + confirm_text: + name: "Confirmation Text" + description: "Text to show on the confirmation button" + default: "Confirm" + selector: + text: + confirm_action: + name: "Confirmation Action" + description: "Action to run when notification is confirmed" + default: [] + selector: + action: + dismiss_text: + name: "Dismiss Text" + description: "Text to show on the dismiss button" + default: "Dismiss" + selector: + text: + dismiss_action: + name: "Dismiss Action" + description: "Action to run when notification is dismissed" + default: [] + selector: + action: + +mode: restart + +sequence: + - alias: "Set up variables" + variables: + action_confirm: "{{ 'CONFIRM_' ~ context.id }}" + action_dismiss: "{{ 'DISMISS_' ~ context.id }}" + - alias: "Send notification" + domain: mobile_app + type: notify + device_id: !input notify_device + title: !input title + message: !input message + data: + actions: + - action: "{{ action_confirm }}" + title: !input confirm_text + - action: "{{ action_dismiss }}" + title: !input dismiss_text + - alias: "Awaiting response" + wait_for_trigger: + - platform: event + event_type: mobile_app_notification_action + event_data: + action: "{{ action_confirm }}" + - platform: event + event_type: mobile_app_notification_action + event_data: + action: "{{ action_dismiss }}" + - choose: + - conditions: "{{ wait.trigger.event.data.action == action_confirm }}" + sequence: !input confirm_action + - conditions: "{{ wait.trigger.event.data.action == action_dismiss }}" + sequence: !input dismiss_action diff --git a/hass/settings/blueprints/template/homeassistant/inverted_binary_sensor.yaml b/hass/settings/blueprints/template/homeassistant/inverted_binary_sensor.yaml new file mode 100644 index 0000000..5be1840 --- /dev/null +++ b/hass/settings/blueprints/template/homeassistant/inverted_binary_sensor.yaml @@ -0,0 +1,27 @@ +blueprint: + name: Invert a binary sensor + description: Creates a binary_sensor which holds the inverted value of a reference binary_sensor + domain: template + source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/template/blueprints/inverted_binary_sensor.yaml + input: + reference_entity: + name: Binary sensor to be inverted + description: The binary_sensor which needs to have its value inverted + selector: + entity: + domain: binary_sensor +variables: + reference_entity: !input reference_entity +binary_sensor: + state: > + {% if states(reference_entity) == 'on' %} + off + {% elif states(reference_entity) == 'off' %} + on + {% else %} + {{ states(reference_entity) }} + {% endif %} + # delay_on: not_used in this example + # delay_off: not_used in this example + # auto_off: not_used in this example + availability: "{{ states(reference_entity) not in ('unknown', 'unavailable') }}" diff --git a/hass/settings/configuration.yaml b/hass/settings/configuration.yaml new file mode 100644 index 0000000..b203400 --- /dev/null +++ b/hass/settings/configuration.yaml @@ -0,0 +1,25 @@ +# Loads default set of integrations. Do not remove. +default_config: + +homeassistant: + packages: !include_dir_named packages + external_url: "https://home-fnv.yandex5.ru" + internal_url: "http://192.168.1.6:8123" + +http: + use_x_forwarded_for: true + ip_ban_enabled: true + login_attempts_threshold: 15 + trusted_proxies: + - 172.19.0.0/24 + - 172.18.0.0/24 + - 192.168.1.0/24 + +# Text to speech +tts: + - platform: google_translate + +automation: !include automations.yaml +script: !include scripts.yaml +scene: !include scenes.yaml + diff --git a/hass/settings/image/6cc087659939dc8bc6047031679fe062/512x512 b/hass/settings/image/6cc087659939dc8bc6047031679fe062/512x512 new file mode 100644 index 0000000..d391ed9 Binary files /dev/null and b/hass/settings/image/6cc087659939dc8bc6047031679fe062/512x512 differ diff --git a/hass/settings/image/6cc087659939dc8bc6047031679fe062/original b/hass/settings/image/6cc087659939dc8bc6047031679fe062/original new file mode 100644 index 0000000..45b4fd6 Binary files /dev/null and b/hass/settings/image/6cc087659939dc8bc6047031679fe062/original differ diff --git a/hass/settings/image/ec890142459e316df6628907e28c0986/512x512 b/hass/settings/image/ec890142459e316df6628907e28c0986/512x512 new file mode 100644 index 0000000..8a56584 Binary files /dev/null and b/hass/settings/image/ec890142459e316df6628907e28c0986/512x512 differ diff --git a/hass/settings/image/ec890142459e316df6628907e28c0986/original b/hass/settings/image/ec890142459e316df6628907e28c0986/original new file mode 100644 index 0000000..0e6e6e1 Binary files /dev/null and b/hass/settings/image/ec890142459e316df6628907e28c0986/original differ diff --git a/hass/settings/ip_bans.yaml b/hass/settings/ip_bans.yaml new file mode 100644 index 0000000..74be965 --- /dev/null +++ b/hass/settings/ip_bans.yaml @@ -0,0 +1,3 @@ + +192.168.1.36: + banned_at: '2023-11-26T14:36:40.730001+00:00' diff --git a/hass/settings/known_devices.yaml b/hass/settings/known_devices.yaml new file mode 100644 index 0000000..90301fc --- /dev/null +++ b/hass/settings/known_devices.yaml @@ -0,0 +1,21 @@ + +tag_danil_iphone: + name: tag_danil_iphone + mac: + icon: + picture: + track: true + +tag_svetlana_iphone: + name: tag_svetlana_iphone + mac: + icon: + picture: + track: true + +tag_mama_iphone: + name: tag_mama_iphone + mac: + icon: + picture: + track: true diff --git a/hass/settings/packages/alarm_and_presence/alarm_control_panel.yaml b/hass/settings/packages/alarm_and_presence/alarm_control_panel.yaml new file mode 100644 index 0000000..bd6be1e --- /dev/null +++ b/hass/settings/packages/alarm_and_presence/alarm_control_panel.yaml @@ -0,0 +1,12 @@ +alarm_control_panel: + - platform: manual + name: ha_alarm + code_arm_required: false + delay_time: 5 + trigger_time: 5 + arming_time: 5 + +homeassistant: + customize: + alarm_control_panel.ha_alarm: + friendly_name: Охрана \ No newline at end of file diff --git a/hass/settings/packages/alarm_and_presence/person_pictures.yaml b/hass/settings/packages/alarm_and_presence/person_pictures.yaml new file mode 100644 index 0000000..ff97b8b --- /dev/null +++ b/hass/settings/packages/alarm_and_presence/person_pictures.yaml @@ -0,0 +1,6 @@ +homeassistant: + customize: + person.danil: + entity_picture: "/local/danil.jpg" + person.svetlana: + entity_picture: "/local/svetlana.jpg" \ No newline at end of file diff --git a/hass/settings/packages/alarm_and_presence/presence_status.yaml b/hass/settings/packages/alarm_and_presence/presence_status.yaml new file mode 100644 index 0000000..c9bc90e --- /dev/null +++ b/hass/settings/packages/alarm_and_presence/presence_status.yaml @@ -0,0 +1,15 @@ +--- +input_select: + presence_extended: + name: presence_extended + options: + - 'Away' + - 'Home' + - 'Just Arrived' + - 'Just Left' + - 'Holidays' + +input_boolean: + guest_mode: + name: Наличие гостей + icon: mdi:alert-octagon \ No newline at end of file diff --git a/hass/settings/packages/alarm_and_presence/tags/tag_danil_keys.yaml b/hass/settings/packages/alarm_and_presence/tags/tag_danil_keys.yaml new file mode 100644 index 0000000..e4d6fbd --- /dev/null +++ b/hass/settings/packages/alarm_and_presence/tags/tag_danil_keys.yaml @@ -0,0 +1,17 @@ +sensor: + - platform: mqtt_room + device_id: !secret tag_danil_keys + name: tag_danil_keys + state_topic: home/presence + timeout: 15 + away_timeout: 60 + +input_boolean: + tag_danil_keys: + name: Данил (Ключи) + initial: on + +homeassistant: + customize: + sensor.tag_danil_keys: + friendly_name: Данил (Ключи) \ No newline at end of file diff --git a/hass/settings/packages/alarm_and_presence/tags/tag_danil_pocket.yaml b/hass/settings/packages/alarm_and_presence/tags/tag_danil_pocket.yaml new file mode 100644 index 0000000..585f4c7 --- /dev/null +++ b/hass/settings/packages/alarm_and_presence/tags/tag_danil_pocket.yaml @@ -0,0 +1,17 @@ +sensor: + - platform: mqtt_room + device_id: !secret tag_danil_pocket + name: tag_danil_pocket + state_topic: home/presence + timeout: 15 + away_timeout: 60 + +input_boolean: + tag_danil_pocket: + name: Данил (Кошелек) + initial: on + +homeassistant: + customize: + sensor.tag_danil_pocket: + friendly_name: Данил (Кошелек) \ No newline at end of file diff --git a/hass/settings/packages/alarm_and_presence/tags/tag_mamik_keys.yaml b/hass/settings/packages/alarm_and_presence/tags/tag_mamik_keys.yaml new file mode 100644 index 0000000..35e5219 --- /dev/null +++ b/hass/settings/packages/alarm_and_presence/tags/tag_mamik_keys.yaml @@ -0,0 +1,17 @@ +sensor: + - platform: mqtt_room + device_id: !secret tag_mama_keys + name: tag_mama_keys + state_topic: home/presence + timeout: 15 + away_timeout: 60 + +input_boolean: + tag_mama_keys: + name: Мама (Ключи) + initial: on + +homeassistant: + customize: + sensor.tag_mama_keys: + friendly_name: Мама (Ключи) \ No newline at end of file diff --git a/hass/settings/packages/alarm_and_presence/tags/tag_svetlana_keys.yaml b/hass/settings/packages/alarm_and_presence/tags/tag_svetlana_keys.yaml new file mode 100644 index 0000000..4e47fdf --- /dev/null +++ b/hass/settings/packages/alarm_and_presence/tags/tag_svetlana_keys.yaml @@ -0,0 +1,17 @@ +sensor: + - platform: mqtt_room + device_id: !secret tag_svetlana_keys + name: tag_svetlana_keys + state_topic: home/presence + timeout: 15 + away_timeout: 60 + +input_boolean: + tag_svetlana_keys: + name: Света (Ключи) + initial: on + +homeassistant: + customize: + sensor.tag_svetlana_keys: + friendly_name: Света (Ключи) \ No newline at end of file diff --git a/hass/settings/packages/alarm_and_presence/tags/tag_test.yaml b/hass/settings/packages/alarm_and_presence/tags/tag_test.yaml new file mode 100644 index 0000000..f97125a --- /dev/null +++ b/hass/settings/packages/alarm_and_presence/tags/tag_test.yaml @@ -0,0 +1,17 @@ +sensor: + - platform: mqtt_room + device_id: !secret tag_test + name: tag_test + state_topic: home/presence + timeout: 15 + away_timeout: 60 + +input_boolean: + tag_test: + name: Тестовая + initial: on + +homeassistant: + customize: + sensor.tag_test: + friendly_name: Тестовая \ No newline at end of file diff --git a/hass/settings/packages/alice_export.yaml b/hass/settings/packages/alice_export.yaml new file mode 100644 index 0000000..8da4815 --- /dev/null +++ b/hass/settings/packages/alice_export.yaml @@ -0,0 +1,63 @@ +yandex_smart_home: + filter: + include_domains: + - cover + - fan + - vacuum + include_entities: + # радиаторы + - climate.radiator_bedroom + - climate.radiator_bedroom_balcony + - climate.radiator_childroom + - climate.radiator_kitchen + - climate.radiator_mainroom + # кондиционеры + - climate.ac_mainroom + - climate.ac_kitchen + - climate.ac_childroom + - climate.ac_bedroom + # вентиляторы + - fan.ventilation_bath + - fan.ventilation_shower + - fan.ventiliatsionnaia_sistema + # свет + - light.danil_bedside_lamp_rgbww_light + - light.light_balcony_spotlight + - light.light_bath + - light.light_bath_shelf + - light.light_bedroom_mainlight + - light.light_bedroom_nightlight_left + - light.light_bedroom_nightlight_right + - light.light_bedroom_pc_nightlight + - light.light_bedroom_pc_spotlight + - light.light_bedroom_spotlights + - light.light_childroom_bed + - light.light_childroom_main + - light.light_childroom_spotlights + - light.light_hall_long + - light.light_kitchen_bar_lamp + - light.light_kitchen_set + - light.light_kitchen_spotlights + - light.light_kitchen_table_lamp + - light.light_mainroom_pc_spotlight + - light.light_shower_main_spotlights + - light.light_shower_shelf_light + - light.light_shower_shower_spotlights + - light.svetlana_bedside_lamp_rgbww_light + - media_player.bedroom_tv + - media_player.kitchen_tv + - media_player.mainroom_tv + - script.away_mode + - switch.light_bath_mirror + - switch.light_mainroom_mainlamp + - switch.light_mainroom_spotlights + - switch.shower_mirror + - switch.watercontrol_valves + - vacuum.dreame_p2027_6b1e_robot_cleaner + - light.plug_yolka + include_entity_globs: + - sensor.temperature_*_temperature + #exclude_entities: + # - light.highlight + #exclude_entity_globs: + # - sensor.weather_* \ No newline at end of file diff --git a/hass/settings/packages/alice_intents.yaml b/hass/settings/packages/alice_intents.yaml new file mode 100644 index 0000000..80eaece --- /dev/null +++ b/hass/settings/packages/alice_intents.yaml @@ -0,0 +1,13 @@ +yandex_station_intents: + intents: + Как дела: Все хорошо + Коля наказан: Отключаю интернет в детской + Включи вентилятор: + execute_command: Поставь режим вентиляции на кондиционере в комнате {{ event.room }} + Выключи вентилятор: + execute_command: Выключи кондиционер в комнате {{ event.room }} + + Не выключай свет в прихожей: # (4) + extra_phrases: + - Не выключай свет в коридоре + say_phrase: "{{ ['Договорились', 'Хорошо', 'Я тебя услышала', 'Оки-доки']|random }}" diff --git a/hass/settings/packages/aqara_lock.yaml b/hass/settings/packages/aqara_lock.yaml new file mode 100644 index 0000000..364fb1b --- /dev/null +++ b/hass/settings/packages/aqara_lock.yaml @@ -0,0 +1,7 @@ +mqtt: + sensor: + - name: door_lock_mqtt_action + unique_id: door_lock_mqtt_action + state_topic: "z2m-tcp/lock_door" + value_template: "{{ value_json.action }}" + json_attributes_topic: "z2m-tcp/lock_door" diff --git a/hass/settings/packages/automations_config.yaml b/hass/settings/packages/automations_config.yaml new file mode 100644 index 0000000..ff50815 --- /dev/null +++ b/hass/settings/packages/automations_config.yaml @@ -0,0 +1,62 @@ +input_boolean: + presence_control: + name: Контроль присутствия (действия по уходу и приходу) + icon: mdi:account-off + + water_leak_control: + name: Контроль протечек + icon: mdi:water-off + + water_control: + name: Контроль подачи воды + icon: mdi:water-pump + + security_sensors_report: + name: Охрана периметра при отсутствии + icon: mdi:access-point + + battery_report: + name: Отчет о состоянии батарей датчиков + icon: mdi:battery-charging-wireless-10 + + water_counters_report: + name: Отчет о поверке счетчиков + icon: mdi:alert-octagon + + balance_report: + name: Отчет о балансе + icon: mdi:alert-octagon + + watchdog_report: + name: Отчет watchdog + icon: mdi:lock-alert + + aqara_lock_report: + name: Отчет замка + icon: mdi:lock-alert + +############################# Автоматизации AppDaemon ############################# + kitchenset_light_control: + name: Контроль подсветки гарнитура + + bath_light_control: + name: Контроль света в ванной + + shower_light_control: + name: Контроль света в душе + + locker_light_control: + name: Контроль света шкаф + + vent_control: + name: Управление бризером + + vent_control_heat: + name: Управление подогревом бризера + + bath_mirror_heat: + name: Управление подогревом зеркала ванна + +############################# Настройки дома ############################# + radiator_enabled: + name: Наличие отопления \ No newline at end of file diff --git a/hass/settings/packages/base/recorder.yaml b/hass/settings/packages/base/recorder.yaml new file mode 100644 index 0000000..8bc0646 --- /dev/null +++ b/hass/settings/packages/base/recorder.yaml @@ -0,0 +1,40 @@ +recorder: + db_url: !secret recorder_db_url + purge_keep_days: 365 + include: + domains: + - alarm_control_panel + - binary_sensor + - climate + - light + - lock + - sensor + - switch + - vacuum + - person + - counter + exclude: + domains: + - automation + - media_player + entity_globs: + - sensor.tag_* + 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 diff --git a/hass/settings/packages/climate/climate_bedroom.yaml b/hass/settings/packages/climate/climate_bedroom.yaml new file mode 100644 index 0000000..fdbc532 --- /dev/null +++ b/hass/settings/packages/climate/climate_bedroom.yaml @@ -0,0 +1,18 @@ +# Example configuration.yaml entry +climate: + - platform: dual_smart_thermostat + name: bedroom_climate + target_sensor: sensor.temperature_bedroom_temperature + heater: input_boolean.bedroom_climate_heater_switch + cooler: input_boolean.bedroom_climate_cooler_switch + openings: + - binary_sensor.window1 + - binary_sensor.window2 + openings_scope: [heat, cool] + precision: 1 + +input_boolean: + bedroom_climate_heater_switch: + name: Виртуальный контроллер нагрева Спальня + bedroom_climate_cooler_switch: + name: Виртуальный контроллер охлаждения Спальня diff --git a/hass/settings/packages/coffee_maker.yaml b/hass/settings/packages/coffee_maker.yaml new file mode 100644 index 0000000..fb20882 --- /dev/null +++ b/hass/settings/packages/coffee_maker.yaml @@ -0,0 +1,103 @@ +#Используем сенсоры нового образца от 2023 +#Документация https://www.home-assistant.io/integrations/template/ + + + +#Это пример нового образца шаблонов +#template: +# - sensor: +# ... +# - binary_sensor: + + +template: +#Кухня: Кофемашина. Статус питания +#Объект: sensor.coffee_machine_power_status + - sensor: + - name: coffee_machine_power_status + unique_id: coffee_machine_power_status + icon: mdi:power + state: > + {% if not states('sensor.coffee_philips_5400_status') in ['Выключено','unknown']%} Включено + {% elif states('sensor.coffee_philips_5400_status') in ['Выключено','unknown'] or is_state('binary_sensor.coffee_philips_5400_power_status', 'off') %}Выключено + {% endif %} + attributes: + Making coffee: > + {% if not states('sensor.coffee_philips_5400_status') in ['Выключено','unknown'] %} + {{ states('sensor.coffee_philips_5400_status') }} + {% elif states('sensor.coffee_philips_5400_status') in ['Выключено','unknown'] or is_state('binary_sensor.coffee_philips_5400_power_status', 'off') %} Выключено + {% endif %} + + +group: +#Кухня: Кофемашина. Инфо и меню +#Объект: group.kitchen_coffee_machine_info_and_menu + kitchen_coffee_machine_info_and_menu: + name: "Кухня: Кофемашина. Сенсоры" + all: false + entities: + - sensor.coffee_philips_5400_esphome_version #Версия ESPHome + - sensor.coffee_philips_5400_coffee_grounds_container #Контейнер кофейной гущи + - sensor.coffee_philips_5400_coffee_pallet #Поддон + - sensor.coffee_philips_5400_grain_tray #Кофейные зерна в лотке + - sensor.coffee_philips_5400_system_status #Системный статус + - sensor.coffee_philips_5400_making_coffee #Приготовление кофе + - sensor.coffee_machine_power_status #Статус питания + - sensor.coffee_philips_5400_internal_temperature #Температура ESP32 + - sensor.coffee_philips_5400_error_code #Системные ошибки + - sensor.coffee_philips_5400_free_mem_size #Размер свободной памяти + - binary_sensor.coffee_philips_5400_power_status #Статус питания + - number.coffee_philips_5400_valve_closed #Отвод воды закрыл носик кофемашины + - number.coffee_philips_5400_valve_open #Отвод воды открыл носик кофемашины + - number.coffee_philips_5400_valve_position #Позиция сервопривода + - switch.coffee_philips_5400_valve_open_closed #Открыть или закрыть носик кофемашины + - switch.coffee_philips_5400_automatic_water_drain #Автоматически открывать или закрывать носик кофемашины + - input_boolean.kitchen_coffee_machine_auto_coffee_making #Авто приготовление кофе + - counter.coffee_machine_making_coffee #Счетчик для авто приготовление кофе + + + +#Кухня: Кофемашина. Кофейные напитки +#Объект: group.kitchen_coffee_machine_coffee_drinks + kitchen_coffee_machine_coffee_drinks: + name: "Кухня: Кофемашина. Кофейные напитки" + all: false + entities: + - select.coffee_philips_5400_drink #Выбор кофейных напиток + - select.coffee_philips_5400_cups #Выбор количество порции + - select.coffee_philips_5400_grind #Крепость, зерно или молотый кофе + - number.coffee_philips_5400_soffee #Объем кофе + - number.coffee_philips_5400_milk #Объем молока + - button.coffee_philips_5400_prepare #Показать рецепт в сенсоре 90 + - sensor.coffee_philips_5400_messge2_90 #Пакет рецепта + - sensor.coffee_philips_5400_coffee_drink #Сенсор выбранного кофейного напитка + - button.coffee_philips_5400_build_coffee #Приготовить кофе + +#Скрипт нужен для карточки, чтобы при нажатии на кнопку "Питание", кофемашина включилась или выключилась +script: + kitchen_coffee_machine_power: + alias: 'Кухня: Кофемашина. Питание' + sequence: + - choose: + - conditions: + - condition: template + value_template: '{{ is_state("binary_sensor.coffee_philips_5400_power_status","off") }}' + alias: Кофемашина выключена + sequence: + - service: button.press + data: {} + target: + entity_id: button.coffee_philips_5400_power_on + alias: Включить кофемашину + - conditions: + - condition: template + value_template: '{{ is_state("binary_sensor.coffee_philips_5400_power_status","on") }}' + alias: Кофемашина включена + sequence: + - service: button.press + data: {} + target: + entity_id: button.coffee_philips_5400_power_off + alias: Выключить кофемашину + mode: single + icon: mdi:power \ No newline at end of file diff --git a/hass/settings/packages/custom_components/yandex_station/yandex_station.yaml b/hass/settings/packages/custom_components/yandex_station/yandex_station.yaml new file mode 100644 index 0000000..6a49a04 --- /dev/null +++ b/hass/settings/packages/custom_components/yandex_station/yandex_station.yaml @@ -0,0 +1,3 @@ +yandex_station: + username: !secret alice_login + password: !secret alice_pwd \ No newline at end of file diff --git a/hass/settings/packages/dishwasher.yaml b/hass/settings/packages/dishwasher.yaml new file mode 100644 index 0000000..c02fd64 --- /dev/null +++ b/hass/settings/packages/dishwasher.yaml @@ -0,0 +1,76 @@ +# sensor: +# - platform: template +# sensors: +# dishwasher_remaining: +# friendly_name: "Dishwasher remaining" +# value_template: > +# {% if states('sensor.dishwasher_operation_state') == 'Inactive' %} - +# {% elif states('sensor.dishwasher_operation_state') == 'Finished' %} - +# {% elif states('sensor.dishwasher_remaining_program_time') == 'unavailable' %} - +# {% elif (as_timestamp(states('sensor.dishwasher_remaining_program_time')) - as_timestamp(now())) < 61 %} < 1 min +# {% else %} +# {{ (((as_timestamp(states('sensor.dishwasher_remaining_program_time')) - as_timestamp(now())) / 60) | round(0)) | string + " min" }} +# {% endif %} +# dishwasher_progress: +# friendly_name: "Dishwasher progress" +# value_template: > +# {% if states('sensor.dishwasher_operation_state') == 'Inactive' %} - +# {% elif states('sensor.dishwasher_operation_state') == 'Finished' %} - +# {% elif states('sensor.dishwasher_program_progress') == 'unavailable' %} - +# {% else %} +# {{ states('sensor.dishwasher_program_progress') + "%" }} +# {% endif %} + +mqtt: + sensor: + - name: hc_dishwasher_state + unique_id: hc_dishwasher_state + state_topic: "homeconnect/dishwasher/state" + value_template: "{{ value_json.state }}" + json_attributes_topic: "homeconnect/dishwasher/state" + - name: hc_dishwasher_remaining + unique_id: hc_dishwasher_remaining + state_topic: "homeconnect/dishwasher/state" + value_template: "{{ value_json.remaining }}" + json_attributes_topic: "homeconnect/dishwasher/state" + binary_sensor: + - state_topic: "homeconnect/dishwasher/state" + name: hc_dishwasher_door + unique_id: hc_dishwasher_door + device_class: opening + payload_on: "Open" + payload_off: "Closed" + value_template: "{{ value_json.door }}" + json_attributes_topic: "homeconnect/dishwasher/state" + - state_topic: "homeconnect/dishwasher/state" + name: hc_dishwasher_power + unique_id: hc_dishwasher_power + payload_on: true + payload_off: false + value_template: "{{ value_json.power }}" + json_attributes_topic: "homeconnect/dishwasher/state" +template: + binary_sensor: + - name: hc_dishwasher_running + unique_id: hc_dishwasher_running + state: "{{ is_state('sensor.hc_dishwasher_state', 'Run') }}" + +counter: + dishwasher_door_open_count: + name: dishwasher_door_open_count + +homeassistant: + customize: + sensor.hc_dishwasher_state: + friendly_name: Посудомойка Статус + binary_sensor.hc_dishwasher_door: + friendly_name: Посудомойка Дверца + binary_sensor.hc_dishwasher_remaining: + friendly_name: Посудомойка До конца программы + sensor.hc_dishwasher_running: + friendly_name: Посудомоечная машина + binary_sensor.hc_dishwasher_power: + friendly_name: Посудомойка Питание + + +# homeconnect/dishwasher/state {"state": "Ready", "door": "Closed", "remaining": "0:15", "power": false, "lowwaterpressure": false, "aquastop": false, "error": false, "remainingseconds": 900} \ No newline at end of file diff --git a/hass/settings/packages/groups.yaml b/hass/settings/packages/groups.yaml new file mode 100644 index 0000000..e69de29 diff --git a/hass/settings/packages/heat_counter.yaml b/hass/settings/packages/heat_counter.yaml new file mode 100644 index 0000000..d739b91 --- /dev/null +++ b/hass/settings/packages/heat_counter.yaml @@ -0,0 +1,35 @@ +template: +- sensor: + - name: heat_total + unit_of_measurement: gKal + default_entity_id: sensor.heat_total + state: '{{ states(''sensor.saures_teploschetchik_fonvizina'') | float + 32.7600 | round(4) }}' + - name: heat_hourly_cost + unit_of_measurement: '₽' + state: "{{ states('sensor.heat_hourly') | float * 3035 | round(2) }}" + - name: heat_daily_cost + unit_of_measurement: '₽' + state: "{{ states('sensor.heat_daily') | float * 3035 | round(2) }}" + - name: heat_monthly_cost + unit_of_measurement: '₽' + state: "{{ states('sensor.heat_monthly') | float * 3035 | round(2) }}" + +utility_meter: + heat_hourly: + source: sensor.saures_teploschetchik_fonvizina + cycle: hourly + heat_daily: + source: sensor.saures_teploschetchik_fonvizina + cycle: daily + heat_monthly: + source: sensor.saures_teploschetchik_fonvizina + cycle: monthly + +homeassistant: + customize: + sensor.heat_hourly: + friendly_name: Тепло в час + sensor.heat_hourly: + friendly_name: Тепло в день + sensor.heat_hourly: + friendly_name: Тепло в месяц diff --git a/hass/settings/packages/notifications/telegram.yaml b/hass/settings/packages/notifications/telegram.yaml new file mode 100644 index 0000000..132d378 --- /dev/null +++ b/hass/settings/packages/notifications/telegram.yaml @@ -0,0 +1,21 @@ +telegram_bot: + - platform: polling + api_key: !secret telegram_key + allowed_chat_ids: + - !secret telegram_group_chat + - !secret telegram_monster_chat + - !secret telegram_sveta_chat + +notify: + - platform: telegram + name: telegram + chat_id: !secret telegram_group_chat + parse_mode: html + - platform: telegram + name: telegram_monster + chat_id: !secret telegram_monster_chat + parse_mode: html + - platform: telegram + name: telegram_sveta + chat_id: !secret telegram_sveta_chat + parse_mode: html diff --git a/hass/settings/packages/power/power_counter.yaml b/hass/settings/packages/power/power_counter.yaml new file mode 100644 index 0000000..b6f2669 --- /dev/null +++ b/hass/settings/packages/power/power_counter.yaml @@ -0,0 +1,191 @@ +# recorder: +# include: +# entities: +# - sensor.power_total +# - sensor.power_active_power +# - sensor.power_monthly_cost_t1 +# - sensor.power_monthly_cost_t2 +# - sensor.power_monthly_cost_t3 + +modbus: + name: modbus_power + type: serial + method: rtu + port: /dev/ttyUSB0 + baudrate: 9600 + stopbits: 1 + bytesize: 8 + parity: N #N + sensors: + - name: power_total + unit_of_measurement: kWh + slave: 1 + address: 10 + precision: 2 + scale: 0.01 + data_type: uint32 + + - name: power_voltage_a + slave: 1 + address: 128 + precision: 1 + scale: 0.1 + unit_of_measurement: 'V' + + - name: power_voltage_b + slave: 1 + address: 129 + precision: 1 + scale: 0.1 + unit_of_measurement: 'V' + + - name: power_voltage_c + slave: 1 + address: 130 + precision: 1 + scale: 0.1 + unit_of_measurement: 'V' + + - name: power_current_a + slave: 1 + address: 131 + precision: 2 + scale: 0.01 + unit_of_measurement: 'A' + + - name: power_current_b + slave: 1 + address: 132 + precision: 2 + scale: 0.01 + unit_of_measurement: 'A' + + - name: power_current_c + slave: 1 + address: 133 + precision: 2 + scale: 0.01 + unit_of_measurement: 'A' + + - name: power_active_power + slave: 1 + address: 134 + data_type: int32 + precision: 3 + scale: 0.001 + unit_of_measurement: kW + + - name: power_reactive_power + slave: 1 + address: 139 + data_type: int32 + precision: 3 + scale: 0.001 + unit_of_measurement: 'Kvar' + + - name: power_power_factor + slave: 1 + address: 149 + precision: 3 + scale: 0.001 + unit_of_measurement: '%' + + - name: power_frequency + slave: 1 + address: 17 + precision: 2 + scale: 0.01 + unit_of_measurement: 'Hz' + +sensor: + - platform: template + sensors: + power_main_total: + unit_of_measurement: 'kWh' + value_template: "{{ states('sensor.power_total') | float - 7072.7 | round(2) }}" + #7072.7 - разница показаний. + + power_monthly_cost_t1: + unit_of_measurement: '₽' + value_template: "{{ states('sensor.power_monthly_t1') | float * 8.23 | round(2) }}" + power_monthly_cost_t2: + unit_of_measurement: '₽' + value_template: "{{ states('sensor.power_monthly_t2') | float * 2.62 | round(2) }}" + power_monthly_cost_t3: + unit_of_measurement: '₽' + value_template: "{{ states('sensor.power_monthly_t3') | float * 5.66 | round(2) }}" + power_monthly_cost_total: + unit_of_measurement: '₽' + value_template: "{{ states('sensor.power_monthly_cost_t1') | float + states('sensor.power_monthly_cost_t2') | float + states('sensor.power_monthly_cost_t3') | float | round(2) }}" + +utility_meter: + power_hourly: + source: sensor.power_total + cycle: hourly + power_daily: + source: sensor.power_total + cycle: daily + power_monthly: + source: sensor.power_total + cycle: monthly + + power_theory: + source: sensor.power_total + cycle: monthly + tariffs: + - t1 + - t2 + - t3 + power_monthly_t1: + source: sensor.power_theory_t1 + cycle: monthly + power_monthly_t2: + source: sensor.power_theory_t2 + cycle: monthly + power_monthly_t3: + source: sensor.power_theory_t3 + cycle: monthly + +homeassistant: + customize: + sensor.power_main_total: + friendly_name: Общий счётчик + + sensor.power_total: + friendly_name: Потреблено итого + + sensor.power_voltage_a: + friendly_name: Напряжение A + + sensor.power_voltage_b: + friendly_name: Напряжение B + + sensor.power_voltage_c: + friendly_name: Напряжение C + + sensor.power_current_a: + friendly_name: Ток A + + sensor.power_current_b: + friendly_name: Ток B + + sensor.power_current_c: + friendly_name: Ток C + + 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/packages/tags_test.yaml b/hass/settings/packages/tags_test.yaml new file mode 100644 index 0000000..263187d --- /dev/null +++ b/hass/settings/packages/tags_test.yaml @@ -0,0 +1,59 @@ +sensor: +# Old phone - 75f45ca5a0b55b563f827339c91da5be + - platform: mqtt_room + device_id: "irk:f8cfda8942512292fd767127fd5028c1" + name: 'tag_danil_iphone' + state_topic: 'espresense/devices/irk:f8cfda8942512292fd767127fd5028c1' + timeout: 30 + away_timeout: 300 + + - platform: mqtt_room + device_id: "irk:23c0abe5a5167d5f3a9fed8f5811e2cb" + name: 'tag_svetlana_iphone' + state_topic: 'espresense/devices/irk:23c0abe5a5167d5f3a9fed8f5811e2cb' + timeout: 30 + away_timeout: 300 + + - platform: mqtt_room + device_id: "irk:ee497d98b502c47ec3f631336ec37eed" + name: 'tag_mama_iphone' + state_topic: 'espresense/devices/irk:ee497d98b502c47ec3f631336ec37eed' + timeout: 30 + away_timeout: 300 + + +homeassistant: + customize: + sensor.tag_danil_iphone: + friendly_name: Данил (телефон) + sensor.tag_svetlana_iphone: + friendly_name: Света (телефон) + sensor.tag_mama_iphone: + friendly_name: Мама (телефон) + +automation: + alias: "Presence: Beacons" + description: "" + trigger: + - platform: state + entity_id: + - sensor.tag_danil_iphone + - sensor.tag_svetlana_iphone + - sensor.tag_mama_iphone + enabled: true + condition: [] + action: + - service: device_tracker.see + data: + dev_id: "{{ trigger.entity_id.split('.')[1] }}" + location_name: | + {% set locations = ['mainroom', 'kitchen'] %} + {% if trigger.to_state.state in locations %} + home + {% elif trigger.to_state.state not in locations %} + not_home + {% else %} + unknown + {% endif %} + source_type: router + mode: restart \ No newline at end of file diff --git a/hass/settings/packages/tv/bedroom_tv.yaml b/hass/settings/packages/tv/bedroom_tv.yaml new file mode 100644 index 0000000..34d1810 --- /dev/null +++ b/hass/settings/packages/tv/bedroom_tv.yaml @@ -0,0 +1,26 @@ +rest_command: + bedroom_tv_wol: + url: "http://192.168.1.1/wol_action.asp?dstmac=C0:D7:AA:AA:1B:12" + method: get + content_type: "application/json" + username: !secret router_username + password: !secret router_password + +automation: + - id: 'bedroomtv_wakeup' + alias: "Wake up bedroom tv on turn_on" + trigger: + - platform: webostv.turn_on + entity_id: media_player.bedroom_tv + action: + - service: cover.set_cover_position + target: + entity_id: cover.curtain_bedroom + data: + position: 38 + - service: rest_command.bedroom_tv_wol + - delay: 5 + - service: media_player.select_source + data: + entity_id: media_player.bedroom_tv + source: YouTube diff --git a/hass/settings/packages/tv/kitchen_tv.yaml b/hass/settings/packages/tv/kitchen_tv.yaml new file mode 100644 index 0000000..0ae6f3e --- /dev/null +++ b/hass/settings/packages/tv/kitchen_tv.yaml @@ -0,0 +1,22 @@ +# Either put this here or in your automations.yaml +rest_command: + kitchen_tv_wol: + url: "http://192.168.1.1/wol_action.asp?dstmac=B4:B2:91:C0:A7:FD" + method: get + content_type: "application/json" + username: !secret router_username + password: !secret router_password + +automation: + - id: 'kitchentv_wakeup' + alias: "Wake up kitchen tv on turn_on" + trigger: + - platform: webostv.turn_on + entity_id: media_player.kitchen_tv + action: + - service: rest_command.kitchen_tv_wol + - delay: 10 + - service: media_player.select_source + data: + entity_id: media_player.kitchen_tv + source: Wink diff --git a/hass/settings/packages/tv/mainroom_tv.yaml b/hass/settings/packages/tv/mainroom_tv.yaml new file mode 100644 index 0000000..71a4aa1 --- /dev/null +++ b/hass/settings/packages/tv/mainroom_tv.yaml @@ -0,0 +1,23 @@ +# Either put this here or in your automations.yaml +rest_command: + mainroom_tv_wol: + url: "http://192.168.1.1/wol_action.asp?dstmac=AC:5A:F0:99:65:11" + method: get + content_type: "application/json" + username: !secret router_username + password: !secret router_password + + +automation: + - id: 'mainroomtv_wakeup' + alias: "Wake up mainroom tv on turn_on" + trigger: + - platform: webostv.turn_on + entity_id: media_player.mainroom_tv + action: + - service: rest_command.mainroom_tv_wol + - delay: 5 + - service: media_player.select_source + data: + entity_id: media_player.mainroom_tv + source: Wink diff --git a/hass/settings/packages/watercontrol.yaml b/hass/settings/packages/watercontrol.yaml new file mode 100644 index 0000000..5e7bc40 --- /dev/null +++ b/hass/settings/packages/watercontrol.yaml @@ -0,0 +1,65 @@ +################################################# +# WATER # +################################################# +mqtt: + sensor: + - unique_id: counter_cold + state_topic: 'home/watercontrol/counter2_1' + name: 'counter_cold' + value_template: '{{ (value | float ) }}' + unit_of_measurement: 'm³' + device_class: water + state_class: total_increasing + - unique_id: counter_hot + state_topic: 'home/watercontrol/counter2_2' + name: 'counter_hot' + value_template: '{{ (value | float ) }}' + unit_of_measurement: 'm³' + device_class: water + state_class: total_increasing + - unique_id: uptime_watercontrol + state_topic: 'home/watercontrol/$uptime' + name: 'uptime_watercontrol' + value_template: '{{ value | int }}' + switch: + - unique_id: water_valve + name: water_valve + state_topic: "home/watercontrol/valves" + command_topic: "home/watercontrol/valves/set" + payload_on: true + payload_off: false + optimistic: false + retain: false + +template: + sensor: + - name: counter_hot_outer + device_class: water + state_class: total_increasing + unit_of_measurement: 'm³' + state: "{{ states('sensor.counter_hot') | float + 88.97 | round(2) }}" + - name: counter_cold_outer + device_class: water + state_class: total_increasing + unit_of_measurement: 'm³' + state: "{{ states('sensor.counter_cold') | float + 144.7 | round(2) }}" + + binary_sensor: + - name: dryer_running + state: "{{ not is_state('sensor.sushilnaia_mash_machine_state', 'stop') }}" + - name: washer_running + state: "{{ not is_state('sensor.stiralnaia_mashina_machine_state', 'stop') }}" + +homeassistant: + customize: + switch.water_valve: + friendly_name: Подача воды + binary_sensor.dryer_running: + friendly_name: "Сушильная машина" + binary_sensor.washer_running: + friendly_name: "Стиральная машина" + + sensor.counter_hot_outer: + friendly_name: "Счетчик Внешний Горячая" + sensor.counter_cold_outer: + friendly_name: "Счетчик Внешний Холодная" diff --git a/hass/settings/scenes.yaml b/hass/settings/scenes.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/hass/settings/scenes.yaml @@ -0,0 +1 @@ +[] diff --git a/hass/settings/scripts.yaml b/hass/settings/scripts.yaml new file mode 100644 index 0000000..09614f6 --- /dev/null +++ b/hass/settings/scripts.yaml @@ -0,0 +1,71 @@ +new_script: + alias: Сценарий Я Ушёл + sequence: + - service: switch.turn_off + data: {} + target: + entity_id: switch.watercontrol_valves + - service: climate.set_preset_mode + data: + preset_mode: away + target: + area_id: + - gostinaia + - kukhnia + - spalnia + - detskaia + - service: media_player.turn_off + data: {} + target: + device_id: + - 368a8521d684580dd39b88502808b3bc + - 07a253973c89fdb7e7b66174bf5a1275 + - b60210b82dc048bffc5a59f683de2f0a + - service: cover.close_cover + data: {} + target: + area_id: + - gostinaia + - kukhnia + - spalnia + mode: single + icon: mdi:shield-lock +'1675839976060': + alias: Вскипяти чайник + sequence: + - service: input_select.set_options + data: + options: Boil Water + target: + entity_id: select.chunmi_a1_35e9_cook_mode + mode: single +'1676313705945': + alias: Сценарий Я Дома + sequence: + - service: switch.turn_on + data: {} + target: + entity_id: switch.watercontrol_valves + - service: climate.set_preset_mode + data: + preset_mode: manual + target: + area_id: + - gostinaia + - kukhnia + - spalnia + - detskaia + - if: + - condition: sun + before: sunset + after: sunrise + then: + - service: cover.open_cover + data: {} + target: + area_id: + - gostinaia + - kukhnia + - spalnia + mode: single + icon: mdi:shield-lock diff --git a/hass/settings/secrets.yaml b/hass/settings/secrets.yaml new file mode 100755 index 0000000..e69de29 diff --git a/hass/settings/secrets.yaml.sample b/hass/settings/secrets.yaml.sample new file mode 100644 index 0000000..28ff92e --- /dev/null +++ b/hass/settings/secrets.yaml.sample @@ -0,0 +1,167 @@ + +################################### +# MAIN SETTINGS # +################################### +ha_password: xxxxxxxxx +timezone: Europe/London +base_url: xxxxxxxxx +base_host: xxxxxxxxx +base_localname: xxxxxxxxx +ha_internal_ip: xxxxxxxxx +modbus_host: modbus.host +modbus_port: 9977 +modbus_admin_pass: xxxxxxxxx + +yandex_tts_key: xxxxxxxxx +################################### +# RECORDER SETTINGS # +################################### +mysql_url: xxxxxxxxx + +################################### +# ESPHOME SETTINGS # +################################### +wifi_24_name: xxxxxxxxx +wifi_24_pass: xxxxxxxxx +esphome_pass: xxxxxxxxx +esphome_captive_pass: xxxxxxxxx + +################################### +# APPLETV SETTINGS # +################################### +atv4_ip: xxxxxxxxx +atv4_login: xxxxxxxxx +atv4_credentials: xxxxxxxxx + +################################### +# PROXY SETTINGS # +################################### +#proxy_url: xxxxxxxxx +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 + +purifier_ip: xxxxxxxxx +purifier_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +breaser_ip: xxxxxxxxx +breaser_token: xxxxxxxxx + +xiaomi_remote_host: xxxxxxxxx +xiaomi_remote_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +################################### +# NEST # +################################### +nest_client_id: xxxxxxxxx +nest_client_secret: xxxxxxxxx + +################################### +# NOTIFICATION SETTINGS # +################################### +telegram_key: xxxxxxxxx +telegram_group_chat: 0 +telegram_monster_chat: 0 +telegram_sveta_chat: xxxxxxxxx +telegram_card_chat: xxxxxxxxx + +################################### +# TAGS # +################################### +tag_danil_pocket: xxxxxxxxx +tag_danil_keys: xxxxxxxxx +tag_svetlana_keys: xxxxxxxxx +tag_mama_keys: xxxxxxxxx +tag_test: xxxxxxxxx + +################################### +# ZONES # +################################### +home_latitude: 1.0 +home_longitude: 1.0 +home_coords: xxxxxxxxx + +work_latitude: 1.0 +work_longitude: 1.0 +work_coords: xxxxxxxxx + +################################### +# SYNOLOGY SETTINGS # +################################### +syno_url: xxxxxxxxx +syno_ip: xxxxxxxxx +syno_user: xxxxxxxxx +syno_pass: xxxxxxxxx +rtsp_entrance: 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_session: xxxxxxxxx +#mosru_flat_id: xxxxxxxxx +mosru_flat_id: xxxxxxxxx +mosru_paycode: xxxxxxxxx +mosru_belorechenskaya_flat_id: xxxxxxxxx +mosru_belorechenskaya_paycode: xxxxxxxxx + +################################### +# Amazon Alexa # +################################### +amazon_mail: xxxxxxxxx +amazon_pass: xxxxxxxxx +alexa_client_id: xxxxxxxxx +alexa_client_secret: xxxxxxxxx + +################################### +# Yandex Alice # +################################### +alice_login: xxxxxxxxx +alice_pwd: xxxxxxxxx + +################################### +# Tuya # +################################### +tuya_username: xxxxxxxxx +tuya_password: xxxxxxxxx + +################################### +# iCloud # +################################### +icloud_mail: xxxxxxxxx +icloud_password: xxxxxxxxx + +################################### +# InfluxDb # +################################### +influxdb_token: xxxxxxxxx \ No newline at end of file diff --git a/hass/settings/ui-lovelace.yaml b/hass/settings/ui-lovelace.yaml new file mode 100644 index 0000000..6f2fb30 --- /dev/null +++ b/hass/settings/ui-lovelace.yaml @@ -0,0 +1,23 @@ +title: Home +views: +type: entities +entities: + - entity: switch.dishwasher_power + type: 'custom:multiple-entity-row' + name: Dishwasher + show_state: false + state_color: true + secondary_info: + entity: sensor.dishwasher_operation_state + name: 'Status:' + icon: 'mdi:dishwasher' + entities: + - entity: sensor.dishwasher_progress + name: Progress + - entity: sensor.dishwasher_remaining + name: Remaining + - entity: binary_sensor.dishwasher_door + name: false + icon: true + state_color: true + - entity: switch.water_valve diff --git a/hass/settings/www/community/jk-bms-card/jk-bms-card.js b/hass/settings/www/community/jk-bms-card/jk-bms-card.js new file mode 100644 index 0000000..92c22f9 --- /dev/null +++ b/hass/settings/www/community/jk-bms-card/jk-bms-card.js @@ -0,0 +1,34 @@ +function e(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function t(e,t,n,l){var s,a=arguments.length,i=a<3?t:null===l?l=Object.getOwnPropertyDescriptor(t,n):l;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,l);else for(var r=e.length-1;r>=0;r--)(s=e[r])&&(i=(a<3?s(i):a>3?s(t,n,i):s(t,n))||i);return a>3&&i&&Object.defineProperty(t,n,i),i}"function"==typeof SuppressedError&&SuppressedError; +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const n=globalThis,l=n.ShadowRoot&&(void 0===n.ShadyCSS||n.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s=Symbol(),a=new WeakMap;let i=class{constructor(e,t,n){if(this._$cssResult$=!0,n!==s)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(l&&void 0===e){const n=void 0!==t&&1===t.length;n&&(e=a.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&a.set(t,e))}return e}toString(){return this.cssText}};const r=(e,...t)=>{const n=1===e.length?e[0]:t.reduce(((t,n,l)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+e[l+1]),e[0]);return new i(n,e,s)},c=l?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const n of e.cssRules)t+=n.cssText;return(e=>new i("string"==typeof e?e:e+"",void 0,s))(t)})(e):e +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */,{is:o,defineProperty:_,getOwnPropertyDescriptor:d,getOwnPropertyNames:h,getOwnPropertySymbols:g,getPrototypeOf:p}=Object,u=globalThis,v=u.trustedTypes,m=v?v.emptyScript:"",f=u.reactiveElementPolyfillSupport,b=(e,t)=>e,y={toAttribute(e,t){switch(t){case Boolean:e=e?m:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let n=e;switch(t){case Boolean:n=null!==e;break;case Number:n=null===e?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch(e){n=null}}return n}},x=(e,t)=>!o(e,t),C={attribute:!0,type:String,converter:y,reflect:!1,useDefault:!1,hasChanged:x};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let S=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=C){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const n=Symbol(),l=this.getPropertyDescriptor(e,n,t);void 0!==l&&_(this.prototype,e,l)}}static getPropertyDescriptor(e,t,n){const{get:l,set:s}=d(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:l,set(t){const a=l?.call(this);s?.call(this,t),this.requestUpdate(e,a,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??C}static _$Ei(){if(this.hasOwnProperty(b("elementProperties")))return;const e=p(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(b("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(b("properties"))){const e=this.properties,t=[...h(e),...g(e)];for(const n of t)this.createProperty(n,e[n])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,n]of t)this.elementProperties.set(e,n)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const n=this._$Eu(e,t);void 0!==n&&this._$Eh.set(n,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const n=new Set(e.flat(1/0).reverse());for(const e of n)t.unshift(c(e))}else void 0!==e&&t.push(c(e));return t}static _$Eu(e,t){const n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise((e=>this.enableUpdating=e)),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach((e=>e(this)))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const n of t.keys())this.hasOwnProperty(n)&&(e.set(n,this[n]),delete this[n]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{if(l)e.adoptedStyleSheets=t.map((e=>e instanceof CSSStyleSheet?e:e.styleSheet));else for(const l of t){const t=document.createElement("style"),s=n.litNonce;void 0!==s&&t.setAttribute("nonce",s),t.textContent=l.cssText,e.appendChild(t)}})(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach((e=>e.hostConnected?.()))}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach((e=>e.hostDisconnected?.()))}attributeChangedCallback(e,t,n){this._$AK(e,n)}_$ET(e,t){const n=this.constructor.elementProperties.get(e),l=this.constructor._$Eu(e,n);if(void 0!==l&&!0===n.reflect){const s=(void 0!==n.converter?.toAttribute?n.converter:y).toAttribute(t,n.type);this._$Em=e,null==s?this.removeAttribute(l):this.setAttribute(l,s),this._$Em=null}}_$AK(e,t){const n=this.constructor,l=n._$Eh.get(e);if(void 0!==l&&this._$Em!==l){const e=n.getPropertyOptions(l),s="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:y;this._$Em=l,this[l]=s.fromAttribute(t,e.type)??this._$Ej?.get(l)??null,this._$Em=null}}requestUpdate(e,t,n){if(void 0!==e){const l=this.constructor,s=this[e];if(n??=l.getPropertyOptions(e),!((n.hasChanged??x)(s,t)||n.useDefault&&n.reflect&&s===this._$Ej?.get(e)&&!this.hasAttribute(l._$Eu(e,n))))return;this.C(e,t,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:n,reflect:l,wrapped:s},a){n&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,a??t??this[e]),!0!==s||void 0!==a)||(this._$AL.has(e)||(this.hasUpdated||n||(t=void 0),this._$AL.set(e,t)),!0===l&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,n]of e){const{wrapped:e}=n,l=this[t];!0!==e||this._$AL.has(t)||void 0===l||this.C(t,void 0,n,l)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach((e=>e.hostUpdate?.())),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach((e=>e.hostUpdated?.())),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach((e=>this._$ET(e,this[e]))),this._$EM()}updated(e){}firstUpdated(e){}};S.elementStyles=[],S.shadowRootOptions={mode:"open"},S[b("elementProperties")]=new Map,S[b("finalized")]=new Map,f?.({ReactiveElement:S}),(u.reactiveElementVersions??=[]).push("2.1.0"); +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const w=globalThis,$=w.trustedTypes,k=$?$.createPolicy("lit-html",{createHTML:e=>e}):void 0,A="$lit$",E=`lit$${Math.random().toFixed(9).slice(2)}$`,M="?"+E,O=`<${M}>`,T=document,V=()=>T.createComment(""),z=e=>null===e||"object"!=typeof e&&"function"!=typeof e,D=Array.isArray,N="[ \t\n\f\r]",P=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,I=/-->/g,L=/>/g,U=RegExp(`>|${N}(?:([^\\s"'>=/]+)(${N}*=${N}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),H=/'/g,F=/"/g,R=/^(?:script|style|textarea|title)$/i,j=(e=>(t,...n)=>({_$litType$:e,strings:t,values:n}))(1),B=Symbol.for("lit-noChange"),K=Symbol.for("lit-nothing"),W=new WeakMap,J=T.createTreeWalker(T,129);function Z(e,t){if(!D(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==k?k.createHTML(t):t}const q=(e,t)=>{const n=e.length-1,l=[];let s,a=2===t?"":3===t?"":"",i=P;for(let t=0;t"===c[0]?(i=s??P,o=-1):void 0===c[1]?o=-2:(o=i.lastIndex-c[2].length,r=c[1],i=void 0===c[3]?U:'"'===c[3]?F:H):i===F||i===H?i=U:i===I||i===L?i=P:(i=U,s=void 0);const d=i===U&&e[t+1].startsWith("/>")?" ":"";a+=i===P?n+O:o>=0?(l.push(r),n.slice(0,o)+A+n.slice(o)+E+d):n+E+(-2===o?t:d)}return[Z(e,a+(e[n]||"")+(2===t?"":3===t?"":"")),l]};class G{constructor({strings:e,_$litType$:t},n){let l;this.parts=[];let s=0,a=0;const i=e.length-1,r=this.parts,[c,o]=q(e,t);if(this.el=G.createElement(c,n),J.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(l=J.nextNode())&&r.length0){l.textContent=$?$.emptyScript:"";for(let n=0;nD(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==K&&z(this._$AH)?this._$AA.nextSibling.data=e:this.T(T.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:n}=e,l="number"==typeof n?this._$AC(e):(void 0===n.el&&(n.el=G.createElement(Z(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===l)this._$AH.p(t);else{const e=new X(l,this),n=e.u(this.options);e.p(t),this.T(n),this._$AH=e}}_$AC(e){let t=W.get(e.strings);return void 0===t&&W.set(e.strings,t=new G(e)),t}k(e){D(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let n,l=0;for(const s of e)l===t.length?t.push(n=new Y(this.O(V()),this.O(V()),this,this.options)):n=t[l],n._$AI(s),l++;l2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=K}_$AI(e,t=this,n,l){const s=this.strings;let a=!1;if(void 0===s)e=Q(this,e,t,0),a=!z(e)||e!==this._$AH&&e!==B,a&&(this._$AH=e);else{const l=e;let i,r;for(e=s[0],i=0;i{const l=n?.renderBefore??t;let s=l._$litPart$;if(void 0===s){const e=n?.renderBefore??null;l._$litPart$=s=new Y(t.insertBefore(V(),e),e,void 0,n??{})}return s._$AI(e),s})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return B}}re._$litElement$=!0,re.finalized=!0,ie.litElementHydrateSupport?.({LitElement:re});const ce=ie.litElementPolyfillSupport;ce?.({LitElement:re}),(ie.litElementVersions??=[]).push("4.2.0"); +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const oe=e=>(t,n)=>{void 0!==n?n.addInitializer((()=>{customElements.define(e,t)})):customElements.define(e,t)} +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */,_e={attribute:!0,type:String,converter:y,reflect:!1,hasChanged:x},de=(e=_e,t,n)=>{const{kind:l,metadata:s}=n;let a=globalThis.litPropertyMetadata.get(s);if(void 0===a&&globalThis.litPropertyMetadata.set(s,a=new Map),"setter"===l&&((e=Object.create(e)).wrapped=!0),a.set(n.name,e),"accessor"===l){const{name:l}=n;return{set(n){const s=t.get.call(this);t.set.call(this,n),this.requestUpdate(l,s,e)},init(t){return void 0!==t&&this.C(l,void 0,e,t),t}}}if("setter"===l){const{name:l}=n;return function(n){const s=this[l];t.call(this,n),this.requestUpdate(l,s,e)}}throw Error("Unsupported decorator location: "+l)};function he(e){return(t,n)=>"object"==typeof n?de(e,t,n):((e,t,n)=>{const l=t.hasOwnProperty(n);return t.constructor.createProperty(n,e),l?Object.getOwnPropertyDescriptor(t,n):void 0})(e,t,n)}const ge="jk-bms-content-card-editor",pe="jk-bms-card";var ue;!function(e){e.delta_cell_voltage="delta_cell_voltage",e.balancing="balancing",e.balancing_current="balancing_current",e.power="power",e.balance_trigger_voltage="balance_trigger_voltage",e.total_runtime_formatted="total_runtime_formatted",e.charging="charging",e.charging_power="charging_power",e.discharging="discharging",e.discharging_power="discharging_power",e.balancer="balancer",e.heater="heater",e.total_voltage="total_voltage",e.total_battery_capacity_setting="total_battery_capacity_setting",e.total_charging_cycle_capacity="total_charging_cycle_capacity",e.average_cell_voltage="average_cell_voltage",e.current="current",e.state_of_charge="state_of_charge",e.capacity_remaining="capacity_remaining",e.charging_cycles="charging_cycles",e.power_tube_temperature="power_tube_temperature",e.min_voltage_cell="min_voltage_cell",e.max_voltage_cell="max_voltage_cell",e.errors="errors",e.software_version="software_version",e.hardware_version="hardware_version",e.cell_voltage_1="cell_voltage_1",e.cell_voltage_2="cell_voltage_2",e.cell_voltage_3="cell_voltage_3",e.cell_voltage_4="cell_voltage_4",e.cell_voltage_5="cell_voltage_5",e.cell_voltage_6="cell_voltage_6",e.cell_voltage_7="cell_voltage_7",e.cell_voltage_8="cell_voltage_8",e.cell_voltage_9="cell_voltage_9",e.cell_voltage_10="cell_voltage_10",e.cell_voltage_11="cell_voltage_11",e.cell_voltage_12="cell_voltage_12",e.cell_voltage_13="cell_voltage_13",e.cell_voltage_14="cell_voltage_14",e.cell_voltage_15="cell_voltage_15",e.cell_voltage_16="cell_voltage_16",e.cell_voltage_17="cell_voltage_17",e.cell_voltage_18="cell_voltage_18",e.cell_voltage_19="cell_voltage_19",e.cell_voltage_20="cell_voltage_20",e.cell_voltage_21="cell_voltage_21",e.cell_voltage_22="cell_voltage_22",e.cell_voltage_23="cell_voltage_23",e.cell_voltage_24="cell_voltage_24",e.cell_voltage_25="cell_voltage_25",e.cell_voltage_26="cell_voltage_26",e.cell_voltage_27="cell_voltage_27",e.cell_voltage_28="cell_voltage_28",e.cell_voltage_29="cell_voltage_29",e.cell_voltage_30="cell_voltage_30",e.cell_voltage_31="cell_voltage_31",e.cell_voltage_32="cell_voltage_32",e.cell_voltage_33="cell_voltage_33",e.cell_voltage_34="cell_voltage_34",e.cell_voltage_35="cell_voltage_35",e.cell_voltage_36="cell_voltage_36",e.cell_voltage_37="cell_voltage_37",e.cell_voltage_38="cell_voltage_38",e.cell_voltage_39="cell_voltage_39",e.cell_voltage_40="cell_voltage_40",e.cell_voltage_41="cell_voltage_41",e.cell_voltage_42="cell_voltage_42",e.cell_voltage_43="cell_voltage_43",e.cell_voltage_44="cell_voltage_44",e.cell_voltage_45="cell_voltage_45",e.cell_voltage_46="cell_voltage_46",e.cell_voltage_47="cell_voltage_47",e.cell_voltage_48="cell_voltage_48",e.cell_voltage_49="cell_voltage_49",e.cell_voltage_50="cell_voltage_50",e.cell_voltage_51="cell_voltage_51",e.cell_voltage_52="cell_voltage_52",e.cell_voltage_53="cell_voltage_53",e.cell_voltage_54="cell_voltage_54",e.cell_voltage_55="cell_voltage_55",e.cell_voltage_56="cell_voltage_56",e.cell_voltage_57="cell_voltage_57",e.cell_voltage_58="cell_voltage_58",e.cell_voltage_59="cell_voltage_59",e.cell_voltage_60="cell_voltage_60",e.cell_voltage_61="cell_voltage_61",e.cell_voltage_62="cell_voltage_62",e.cell_voltage_63="cell_voltage_63",e.cell_voltage_64="cell_voltage_64",e.cell_voltage_65="cell_voltage_65",e.cell_voltage_66="cell_voltage_66",e.cell_voltage_67="cell_voltage_67",e.cell_voltage_68="cell_voltage_68",e.cell_voltage_69="cell_voltage_69",e.cell_voltage_70="cell_voltage_70",e.cell_voltage_71="cell_voltage_71",e.cell_voltage_72="cell_voltage_72",e.cell_voltage_73="cell_voltage_73",e.cell_voltage_74="cell_voltage_74",e.cell_voltage_75="cell_voltage_75",e.cell_voltage_76="cell_voltage_76",e.cell_voltage_77="cell_voltage_77",e.cell_voltage_78="cell_voltage_78",e.cell_voltage_79="cell_voltage_79",e.cell_voltage_80="cell_voltage_80",e.cell_voltage_81="cell_voltage_81",e.cell_voltage_82="cell_voltage_82",e.cell_voltage_83="cell_voltage_83",e.cell_voltage_84="cell_voltage_84",e.cell_voltage_85="cell_voltage_85",e.cell_voltage_86="cell_voltage_86",e.cell_voltage_87="cell_voltage_87",e.cell_voltage_88="cell_voltage_88",e.cell_voltage_89="cell_voltage_89",e.cell_voltage_90="cell_voltage_90",e.cell_voltage_91="cell_voltage_91",e.cell_voltage_92="cell_voltage_92",e.cell_voltage_93="cell_voltage_93",e.cell_voltage_94="cell_voltage_94",e.cell_voltage_95="cell_voltage_95",e.cell_voltage_96="cell_voltage_96",e.cell_resistance_1="cell_resistance_1",e.cell_resistance_2="cell_resistance_2",e.cell_resistance_3="cell_resistance_3",e.cell_resistance_4="cell_resistance_4",e.cell_resistance_5="cell_resistance_5",e.cell_resistance_6="cell_resistance_6",e.cell_resistance_7="cell_resistance_7",e.cell_resistance_8="cell_resistance_8",e.cell_resistance_9="cell_resistance_9",e.cell_resistance_10="cell_resistance_10",e.cell_resistance_11="cell_resistance_11",e.cell_resistance_12="cell_resistance_12",e.cell_resistance_13="cell_resistance_13",e.cell_resistance_14="cell_resistance_14",e.cell_resistance_15="cell_resistance_15",e.cell_resistance_16="cell_resistance_16",e.cell_resistance_17="cell_resistance_17",e.cell_resistance_18="cell_resistance_18",e.cell_resistance_19="cell_resistance_19",e.cell_resistance_20="cell_resistance_20",e.cell_resistance_21="cell_resistance_21",e.cell_resistance_22="cell_resistance_22",e.cell_resistance_23="cell_resistance_23",e.cell_resistance_24="cell_resistance_24",e.cell_resistance_25="cell_resistance_25",e.cell_resistance_26="cell_resistance_26",e.cell_resistance_27="cell_resistance_27",e.cell_resistance_28="cell_resistance_28",e.cell_resistance_29="cell_resistance_29",e.cell_resistance_30="cell_resistance_30",e.cell_resistance_31="cell_resistance_31",e.cell_resistance_32="cell_resistance_32",e.cell_resistance_33="cell_resistance_33",e.cell_resistance_34="cell_resistance_34",e.cell_resistance_35="cell_resistance_35",e.cell_resistance_36="cell_resistance_36",e.cell_resistance_37="cell_resistance_37",e.cell_resistance_38="cell_resistance_38",e.cell_resistance_39="cell_resistance_39",e.cell_resistance_40="cell_resistance_40",e.cell_resistance_41="cell_resistance_41",e.cell_resistance_42="cell_resistance_42",e.cell_resistance_43="cell_resistance_43",e.cell_resistance_44="cell_resistance_44",e.cell_resistance_45="cell_resistance_45",e.cell_resistance_46="cell_resistance_46",e.cell_resistance_47="cell_resistance_47",e.cell_resistance_48="cell_resistance_48",e.cell_resistance_49="cell_resistance_49",e.cell_resistance_50="cell_resistance_50",e.cell_resistance_51="cell_resistance_51",e.cell_resistance_52="cell_resistance_52",e.cell_resistance_53="cell_resistance_53",e.cell_resistance_54="cell_resistance_54",e.cell_resistance_55="cell_resistance_55",e.cell_resistance_56="cell_resistance_56",e.cell_resistance_57="cell_resistance_57",e.cell_resistance_58="cell_resistance_58",e.cell_resistance_59="cell_resistance_59",e.cell_resistance_60="cell_resistance_60",e.cell_resistance_61="cell_resistance_61",e.cell_resistance_62="cell_resistance_62",e.cell_resistance_63="cell_resistance_63",e.cell_resistance_64="cell_resistance_64",e.cell_resistance_65="cell_resistance_65",e.cell_resistance_66="cell_resistance_66",e.cell_resistance_67="cell_resistance_67",e.cell_resistance_68="cell_resistance_68",e.cell_resistance_69="cell_resistance_69",e.cell_resistance_70="cell_resistance_70",e.cell_resistance_71="cell_resistance_71",e.cell_resistance_72="cell_resistance_72",e.cell_resistance_73="cell_resistance_73",e.cell_resistance_74="cell_resistance_74",e.cell_resistance_75="cell_resistance_75",e.cell_resistance_76="cell_resistance_76",e.cell_resistance_77="cell_resistance_77",e.cell_resistance_78="cell_resistance_78",e.cell_resistance_79="cell_resistance_79",e.cell_resistance_80="cell_resistance_80",e.cell_resistance_81="cell_resistance_81",e.cell_resistance_82="cell_resistance_82",e.cell_resistance_83="cell_resistance_83",e.cell_resistance_84="cell_resistance_84",e.cell_resistance_85="cell_resistance_85",e.cell_resistance_86="cell_resistance_86",e.cell_resistance_87="cell_resistance_87",e.cell_resistance_88="cell_resistance_88",e.cell_resistance_89="cell_resistance_89",e.cell_resistance_90="cell_resistance_90",e.cell_resistance_91="cell_resistance_91",e.cell_resistance_92="cell_resistance_92",e.cell_resistance_93="cell_resistance_93",e.cell_resistance_94="cell_resistance_94",e.cell_resistance_95="cell_resistance_95",e.cell_resistance_96="cell_resistance_96",e.temperature_sensor_1="temperature_sensor_1",e.temperature_sensor_2="temperature_sensor_2",e.temperature_sensor_3="temperature_sensor_3",e.temperature_sensor_4="temperature_sensor_4"}(ue||(ue={}));var ve={title:"JK BMS Card",description:"A customizable Home Assistant card to emulate the JK BMS screen",version:"Version"},me={language:"Language",title:"Title",prefix:"Prefix of the sensors",cellCount:"Number of cells",cellColumns:"Number of columns",cellLayout:"Cell Layout",layout:"Layout",deltaVoltageUnit:"Delta Voltage Unit",cellColorMode:"Cell Color Mode",cellOrientation:"Cell Orientation",minCellVoltage:"Min Cell Voltage (V)",maxCellVoltage:"Max Cell Voltage (V)",manualAssignment:"Manual entity assignment",tempSensorsCount:"Number of temperature sensors available",hasHeater:"Heater option is used"},fe={charge:"Charge",discharge:"Discharge",balance:"Balance",heater:"Heater"},be={power:"Power:",capacity:"Capacity:",cycleCapacity:"Cycle Cap:",averageCellV:"Avg Cell V:",balanceCurrent:"Balance Cur:",stateOfCharge:"SOC:",remainingAmps:"Remaining:",cycles:"Cycles:",delta:"Delta Voltage:",mosfetTemp:"MOS Temp:",temperature_sensor_1:"Temp. 1:",temperature_sensor_2:"Temp. 2:",temperature_sensor_3:"Temp. 3:",temperature_sensor_4:"Temp. 4:"},ye={capacity:"Capacity",time:"Time","grid-solar":"Grid/Solar",load:"Load","total-voltage":"Total Voltage",current:"Current",mosfetTemp:"MOS Temp",delta:"Delta",temperature_sensor:"Temp",balancing:"Balancing",volt:"V",charge:"Charge",discharge:"Discharge",remaining:"Remaining"},xe={common:ve,config:me,switches:fe,stats:be,html_texts:ye},Ce={title:"Карта JK BMS",description:"Налаштовувана картка Home Assistant для емуляції екрану JK BMS",version:"Версія"},Se={language:"Мова",title:"Заголовок",prefix:"Префікс сенсорів",cellCount:"Кількість комірок",cellColumns:"Кількість стовпців",cellLayout:"Розташування комірок",layout:"Макет",deltaVoltageUnit:"Одиниця дельта напруги",cellColorMode:"Режим кольору комірок",cellOrientation:"Орієнтація комірок",minCellVoltage:"Мін. напруга комірки (В)",maxCellVoltage:"Макс. напруга комірки (В)",manualAssignment:"Ручне призначення сутності",tempSensorsCount:"Кількість доступних датчиків температури",hasHeater:"Використовується опція підігріву"},we={charge:"Заряд",discharge:"Розряд",balance:"Баланс",heater:"Підігрів"},$e={power:"Потужність:",capacity:"Ємність:",cycleCapacity:"Ємність циклу:",averageCellV:"Середня напруга комірки:",balanceCurrent:"Струм балансування:",stateOfCharge:"Заряд:",remainingAmps:"Залишок (Аг):",cycles:"Цикли:",delta:"Дельта напруги:",mosfetTemp:"Температура MOS:",temperature_sensor_1:"Темп. 1:",temperature_sensor_2:"Темп. 2:",temperature_sensor_3:"Темп. 3:",temperature_sensor_4:"Темп. 4:"},ke={capacity:"Ємність",time:"Час","grid-solar":"Мережа/Сонячна",load:"Навантаження","total-voltage":"Загальна напруга",current:"Струм",mosfetTemp:"Темп. MOS",delta:"Дельта",temperature_sensor:"Темп",balancing:"Балансування",volt:"В",charge:"Заряд",discharge:"Розряд",remaining:"Залишок"},Ae={common:Ce,config:Se,switches:we,stats:$e,html_texts:ke},Ee={title:"Card JK BMS",description:"Un card Home Assistant personalizabil care imită ecranul JK BMS",version:"Versiune"},Me={language:"Limba",title:"Titlu",prefix:"Prefixul senzorilor",cellCount:"Număr de celule",cellColumns:"Număr de coloane",cellLayout:"Aranjament celule",layout:"Aranjament general",deltaVoltageUnit:"Unitate diferență tensiune",cellColorMode:"Mod colorare celule",cellOrientation:"Orientare celule",minCellVoltage:"Tensiune minimă celulă (V)",maxCellVoltage:"Tensiune maximă celulă (V)",manualAssignment:"Atribuire manuală entități",tempSensorsCount:"Număr senzori temperatură disponibili",hasHeater:"Opțiunea încălzitor este utilizată"},Oe={charge:"Încărcare",discharge:"Descărcare",balance:"Echilibrare",heater:"Încălzitor"},Te={power:"Putere:",capacity:"Capacitate:",cycleCapacity:"Cap. totală:",averageCellV:"Medie celule:",balanceCurrent:"Curent echilibrare:",stateOfCharge:"SOC:",remainingAmps:"Rămase:",cycles:"Cicluri:",delta:"Diferență tensiune:",mosfetTemp:"Temp. MOSFET:",temperature_sensor_1:"Temp. 1:",temperature_sensor_2:"Temp. 2:",temperature_sensor_3:"Temp. 3:",temperature_sensor_4:"Temp. 4:"},Ve={capacity:"Capacitate",time:"Timp","grid-solar":"Rețea/Solar",load:"Consum","total-voltage":"Tensiune totală",current:"Curent",mosfetTemp:"Temp. MOSFET",delta:"Diferență",temperature_sensor:"Temp",balancing:"Echilibrare",volt:"V",charge:"Încărcare",discharge:"Descărcare",remaining:"Rămas"},ze={common:Ee,config:Me,switches:Oe,stats:Te,html_texts:Ve},De={title:"JK BMS Karte",description:"Eine anpassbare Home Assistant Karte, die den JK BMS Bildschirm nachbildet",version:"Version"},Ne={language:"Sprache",title:"Titel",prefix:"Präfix der Sensoren",cellCount:"Anzahl der Zellen",cellColumns:"Anzahl der Spalten",cellLayout:"Zellen-Layout",layout:"Layout",deltaVoltageUnit:"Einheit der Spannungsdifferenz",cellColorMode:"Zellen-Farbmodus",cellOrientation:"Zellen-Ausrichtung",minCellVoltage:"Min. Zellspannung (V)",maxCellVoltage:"Max. Zellspannung (V)",manualAssignment:"Manuelle Entitäten-Zuweisung",tempSensorsCount:"Anzahl verfügbarer Temperatursensoren",hasHeater:"Heizungs-Option wird verwendet"},Pe={charge:"Laden",discharge:"Entladen",balance:"Balancing",heater:"Heizung"},Ie={power:"Leistung:",capacity:"Kapazität:",cycleCapacity:"Zyklus-Kap.:",averageCellV:"Durchschn. V:",balanceCurrent:"Balancing-Strom:",stateOfCharge:"SOC:",remainingAmps:"Verbleibend:",cycles:"Zyklen:",delta:"Spannungsdiff.:",mosfetTemp:"MOS Temp:",temperature_sensor_1:"Temp. 1:",temperature_sensor_2:"Temp. 2:",temperature_sensor_3:"Temp. 3:",temperature_sensor_4:"Temp. 4:"},Le={capacity:"Kapazität",time:"Zeit","grid-solar":"Netz/Solar",load:"Last","total-voltage":"Gesamtspannung",current:"Stromstärke",mosfetTemp:"MOS Temp",delta:"Differenz",temperature_sensor:"Temp",balancing:"Balancing",volt:"V",charge:"Laden",discharge:"Entladen",remaining:"Restzeit"},Ue={common:De,config:Ne,switches:Pe,stats:Ie,html_texts:Le};const He={hass:null},Fe={en:Object.freeze({__proto__:null,common:ve,config:me,default:xe,html_texts:ye,stats:be,switches:fe}),uk:Object.freeze({__proto__:null,common:Ce,config:Se,default:Ae,html_texts:ke,stats:$e,switches:we}),ro:Object.freeze({__proto__:null,common:Ee,config:Me,default:ze,html_texts:Ve,stats:Te,switches:Oe}),de:Object.freeze({__proto__:null,common:De,config:Ne,default:Ue,html_texts:Le,stats:Ie,switches:Pe})};function Re(e,t="",n=""){try{let l=He.cardConfig?.language,s=localStorage.getItem("selectedLanguage");null===s&&(s=localStorage.getItem("assist_debug_language")),null===s&&(s=localStorage.getItem("editor-language")),null===s&&(s="en"),s=s.replace(/['"]+/g,"").replace("-","_");const a=l||He.hass?.selectedLanguage||He.hass?.locale?.language||He.hass?.language||s;let i;try{i=e?.split(".").reduce(((e,t)=>e[t]),Fe[a])}catch(t){i=e?.split(".")?.reduce(((e,t)=>e[t]),Fe.en)}return void 0===i&&(i=e?.split(".")?.reduce(((e,t)=>e[t]),Fe.en)),""!==t&&""!==n&&(i=i.replace(t,n)),i}catch(t){return e}}const je=(e,t)=>{const n=e?.entities[t]?.toString()?.trim();return n&&n.length>1?n:t?.toString()},Be=(e,t,n,l="sensor")=>{if(!e)return;e.stopPropagation();const s=je(t,n),a=s.includes("sensor.")||s.includes("switch.")||s.includes("number.")||s.includes("binary_sensor.")?s:l+"."+t?.prefix+"_"+s;let i=new CustomEvent("hass-more-info",{detail:{entityId:a},composed:!0});e.target.dispatchEvent(i)},Ke=(e,t,n,l=2,s="",a="sensor")=>{const i=je(t,n);if(!i)return s;const r=i.includes("sensor.")||i.includes("switch.")||i.includes("number.")||i.includes("binary_sensor.")?i:"".concat(a,".").concat(t.prefix,"_").concat(i),c=e?.states[r],o=c?.state,_=Number(o);return isNaN(_)?o??s:_.toFixed(l)},We=(e="V",t)=>{const n="string"==typeof t?parseFloat(t):t;if(isNaN(n))return"-";if("mV"===e)return"".concat((1e3*n).toFixed(0)," mV");const l=n.toLocaleString("en-US",{minimumFractionDigits:0,maximumFractionDigits:3});return"".concat(l," V")};var Je,Ze,qe,Ge,Qe,Xe,Ye,et,tt,nt,lt,st,at,it;let rt=class extends re{constructor(){super(...arguments),this.minCellId="",this.maxCellId="",this.maxDeltaV=0,this.shouldBalance=!1}_navigate(e,t,n="sensor"){Be(e,this.config,t,n)}getState(e,t=2,n="",l="sensor"){return Ke(this.hass,this.config,e,t,n,l)}_renderSwitch(t,n){const l=this.getState(t,0,"","switch"),s="on"===l?"status-on":"status-off";return j(Je||(Je=e(['\n
',"\n
\n "])),(e=>this._navigate(e,t,"switch")),Re("switches."+n),s,l.toUpperCase())}configOrEnum(e){return je(this.config,e)}render(){if(He.hass=this.hass,!this.hass||!this.config)return j(Ze||(Ze=e([""])));const t=this.config.title||"Bat 1";this.maxDeltaV=parseFloat(this.getState(ue.delta_cell_voltage,3,"0"));const n=parseFloat(this.getState(ue.balancing_current,2,"0")),l=parseFloat(this.getState(ue.power,2,"0")),s=Number(this.getState(ue.balance_trigger_voltage,2,"","number"));this.shouldBalance=this.maxDeltaV>=s;const a=l>0?"power-positive":l<0?"power-negative":"power-even",i=n>0?"balance-positive":n<0?"balance-negative":"balance-even",r=this.shouldBalance?"delta-needs-balancing":"delta-ok",c=this.getState(ue.total_runtime_formatted),o=c&&"unknown"!=c?j(qe||(qe=e([" | ",': ',""])),Re("html_texts.time"),c.toUpperCase()):"";return j(Ge||(Ge=e(['\n \n
\n
\n
\n\n
\n ',"\n ","\n ","\n ","\n
\n \n ",'\n\n
\n
\n
\n '," ","\n
\n ",' '," A\n ",'\n
\n\n
\n
\n '," A\n
\n ",' \n
\n\n \n \n \n\n
\n ',"\n
\n \n "])),(e=>this._navigate(e,ue.total_runtime_formatted)),t,o,"1"==this.config.hasHeater?"4":"3",this._renderSwitch(ue.charging,"charge"),this._renderSwitch(ue.discharging,"discharge"),this._renderSwitch(ue.balancer,"balance"),"1"==this.config.hasHeater?this._renderSwitch(ue.heater,"heater"):"",this._renderError(),(e=>this._navigate(e,ue.total_voltage)),this.getState(ue.total_voltage),Re("html_texts.volt"),Re("stats.power"),a,(e=>this._navigate(e,ue.power)),this.getState(ue.power),Re("stats.capacity"),(e=>this._navigate(e,ue.total_battery_capacity_setting)),this.getState(ue.total_battery_capacity_setting),Re("stats.cycleCapacity"),(e=>this._navigate(e,ue.total_charging_cycle_capacity)),this.getState(ue.total_charging_cycle_capacity),Re("stats.averageCellV"),(e=>this._navigate(e,ue.average_cell_voltage)),this.getState(ue.average_cell_voltage,3),Re("html_texts.volt"),Re("stats.balanceCurrent"),i,n.toFixed(1),this._renderTemps(1),(e=>this._navigate(e,ue.current)),this.getState(ue.current),Re("stats.stateOfCharge"),(e=>this._navigate(e,ue.state_of_charge)),this.getState(ue.state_of_charge),Re("stats.remainingAmps"),(e=>this._navigate(e,ue.capacity_remaining)),this.getState(ue.capacity_remaining),Re("stats.cycles"),(e=>this._navigate(e,ue.charging_cycles)),this.getState(ue.charging_cycles),Re("stats.delta"),r,(e=>this._navigate(e,ue.delta_cell_voltage)),We(this.config.deltaVoltageUnit,this.maxDeltaV),Re("stats.mosfetTemp"),(e=>this._navigate(e,ue.power_tube_temperature)),this.getState(ue.power_tube_temperature),this._renderTemps(2),this.config.cellColumns??2,this._renderCells("bankMode"==this.config.cellLayout))}updated(){requestAnimationFrame((()=>this._updateFlowLine()))}connectedCallback(){super.connectedCallback(),window.addEventListener("resize",this._updateFlowLine.bind(this))}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("resize",this._updateFlowLine.bind(this))}_renderError(){const t=this.getState(ue.errors,0);return t.trim().length<=1||"0"==t?j(Qe||(Qe=e([""]))):j(Xe||(Xe=e(['',""])),t)}_renderTemps(t){const n=[],l=this.config?.tempSensorsCount??0;for(let s=t;s<=l;s+=2)n.push(j(Ye||(Ye=e(["\n
",' \n \n ',"\n ","\n \n ","\n
\n "])),t,(e=>this._navigate(e,ue["cell_voltage_".concat(t)])),t.toString().padStart(2,"0"),i?j(st||(st=e([''," ",""])),i,n,Re("html_texts.volt")):j(at||(at=e([""," ",""])),n,Re("html_texts.volt")),r)}_updateFlowLine(){const e=parseFloat(this.getState(ue.balancing_current,3,"0")),t=this.renderRoot.querySelector("#cell-".concat(this.minCellId)),n=this.renderRoot.querySelector("#cell-".concat(this.maxCellId)),l=this.renderRoot.querySelector("#flow-path");if(!l)return;if(!this.shouldBalance&&0===e||!t||!n)return l.setAttribute("d",""),void(l.style.display="none");l.style.display="inline";const s=(this.renderRoot instanceof ShadowRoot?this.renderRoot.host:this).getBoundingClientRect(),a=t.getBoundingClientRect(),i=e=>{const t=e.left+e.width/2this.fetchHistory()),6e4)}_navigate(e,t,n="sensor"){Be(e,this.config,t,n)}updated(e){e.has("hass")&&this._updateRealtimeHistory()}_updateRealtimeHistory(){if(!this.hass||!this.config)return;const e=[ue.total_voltage,ue.current,ue.power_tube_temperature,ue.delta_cell_voltage],t=Date.now()-36e5;e.forEach((e=>{const n=this._resolveEntityId(e);if(!n)return;const l=this.hass.states[n];if(!l)return;const s=parseFloat(l.state),a=new Date(l.last_updated).getTime();if(isNaN(s))return;const i=this.historyData[n]||[],r=i.length>0?i[i.length-1]:null;if(!r||a>r.time){const e=[...i,{state:s,time:a}];for(;e.length>0&&e[0].timee)),t=[...new Set(e)];if(0===t.length)return;const n=new Date,l=new Date(n.getTime()-36e5);try{const e=await this.hass.callWS({type:"history/history_during_period",start_time:l.toISOString(),end_time:n.toISOString(),entity_ids:t,minimal_response:!0,no_attributes:!0});if(e){const t={};Object.keys(e).forEach((n=>{const l=e[n];Array.isArray(l)&&(t[n]=l.map((e=>({state:parseFloat(e.s||e.state),time:1e3*(e.lu||new Date(e.last_updated).getTime()/1e3)}))).filter((e=>!isNaN(e.state))))})),this.historyData={...this.historyData,...t}}}catch(e){console.warn("JK BMS Card: Failed to fetch history via WS",e)}}configOrEnum(e){return je(this.config,e)}_resolveEntityId(e){const t=this.configOrEnum(e);if(t)return t.includes(".")?t:"sensor.".concat(this.config.prefix,"_").concat(t)}_renderSparkline(t,n){const l=this._resolveEntityId(t);if(!l||!this.historyData[l]||this.historyData[l].length<2)return j(ct||(ct=e(['\n
'])));const s=this.historyData[l];let a=1/0,i=-1/0;s.forEach((e=>{const t=Number(Number(e.state).toFixed(3));ti&&(i=t)}));let r=i-a;if(r<1){const e=(a+i)/2;a=e-.5,i=e+.5}else a-=.05*r,i+=.05*r;const c=s[0].time,o=s[s.length-1].time-c;if(o<=0)return j(ot||(ot=e([""])));let _="";return s.forEach(((e,t)=>{const n=(e.time-c)/o*100,l=100-(e.state-a)/(i-a)*100;_+="".concat(0===t?"M":"L"," ").concat(n.toFixed(1),",").concat(l.toFixed(1)," ")})),j(_t||(_t=e(['\n
\n \n \n \n \n
\n '])),_,n,_,n)}getState(e,t=2,n="",l="sensor"){return Ke(this.hass,this.config,e,t,n,l)}render(){if(He.hass=this.hass,!this.hass||!this.config)return j(dt||(dt=e([""])));const t=this.getState(ue.hardware_version),n=this.getState(ue.software_version),l=this.config.title||j(ht||(ht=e(["Bat 1 - ",": "," Ah
\n HW: "," | SW: ",""])),Re("html_texts.capacity"),this.getState(ue.total_battery_capacity_setting),t,n),s=this.getState(ue.total_runtime_formatted),a=s&&"unknown"!=s?j(gt||(gt=e(["",": ",""])),Re("html_texts.time"),s.toUpperCase()):"",i=parseFloat(this.getState(ue.current)),r=this.getState(ue.charging_power),c=this.getState(ue.discharging_power),o=i>0,_=i<0,d="on"===this.getState(ue.charging,0,"","switch"),h="on"===this.getState(ue.discharging,0,"","switch"),g="on"===this.getState(ue.balancing,0,"","binary_sensor");parseFloat(this.getState(ue.balancing_current));const p=this.getState(ue.state_of_charge);this.getState(ue.total_battery_capacity_setting);const u=this.getState(ue.total_voltage),v=this.getState(ue.power_tube_temperature),m=this.getState(ue.total_charging_cycle_capacity),f=parseFloat(this.getState(ue.charging_cycles)).toFixed(0).toString();return this.tempSensorsCount=this.config.tempSensorsCount,this.calculateDynamicMinMax(),j(pt||(pt=e(['\n \n
\n
\n\n
\n \x3c!-- Solar/Grid Node --\x3e\n
\n
\n \n
\n
','
\n
\n ',': ','\n
\n
\n
\n
\n
\n\n \x3c!-- Reactor (SOC) --\x3e\n
\n
\n
SoC:
\n
',"%
\n ",'\n
\n
\n\n \x3c!-- Load Node --\x3e\n
\n
\n \n
\n
','
\n
\n ',': ','\n
\n \n
\n
\n
\n
\n\n \x3c!-- SVG Flow Lines --\x3e\n \n \x3c!-- Left path (Charge) - Rough coordinates for now, will refine --\x3e\n \n \x3c!-- Right path (Discharge) --\x3e\n \n\n \n \n \n \n \n \n
\n\n \x3c!-- Stats Panels --\x3e\n
\n
\n
\n ','\n
',':
\n
\n
\n\n
\n ','\n
',':
\n
\n
\n
\n\n
\n
\n ','\n
',':
\n
\n
\n\n
\n ','\n
'," ",':
\n
\n
\n
','
\n
\n
\n \n
\n
\n
\n
\n
\n \n \x3c!-- Add number of cycles --\x3e\n
\n
\n
','
\n
\n
\n \n
\n
\n
\n
\n
\n
\n\n \x3c!-- Cells --\x3e\n
\n ',"\n
\n\n \n "])),(e=>this._navigate(e,ue.software_version)),l,(e=>this._navigate(e,ue.total_runtime_formatted)),a,(e=>this._navigate(e,ue.charging,"switch")),o?"var(--solar-color)":"#444",Re("html_texts.grid-solar"),Re("html_texts.charge"),d?"status-on":"status-off",d?"ON":"OFF",(e=>this._navigate(e,ue.charging_power)),r,g?"reactor-ring-balancing":"reactor-ring-base",(e=>this._navigate(e,ue.state_of_charge)),p,g?j(ut||(ut=e(['
\n ','\n
\n '," ",'\n
\n
\n ',"\n ",'\n
\n\n \x3c!-- Right column (2 and 4) --\x3e\n
\n ',"\n ","\n
\n "])),t?a(ue.temperature_sensor_1,1):"",l?a(ue.temperature_sensor_3,3):"",n?a(ue.temperature_sensor_2,2):"",s?a(ue.temperature_sensor_4,4):"")}_renderCells(){const e=[],t="bankMode"===this.config.cellLayout,n=this.config.cellColumns||2,l=this.config.cellCount||16,s=Math.floor(l/n),a=t?Math.ceil(l/n):l,i=l%n;for(let r=1;r<=a;r++){const c=[];if(t)if(i&&r===a)c.push(l);else for(let e=0;e100&&(d=100));const h=parseFloat(s),g=!isNaN(h)&&h>0,p=this.config.cellColorMode||"progress";let u="";if("gradient"===p){const e=[{r:180,g:60,b:60},{r:180,g:180,b:50},{r:60,g:180,b:60},{r:60,g:120,b:200},{r:90,g:60,b:200}];let t=Math.floor(d/25);t>=4&&(t=3);const n=d%25/25,l=e[t],s=e[t+1],a=Math.round(l.r+(s.r-l.r)*n),i=Math.round(l.g+(s.g-l.g)*n),r=Math.round(l.b+(s.b-l.b)*n);u="background: rgba(".concat(a,", ").concat(i,", ").concat(r,", 0.85);")}const v="gradient"===p?"background: rgba(0, 0, 0, 0.25); padding: 1px 3px; border-radius: 3px;":"",m=this.config.cellOrientation||"horizontal",f="vertical"===m?"cell-item cell-item-vertical":"cell-item";t.push(j(yt||(yt=e(['\n
\n ',"\n ","\n
\n "])),f,u,"progress"===p?j(xt||(xt=e(['\n
'])),d):"","vertical"===m?j(Ct||(Ct=e(['\n ','\n \n ','\n '," ","\n \n ","\n "])),(e=>this._navigate(e,ue["cell_voltage_".concat(n)])),String(n).padStart(2,"0"),r,v,l,Re("html_texts.volt"),g?j($t||($t=e(['\n .icon-circle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n justify-self: start;\n }\n\n .icon-stats > :nth-child(2) {\n justify-self: end;\n padding-right: 5px;\n }\n\n .icon-stats > .stat-value {\n font-size: 1.2em;\n font-weight: bold;\n }\n\n /* Cell Grid */\n\n .cell-grid {\n display: grid;\n gap: 6px;\n }\n\n .grid-1 {\n grid-template-columns: 1fr;\n }\n\n .grid-2 {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .grid-3 {\n grid-template-columns: repeat(3, 1fr);\n }\n\n .grid-4 {\n grid-template-columns: repeat(4, 1fr);\n }\n\n .grid-5 {\n grid-template-columns: repeat(5, 1fr);\n }\n\n .grid-6 {\n grid-template-columns: repeat(6, 1fr);\n }\n\n .grid-7 {\n grid-template-columns: repeat(7, 1fr);\n }\n\n .grid-8 {\n grid-template-columns: repeat(8, 1fr);\n }\n\n .cell-item {\n background: var(--secondary-background-color, #2a2a2a);\n border-radius: 8px;\n padding: 4px 6px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 4px;\n font-size: 0.85em;\n position: relative;\n overflow: hidden;\n z-index: 0;\n }\n\n .cell-item-vertical {\n flex-direction: column;\n justify-content: center;\n padding: 6px 4px;\n gap: 3px;\n }\n\n .cell-item-vertical .cell-id {\n margin-right: 0;\n margin-bottom: 2px;\n }\n\n .cell-item-bg {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: rgba(48, 144, 199, 0.2);\n z-index: -1;\n transition: width 0.5s ease-out;\n }\n\n .cell-id {\n display: inline-block;\n padding: 0.1rem 0.25rem;\n background-color: #195569;\n color: #e4f3f8;\n border-radius: 999px;\n font-weight: 500;\n font-size: 1em;\n min-width: 1.4rem;\n text-align: center;\n margin-right: 3px;\n flex-shrink: 0;\n }\n\n .cell-volts {\n color: var(--primary-text-color);\n font-family: monospace;\n font-size: 1.2em;\n }\n\n .cell-res {\n color: var(--secondary-text-color);\n font-size: 1em;\n font-family: monospace;\n }\n\n .cell-low {\n color: #FFA500;\n }\n\n /* Example warning color */\n\n .cell-high {\n color: var(--discharge-color);\n }\n\n .status-on {\n color: var(--accent-color);\n font-weight: bold;\n }\n\n .status-off {\n color: var(--disabled-text-color, #666);\n font-weight: bold;\n }\n\n .clickable {\n cursor: pointer;\n }\n\n /* SVG Overlay for Lines */\n\n .flow-svg {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n z-index: 1;\n }\n\n .flow-svg path {\n fill: none;\n stroke-width: 2;\n stroke-dasharray: 8;\n }\n\n .path-charge {\n stroke: var(--solar-color);\n animation: flow 1s linear infinite;\n }\n\n .path-discharge {\n stroke: var(--discharge-color);\n animation: flow 1s linear infinite;\n }\n\n .path-inactive {\n stroke: #444;\n stroke-dasharray: 0;\n animation: none;\n }\n\n @keyframes flow {\n from {\n stroke-dashoffset: 16;\n }\n to {\n stroke-dashoffset: 0;\n }\n }\n\n /* Sparkline CSS */\n /* Sparkline CSS */\n\n .metric-group {\n position: relative;\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 8px 0;\n z-index: 1;\n }\n\n .sparkline-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n overflow: hidden;\n opacity: 0.3;\n z-index: -1;\n pointer-events: none;\n }\n\n .sparkline-svg {\n width: 100%;\n height: 100%;\n }\n\n .spark-line {\n fill: none;\n stroke-width: 2;\n vector-effect: non-scaling-stroke;\n }\n\n .spark-area {\n fill-opacity: 0.2;\n stroke: none;\n }\n "]))),t([he()],At.prototype,"hass",void 0),t([he()],At.prototype,"config",void 0),t([he()],At.prototype,"historyData",void 0),At=t([oe("jk-bms-core-reactor-layout")],At);const Vt="1.2.10";console.groupCollapsed("%c🔋 JK-BMS Battery Card%c ready! 🚀 (v".concat(Vt,")"),"background: linear-gradient(to right, #41cd52, #3090c7); color: white; font-weight: bold; padding: 2px 8px; border-radius: 6px;","background: none; color: #AAA; font-style: italic;"),console.log("%c📖 Docs:","color: #41cd52; font-weight: bold;","https://github.com/Pho3niX90/jk-bms-card"),console.groupEnd();let zt=Tt=class extends re{setConfig(e){this._config=Tt.getStubConfig(),this._config={...this._config,...e},He.cardConfig=this._config}static getStubConfig(){return{language:"",title:Re("title"),prefix:"jk_bms",cellCount:16,cellColumns:2,cellLayout:"bankMode",tempSensorsCount:0,hasHeater:0,entities:Object.keys(ue).reduce(((e,t)=>(e[t]="",e)),{})}}static async getConfigElement(){return await Promise.resolve().then((function(){return It})),document.createElement(ge)}getCardSize(){return 3}render(){return He.hass=this.hass,this.hass&&this._config?"core-reactor"===this._config.layout?j(Mt||(Mt=e([""])),this.hass,this._config):j(Ot||(Ot=e(["\n \n "])),this.hass,this._config):j(Et||(Et=e([""])))}};var Dt,Nt;t([he()],zt.prototype,"hass",void 0),t([he()],zt.prototype,"_config",void 0),zt=Tt=t([oe(pe)],zt),window.customCards.push({type:pe,name:"JK BMS Card",preview:!0,description:Re("common.description"),configurable:!0});let Pt=class extends re{constructor(){super(...arguments),this._computeLabelCallback=e=>Re("config.".concat(e.name))??e.name}setConfig(e){this._config={...this._config,...e}}render(){if(!this._config||!this.hass)return j(Dt||(Dt=e([""])));const t=Object.values(ue).map((e=>({name:e,selector:{entity:{}}})));return j(Nt||(Nt=e(["\n\t\t\t\n\t\t"])),this.hass,this._config,this._computeLabelCallback.bind(this),[{type:"grid",title:Re("config.title"),schema:[{type:"grid",schema:[{name:"title",selector:{text:{}}}]}]},{type:"grid",title:Re("config.language"),schema:[{type:"grid",schema:[{name:"language",selector:{select:{options:[{label:"Auto (follow HA)",value:""},{label:"English",value:"en"},{label:"Română",value:"ro"},{label:"Українська",value:"uk"},{label:"German",value:"de"}],mode:"dropdown"}}}]}]},{type:"grid",title:Re("config.prefix"),schema:[{type:"grid",schema:[{name:"prefix",selector:{text:{}}}]}]},{type:"grid",title:Re("config.layout"),schema:[{type:"grid",schema:[{name:"layout",selector:{select:{options:[{label:"Default",value:"default"},{label:"Core Reactor",value:"core-reactor"}]}}},{name:"deltaVoltageUnit",selector:{select:{options:[{label:"Volts (V)",value:"V"},{label:"Millivolts (mV)",value:"mV"}]}}},{name:"minCellVoltage",selector:{number:{min:2,max:4,step:.01,mode:"box"}}}]},{type:"grid",column_min_width:"200px",schema:[{name:"cellColorMode",selector:{select:{options:[{label:"Progress Bar",value:"progress"},{label:"Voltage Gradient",value:"gradient"}]}}},{name:"cellOrientation",selector:{select:{options:[{label:"Vertical",value:"vertical"},{label:"Horizontal",value:"horizontal"}]}}},{name:"maxCellVoltage",selector:{number:{min:2,max:4,step:.01,mode:"box"}}}]}]},{type:"grid",title:Re("config.cellCount"),schema:[{type:"grid",schema:[{name:"cellCount",selector:{number:{min:2,max:48,step:2}}},{name:"cellColumns",selector:{number:{min:1,max:8,step:1}}},{name:"cellLayout",selector:{select:{options:[{label:"Incremental",value:"incremental"},{label:"Bank Mode",value:"bankMode"}]}}}]}]},{type:"grid",title:Re("config.tempSensorsCount"),schema:[{type:"grid",schema:[{name:"tempSensorsCount",selector:{number:{min:0,max:4,step:2}}}]}]},{type:"grid",title:Re("config.hasHeater"),schema:[{type:"grid",schema:[{name:"hasHeater",selector:{select:{multiple:!1,mode:"list",options:[{label:"Yes",value:"1"},{label:"No",value:"0"}]}}}]}]},{type:"expandable",title:Re("config.manualAssignment"),schema:[{name:"entities",type:"grid",schema:t}]}],this._valueChanged.bind(this))}_valueChanged(e){!function(e,t,n,l){l=l||{},n=null==n?{}:n;const s=new Event(t,{bubbles:void 0===l.bubbles||l.bubbles,cancelable:Boolean(l.cancelable),composed:void 0===l.composed||l.composed});s.detail=n,e.dispatchEvent(s)}(this,"config-changed",{config:e.detail.value})}};t([he()],Pt.prototype,"hass",void 0),t([he()],Pt.prototype,"_config",void 0),t([he()],Pt.prototype,"lovelace",void 0),Pt=t([oe(ge)],Pt);var It=Object.freeze({__proto__:null,get JkBmsCardEditor(){return Pt}});export{Vt as CARD_VERSION,zt as JkBmsCard}; diff --git a/hass/settings/www/community/jk-bms-card/jk-bms-card.js.gz b/hass/settings/www/community/jk-bms-card/jk-bms-card.js.gz new file mode 100644 index 0000000..f786afa Binary files /dev/null and b/hass/settings/www/community/jk-bms-card/jk-bms-card.js.gz differ diff --git a/hass/settings/www/community/lovelace-auto-entities/auto-entities.js b/hass/settings/www/community/lovelace-auto-entities/auto-entities.js new file mode 100644 index 0000000..84a74b4 --- /dev/null +++ b/hass/settings/www/community/lovelace-auto-entities/auto-entities.js @@ -0,0 +1,172 @@ +function t(t,e,i,n){var s,o=arguments.length,r=o<3?e:null===n?n=Object.getOwnPropertyDescriptor(e,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)r=Reflect.decorate(t,e,i,n);else for(var a=t.length-1;a>=0;a--)(s=t[a])&&(r=(o<3?s(r):o>3?s(e,i,r):s(e,i))||r);return o>3&&r&&Object.defineProperty(e,i,r),r}const e=window,i=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,n=Symbol(),s=new WeakMap;let o=class{constructor(t,e,i){if(this._$cssResult$=!0,i!==n)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(i&&void 0===t){const i=void 0!==e&&1===e.length;i&&(t=s.get(e)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),i&&s.set(e,t))}return t}toString(){return this.cssText}};const r=(t,...e)=>{const i=1===t.length?t[0]:e.reduce(((e,i,n)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[n+1]),t[0]);return new o(i,t,n)},a=i?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new o("string"==typeof t?t:t+"",void 0,n))(e)})(t):t;var l;const d=window,c=d.trustedTypes,h=c?c.emptyScript:"",u=d.reactiveElementPolyfillSupport,v={toAttribute(t,e){switch(e){case Boolean:t=t?h:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},f=(t,e)=>e!==t&&(e==e||t==t),p={attribute:!0,type:String,converter:v,reflect:!1,hasChanged:f};let g=class extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this.u()}static addInitializer(t){var e;this.finalize(),(null!==(e=this.h)&&void 0!==e?e:this.h=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((e,i)=>{const n=this._$Ep(i,e);void 0!==n&&(this._$Ev.set(n,i),t.push(n))})),t}static createProperty(t,e=p){if(e.state&&(e.attribute=!1),this.finalize(),this.elementProperties.set(t,e),!e.noAccessor&&!this.prototype.hasOwnProperty(t)){const i="symbol"==typeof t?Symbol():"__"+t,n=this.getPropertyDescriptor(t,i,e);void 0!==n&&Object.defineProperty(this.prototype,t,n)}}static getPropertyDescriptor(t,e,i){return{get(){return this[e]},set(n){const s=this[t];this[e]=n,this.requestUpdate(t,s,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||p}static finalize(){if(this.hasOwnProperty("finalized"))return!1;this.finalized=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,e=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const i of e)this.createProperty(i,t[i])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(a(t))}else void 0!==t&&e.push(a(t));return e}static _$Ep(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}u(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)))}addController(t){var e,i;(null!==(e=this._$ES)&&void 0!==e?e:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(i=t.hostConnected)||void 0===i||i.call(t))}removeController(t){var e;null===(e=this._$ES)||void 0===e||e.splice(this._$ES.indexOf(t)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach(((t,e)=>{this.hasOwnProperty(e)&&(this._$Ei.set(e,this[e]),delete this[e])}))}createRenderRoot(){var t;const n=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return((t,n)=>{i?t.adoptedStyleSheets=n.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):n.forEach((i=>{const n=document.createElement("style"),s=e.litNonce;void 0!==s&&n.setAttribute("nonce",s),n.textContent=i.cssText,t.appendChild(n)}))})(n,this.constructor.elementStyles),n}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostConnected)||void 0===e?void 0:e.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostDisconnected)||void 0===e?void 0:e.call(t)}))}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$EO(t,e,i=p){var n;const s=this.constructor._$Ep(t,i);if(void 0!==s&&!0===i.reflect){const o=(void 0!==(null===(n=i.converter)||void 0===n?void 0:n.toAttribute)?i.converter:v).toAttribute(e,i.type);this._$El=t,null==o?this.removeAttribute(s):this.setAttribute(s,o),this._$El=null}}_$AK(t,e){var i;const n=this.constructor,s=n._$Ev.get(t);if(void 0!==s&&this._$El!==s){const t=n.getPropertyOptions(s),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(i=t.converter)||void 0===i?void 0:i.fromAttribute)?t.converter:v;this._$El=s,this[s]=o.fromAttribute(e,t.type),this._$El=null}}requestUpdate(t,e,i){let n=!0;void 0!==t&&(((i=i||this.constructor.getPropertyOptions(t)).hasChanged||f)(this[t],e)?(this._$AL.has(t)||this._$AL.set(t,e),!0===i.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,i))):n=!1),!this.isUpdatePending&&n&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach(((t,e)=>this[e]=t)),this._$Ei=void 0);let e=!1;const i=this._$AL;try{e=this.shouldUpdate(i),e?(this.willUpdate(i),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostUpdate)||void 0===e?void 0:e.call(t)})),this.update(i)):this._$Ek()}catch(t){throw e=!1,this._$Ek(),t}e&&this._$AE(i)}willUpdate(t){}_$AE(t){var e;null===(e=this._$ES)||void 0===e||e.forEach((t=>{var e;return null===(e=t.hostUpdated)||void 0===e?void 0:e.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return!0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,e)=>this._$EO(e,this[e],t))),this._$EC=void 0),this._$Ek()}updated(t){}firstUpdated(t){}};var _;g.finalized=!0,g.elementProperties=new Map,g.elementStyles=[],g.shadowRootOptions={mode:"open"},null==u||u({ReactiveElement:g}),(null!==(l=d.reactiveElementVersions)&&void 0!==l?l:d.reactiveElementVersions=[]).push("1.5.0");const m=window,y=m.trustedTypes,b=y?y.createPolicy("lit-html",{createHTML:t=>t}):void 0,$=`lit$${(Math.random()+"").slice(9)}$`,w="?"+$,E=`<${w}>`,A=document,C=(t="")=>A.createComment(t),S=t=>null===t||"object"!=typeof t&&"function"!=typeof t,O=Array.isArray,j=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,k=/-->/g,x=/>/g,T=RegExp(">|[ \t\n\f\r](?:([^\\s\"'>=/]+)([ \t\n\f\r]*=[ \t\n\f\r]*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)","g"),U=/'/g,P=/"/g,M=/^(?:script|style|textarea|title)$/i,N=(t=>(e,...i)=>({_$litType$:t,strings:e,values:i}))(1),I=Symbol.for("lit-noChange"),R=Symbol.for("lit-nothing"),H=new WeakMap,D=A.createTreeWalker(A,129,null,!1),F=(t,e)=>{const i=t.length-1,n=[];let s,o=2===e?"":"",r=j;for(let e=0;e"===l[0]?(r=null!=s?s:j,d=-1):void 0===l[1]?d=-2:(d=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?T:'"'===l[3]?P:U):r===P||r===U?r=T:r===k||r===x?r=j:(r=T,s=void 0);const h=r===T&&t[e+1].startsWith("/>")?" ":"";o+=r===j?i+E:d>=0?(n.push(a),i.slice(0,d)+"$lit$"+i.slice(d)+$+h):i+$+(-2===d?(n.push(void 0),e):h)}const a=o+(t[i]||"")+(2===e?"":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==b?b.createHTML(a):a,n]};class L{constructor({strings:t,_$litType$:e},i){let n;this.parts=[];let s=0,o=0;const r=t.length-1,a=this.parts,[l,d]=F(t,e);if(this.el=L.createElement(l,i),D.currentNode=this.el.content,2===e){const t=this.el.content,e=t.firstChild;e.remove(),t.append(...e.childNodes)}for(;null!==(n=D.nextNode())&&a.length0){n.textContent=y?y.emptyScript:"";for(let i=0;iO(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]))(t)?this.k(t):this.g(t)}O(t,e=this._$AB){return this._$AA.parentNode.insertBefore(t,e)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}g(t){this._$AH!==R&&S(this._$AH)?this._$AA.nextSibling.data=t:this.T(A.createTextNode(t)),this._$AH=t}$(t){var e;const{values:i,_$litType$:n}=t,s="number"==typeof n?this._$AC(t):(void 0===n.el&&(n.el=L.createElement(n.h,this.options)),n);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===s)this._$AH.p(i);else{const t=new G(s,this),e=t.v(this.options);t.p(i),this.T(e),this._$AH=t}}_$AC(t){let e=H.get(t.strings);return void 0===e&&H.set(t.strings,e=new L(t)),e}k(t){O(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,n=0;for(const s of t)n===e.length?e.push(i=new z(this.O(C()),this.O(C()),this,this.options)):i=e[n],i._$AI(s),n++;n2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=R}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,e=this,i,n){const s=this.strings;let o=!1;if(void 0===s)t=W(this,t,e,0),o=!S(t)||t!==this._$AH&&t!==I,o&&(this._$AH=t);else{const n=t;let r,a;for(t=s[0],r=0;r{var n,s;const o=null!==(n=null==i?void 0:i.renderBefore)&&void 0!==n?n:e;let r=o._$litPart$;if(void 0===r){const t=null!==(s=null==i?void 0:i.renderBefore)&&void 0!==s?s:null;o._$litPart$=r=new z(e.insertBefore(C(),t),t,void 0,null!=i?i:{})}return r._$AI(t),r})(e,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}render(){return I}}tt.finalized=!0,tt._$litElement$=!0,null===(Q=globalThis.litElementHydrateSupport)||void 0===Q||Q.call(globalThis,{LitElement:tt});const et=globalThis.litElementPolyfillSupport;null==et||et({LitElement:tt}),(null!==(X=globalThis.litElementVersions)&&void 0!==X?X:globalThis.litElementVersions=[]).push("3.2.2");const it=(t,e)=>"method"===e.kind&&e.descriptor&&!("value"in e.descriptor)?{...e,finisher(i){i.createProperty(e.key,t)}}:{kind:"field",key:Symbol(),placement:"own",descriptor:{},originalKey:e.key,initializer(){"function"==typeof e.initializer&&(this[e.key]=e.initializer.call(this))},finisher(i){i.createProperty(e.key,t)}};function nt(t){return(e,i)=>void 0!==i?((t,e,i)=>{e.constructor.createProperty(i,t)})(t,e,i):it(t,e)}function st(t){return nt({...t,state:!0})}var ot;function rt(){return document.querySelector("hc-main")?document.querySelector("hc-main").hass:document.querySelector("home-assistant")?document.querySelector("home-assistant").hass:void 0}null===(ot=window.HTMLSlotElement)||void 0===ot||ot.prototype.assignedElements;const at="lovelace-player-device-id";function lt(){if(!localStorage[at]){const t=()=>Math.floor(1e5*(1+Math.random())).toString(16).substring(1);window.fully&&"function"==typeof fully.getDeviceId?localStorage[at]=fully.getDeviceId():localStorage[at]=`${t()}${t()}-${t()}${t()}`}return localStorage[at]}let dt=lt();const ct=new URLSearchParams(window.location.search);var ht;function ut(t){return!!String(t).includes("{%")||(!!String(t).includes("{{")||void 0)}ct.get("deviceID")&&null!==(ht=ct.get("deviceID"))&&("clear"===ht?localStorage.removeItem(at):localStorage[at]=ht,dt=lt()),window.cardMod_template_cache=window.cardMod_template_cache||{};const vt=window.cardMod_template_cache;async function ft(t,e,i){const n=rt().connection,s=JSON.stringify([e,i]);let o=vt[s];o?(o.callbacks.has(t)||pt(t),t(o.value),o.callbacks.add(t)):(pt(t),t(""),i=Object.assign({user:rt().user.name,browser:dt,hash:location.hash.substr(1)||""},i),vt[s]=o={template:e,variables:i,value:"",callbacks:new Set([t]),unsubscribe:n.subscribeMessage((t=>function(t,e){const i=vt[t];i&&(i.value=e.result,i.callbacks.forEach((t=>t(e.result))))}(s,t)),{type:"render_template",template:e,variables:i})})}async function pt(t){let e;for(const[i,n]of Object.entries(vt))if(n.callbacks.has(t)){n.callbacks.delete(t),0==n.callbacks.size&&(e=n.unsubscribe,delete vt[i]);break}e&&await(await e)()}var gt;const _t=(t,e)=>{if(t===e)return!0;if(typeof t!=typeof e)return!1;if(!(t instanceof Object&&e instanceof Object))return!1;for(const i in t)if(t.hasOwnProperty(i)){if(!e.hasOwnProperty(i))return!1;if(t[i]!==e[i]){if("object"!=typeof t[i])return!1;if(!_t(t[i],e[i]))return!1}}for(const i in e)if(e.hasOwnProperty(i)&&!t.hasOwnProperty(i))return!1;return!0};window.autoEntities_cache=null!==(gt=window.autoEntities_cache)&&void 0!==gt?gt:{};const mt=window.autoEntities_cache;async function yt(t){var e;return mt.areas=null!==(e=mt.areas)&&void 0!==e?e:await t.callWS({type:"config/area_registry/list"}),mt.areas}function bt(){return mt.areas}async function $t(t){var e;return mt.devices=null!==(e=mt.devices)&&void 0!==e?e:await t.callWS({type:"config/device_registry/list"}),mt.devices}function wt(){return mt.devices}async function Et(t){var e;return mt.entities=null!==(e=mt.entities)&&void 0!==e?e:await t.callWS({type:"config/entity_registry/list"}),mt.entities}function At(){return mt.entities}const Ct=/([mhd])\s+ago\s*$/i,St="m ago";function Ot(t,e){if("string"==typeof t&&t.startsWith("$$")&&(t=t.substring(2),e=JSON.stringify(e)),"string"==typeof e&&"string"==typeof t&&(t.startsWith("/")&&t.endsWith("/")||-1!==t.indexOf("*"))){return t.startsWith("/")||(t=`/^${t=t.replace(/\./g,".").replace(/\*/g,".*")}$/`),new RegExp(t.slice(1,-1)).test(e)}if("string"==typeof t){const i=Ct.exec(t);if(i){t=t.replace(i[0],"");e=((new Date).getTime()-new Date(e).getTime())/6e4;const n=i[1];"h"===n?e/=60:"d"===n&&(e=e/60/24)}}if("string"==typeof t){if(t.startsWith("<="))return parseFloat(e)<=parseFloat(t.substring(2));if(t.startsWith(">="))return parseFloat(e)>=parseFloat(t.substring(2));if(t.startsWith("<"))return parseFloat(e)"))return parseFloat(e)>parseFloat(t.substring(1));if(t.startsWith("!"))return parseFloat(e)!=parseFloat(t.substring(1));if(t.startsWith("="))return parseFloat(e)==parseFloat(t.substring(1))}return t===e}const jt={options:async()=>!0,sort:async()=>!0,domain:async(t,e,i)=>Ot(e,i.entity_id.split(".")[0]),entity_id:async(t,e,i)=>Ot(e,i.entity_id),state:async(t,e,i)=>Ot(e,i.state),name:async(t,e,i)=>{var n;return Ot(e,null===(n=i.attributes)||void 0===n?void 0:n.friendly_name)},group:async(t,e,i)=>{var n,s,o;return null===(o=null===(s=null===(n=t.states[e])||void 0===n?void 0:n.attributes)||void 0===s?void 0:s.entity_id)||void 0===o?void 0:o.includes(i.entity_id)},attributes:async(t,e,i)=>{for(const[t,n]of Object.entries(e)){let e=t.split(" ")[0],s=i.attributes;for(const t of e.split(":"))s=s?s[t]:void 0;if(void 0===s||!Ot(n,s))return!1}return!0},not:async(t,e,i)=>!await kt(t,e,i.entity_id),and:async(t,e,i)=>{for(const n of e)if(!await kt(t,n,i.entity_id))return!1;return!0},or:async(t,e,i)=>{for(const n of e)if(await kt(t,n,i.entity_id))return!0;return!1},device:async(t,e,i)=>{const n=(await Et(t)).find((t=>t.entity_id===i.entity_id));if(!n)return!1;const s=(await $t(t)).find((t=>t.id===n.device_id));return!!s&&(Ot(e,s.name_by_user)||Ot(e,s.name))},device_manufacturer:async(t,e,i)=>{const n=(await Et(t)).find((t=>t.entity_id===i.entity_id));if(!n)return!1;const s=(await $t(t)).find((t=>t.id===n.device_id));return!!s&&Ot(e,s.manufacturer)},device_model:async(t,e,i)=>{const n=(await Et(t)).find((t=>t.entity_id===i.entity_id));if(!n)return!1;const s=(await $t(t)).find((t=>t.id===n.device_id));return!!s&&Ot(e,s.model)},area:async(t,e,i)=>{const n=(await Et(t)).find((t=>t.entity_id===i.entity_id));if(!n)return!1;let s=(await yt(t)).find((t=>t.area_id===n.area_id));if(s)return Ot(e,s.name)||Ot(e,s.area_id);const o=(await $t(t)).find((t=>t.id===n.device_id));return!!o&&(s=(await yt(t)).find((t=>t.area_id===o.area_id)),!!s&&(Ot(e,s.name)||Ot(e,s.area_id)))},entity_category:async(t,e,i)=>{const n=(await Et(t)).find((t=>t.entity_id===i.entity_id));return!!n&&Ot(e,n.entity_category)},last_changed:async(t,e,i)=>(Ct.test(e)||(e+=St),Ot(e,i.last_changed)),last_updated:async(t,e,i)=>(Ct.test(e)||(e+=St),Ot(e,i.last_updated)),last_triggered:async(t,e,i)=>null!=i.attributes.last_triggered&&(Ct.test(e)||(e+=St),Ot(e,i.attributes.last_triggered)),integration:async(t,e,i)=>{const n=(await Et(t)).find((t=>t.entity_id===i.entity_id));return!!n&&Ot(e,n.platform)},hidden_by:async(t,e,i)=>{const n=(await Et(t)).find((t=>t.entity_id===i.entity_id));return!!n&&Ot(e,n.hidden_by)},label:async(t,e,i)=>{const n=(await Et(t)).find((t=>t.entity_id===i.entity_id)),s=await async function(t){var e;return mt.labels=null!==(e=mt.labels)&&void 0!==e?e:await t.callWS({type:"config/label_registry/list"}),mt.labels}(t),o=(t,e)=>{if(Ot(t,e))return!0;const i=s.find((t=>t.label_id===e));if(!i)return!1;return Ot(t,i.name)};if(!n)return!1;if(!n.labels)return!1;const r=n.labels.some((t=>o(e,t)));if(r)return r;const a=(await $t(t)).find((t=>t.id===n.device_id));if(!a)return!1;return a.labels.some((t=>o(e,t)))}};async function kt(t,e,i){var n;if(!t.states[i])return!1;for(let[s,o]of Object.entries(e))if(s=s.trim().split(" ")[0].trim(),!await(null===(n=jt[s])||void 0===n?void 0:n.call(jt,t,o,t.states[i])))return!1;return!0}function xt(t,e,i){var n,s,o,r;const[a,l]=i.reverse?[-1,1]:[1,-1];return i.ignore_case&&(t=null!==(s=null===(n=null==t?void 0:t.toLowerCase)||void 0===n?void 0:n.call(t))&&void 0!==s?s:t,e=null!==(r=null===(o=null==e?void 0:e.toLowerCase)||void 0===o?void 0:o.call(e))&&void 0!==r?r:e),i.numeric&&(isNaN(parseFloat(t))&&isNaN(parseFloat(e))||(t=isNaN(parseFloat(t))?void 0:parseFloat(t),e=isNaN(parseFloat(e))?void 0:parseFloat(e))),void 0===t&&void 0===e?0:void 0===t?a:void 0===e?l:i.numeric?t===e?0:(i.reverse?-1:1)*(t0,domain:(t,e,i)=>{var n,s;return xt(null===(n=null==t?void 0:t.entity_id)||void 0===n?void 0:n.split(".")[0],null===(s=null==e?void 0:e.entity_id)||void 0===s?void 0:s.split(".")[0],i)},entity_id:(t,e,i)=>xt(null==t?void 0:t.entity_id,null==e?void 0:e.entity_id,i),friendly_name:(t,e,i)=>{var n,s,o,r;return xt((null===(n=null==t?void 0:t.attributes)||void 0===n?void 0:n.friendly_name)||(null===(s=null==t?void 0:t.entity_id)||void 0===s?void 0:s.split(".")[1]),(null===(o=null==e?void 0:e.attributes)||void 0===o?void 0:o.friendly_name)||(null===(r=null==e?void 0:e.entity_id)||void 0===r?void 0:r.split(".")[1]),i)},name:(t,e,i)=>{var n,s,o,r;return xt((null===(n=null==t?void 0:t.attributes)||void 0===n?void 0:n.friendly_name)||(null===(s=null==t?void 0:t.entity_id)||void 0===s?void 0:s.split(".")[1]),(null===(o=null==e?void 0:e.attributes)||void 0===o?void 0:o.friendly_name)||(null===(r=null==e?void 0:e.entity_id)||void 0===r?void 0:r.split(".")[1]),i)},device:(t,e,i)=>{var n,s;const o=At().find((e=>e.entity_id===t.entity_id)),r=At().find((t=>t.entity_id===e.entity_id));if(!o||!r)return 0;const a=wt().find((t=>t.id===o.device_id)),l=wt().find((t=>t.id===r.device_id));return a&&l?xt(null!==(n=a.name_by_user)&&void 0!==n?n:a.name,null!==(s=l.name_by_user)&&void 0!==s?s:l.name,i):0},area:(t,e,i)=>{const n=At().find((e=>e.entity_id===t.entity_id)),s=At().find((t=>t.entity_id===e.entity_id));if(!n||!s)return 0;const o=wt().find((t=>t.id===n.device_id)),r=wt().find((t=>t.id===s.device_id));if(!o||!r)return 0;const a=bt().find((t=>t.area_id===o.area_id)),l=bt().find((t=>t.area_id===r.area_id));return a&&l?xt(a.name,l.name,i):0},state:(t,e,i)=>xt(null==t?void 0:t.state,null==e?void 0:e.state,i),attribute:(t,e,i)=>{var n;const[s,o]=(null==i?void 0:i.reverse)?[-1,1]:[1,-1];let r=null==t?void 0:t.attributes,a=null==e?void 0:e.attributes;for(const t of null===(n=null==i?void 0:i.attribute)||void 0===n?void 0:n.split(":")){if(void 0===r&&void 0===a)return 0;if(void 0===r)return s;if(void 0===a)return o;[r,a]=[r[t],a[t]]}return xt(r,a,i)},last_changed:(t,e,i)=>{const[n,s]=(null==i?void 0:i.reverse)?[-1,1]:[1,-1];return null==(null==t?void 0:t.last_changed)&&null==(null==e?void 0:e.last_changed)?0:null==(null==t?void 0:t.last_changed)?n:null==(null==e?void 0:e.last_changed)?s:(i.numeric=!0,xt(new Date(null==t?void 0:t.last_changed).getTime(),new Date(null==e?void 0:e.last_changed).getTime(),i))},last_updated:(t,e,i)=>{const[n,s]=(null==i?void 0:i.reverse)?[-1,1]:[1,-1];return null==(null==t?void 0:t.last_updated)&&null==(null==e?void 0:e.last_updated)?0:null==(null==t?void 0:t.last_updated)?n:null==(null==e?void 0:e.last_updated)?s:(i.numeric=!0,xt(new Date(null==t?void 0:t.last_updated).getTime(),new Date(null==e?void 0:e.last_updated).getTime(),i))},last_triggered:(t,e,i)=>{var n,s,o,r,a,l;const[d,c]=(null==i?void 0:i.reverse)?[-1,1]:[1,-1];return null==(null===(n=null==t?void 0:t.attributes)||void 0===n?void 0:n.last_triggered)&&null==(null===(s=null==e?void 0:e.attributes)||void 0===s?void 0:s.last_triggered)?0:null==(null===(o=null==t?void 0:t.attributes)||void 0===o?void 0:o.last_triggered)?d:null==(null===(r=null==e?void 0:e.attributes)||void 0===r?void 0:r.last_triggered)?c:(i.numeric=!0,xt(new Date(null===(a=null==t?void 0:t.attributes)||void 0===a?void 0:a.last_triggered).getTime(),new Date(null===(l=null==e?void 0:e.attributes)||void 0===l?void 0:l.last_triggered).getTime(),i))}};function Ut(t,e){return function(i,n){var s,o;return null!==(o=null===(s=Tt[e.method])||void 0===s?void 0:s.call(Tt,t.states[i.entity],t.states[n.entity],e))&&void 0!==o?o:0}}var Pt="1.13.0";const Mt=["none","domain","entity_id","state","name","group","area","device","device_manufacturer","device_model","attributes","last_changed","last_updated","last_triggered","entity_category","integration","hidden_by","label"],Nt={type:"select",options:[["domain","Entity Domain"],["entity_id","Entity ID"],["state","Entity State"],["name","Friendly Name"],["group","Member of Group"],["area","In area"],["device","Device"],["label","Label"],["device_manufacturer","Device Manufacturer"],["device_model","Device Model"],["attributes","Attribute"],["last_changed","Last Change"],["last_updated","Last Update"],["last_triggered","Last Trigger"],["entity_category","Entity Category"],["integration","Governing integration"],["hidden_by","Hidden by"]]},It=([t,e],i)=>{var n;return Mt.includes(t)?{type:"grid",name:"",schema:[Object.assign(Object.assign({},Nt),{name:`key_${i}`,label:"Property"}),{name:`value_${i}`,selector:null!==(n={attributes:{object:{}}}[t])&&void 0!==n?n:{text:{}},label:"Value"}]}:{type:"Constant",name:"Some filters are not shown",value:"Please switch to the CODE EDITOR to access all options."}},Rt=[{name:"options",selector:{object:{}}}],Ht=[{name:"data",selector:{object:{}}}],Dt=[{name:"method",label:"Sort method",type:"select",options:[["domain","Entity Domain"],["entity_id","Entity ID"],["friendly_name","Friendly Name"],["state","Entity State"],["last_changed","Last Change"],["last_updated","Last Update"],["last_triggered","Last Trigger"]]},{type:"constant",name:"Sorting options:",value:""},{type:"grid",name:"",schema:[{name:"reverse",type:"boolean",label:"Reverse"},{name:"ignore_case",type:"boolean",label:"Ignore case"},{name:"numeric",type:"boolean",label:"Numeric sort"},{name:"ip",type:"boolean",label:"IP address short"}]}],Ft=[{type:"grid",name:"",schema:[{name:"show_empty",type:"boolean",label:"Show if empty"},{name:"card_param",type:"string",label:"Parameter to populate"}]}];class Lt extends tt{constructor(){super(...arguments),this._selectedTab=0,this._cardGUIMode=!0,this._cardGUIModeAvailable=!0}setConfig(t){this._config=t}connectedCallback(){super.connectedCallback(),(async()=>{var t,e;if(customElements.get("ha-form"))return;const i=await(null===(e=(t=window).loadCardHelpers)||void 0===e?void 0:e.call(t));if(!i)return;const n=await i.createCardElement({type:"entity"});n&&await n.getConfigElement()})()}_handleSwitchTab(t){this._selectedTab=parseInt(t.detail.index,10)}_addFilterGroup(){var t;if(!this._config)return;const e=[...null===(t=this._config.filter)||void 0===t?void 0:t.include];e.push({});const i=Object.assign(Object.assign({},this._config.filter),{include:e});this._config=Object.assign(Object.assign({},this._config),{filter:i}),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_deleteFilterGroup(t){var e;if(!this._config)return;const i=[...null===(e=this._config.filter)||void 0===e?void 0:e.include];i.splice(t,1);const n=Object.assign(Object.assign({},this._config.filter),{include:i});this._config=Object.assign(Object.assign({},this._config),{filter:n}),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_moveFilterGroup(t,e){var i;if(!this._config)return;const n=[...null===(i=this._config.filter)||void 0===i?void 0:i.include];[n[t],n[t+e]]=[n[t+e],n[t]];const s=Object.assign(Object.assign({},this._config.filter),{include:n});this._config=Object.assign(Object.assign({},this._config),{filter:s}),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_addSpecialEntry(){var t;if(!this._config)return;const e=[...null===(t=this._config.filter)||void 0===t?void 0:t.include];e.push({type:""});const i=Object.assign(Object.assign({},this._config.filter),{include:e});this._config=Object.assign(Object.assign({},this._config),{filter:i}),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}async _changeSpecialEntry(t,e){var i,n,s,o;if(!this._config)return;const r=null!==(n=Object.assign({},null===(i=e.detail.value)||void 0===i?void 0:i.data))&&void 0!==n?n:{type:""};r.type=null!==(s=r.type)&&void 0!==s?s:"";const a=[...null===(o=this._config.filter)||void 0===o?void 0:o.include];a[t]=r;const l=Object.assign(Object.assign({},this._config.filter),{include:a});this._config=Object.assign(Object.assign({},this._config),{filter:l}),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}async _changeGroupOptions(t,e){var i;if(!this._config)return;const n=e.detail.value,s=[...null===(i=this._config.filter)||void 0===i?void 0:i.include];s[t]=Object.assign({},n);const o=Object.assign(Object.assign({},this._config.filter),{include:s});this._config=Object.assign(Object.assign({},this._config),{filter:o}),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_changeFilter(t,e){var i;if(!this._config)return;const n=((t,e)=>{var i;const n={};for(let s=0;s<=t.filter.include.length+1;s++)void 0!==e[`key_${s}`]&&(n[e[`key_${s}`]]=null!==(i=e[`value_${s}`])&&void 0!==i?i:"");return void 0!==e.key_new&&(n[e.key_new]=""),n})(this._config,e.detail.value),s=[...null===(i=this._config.filter)||void 0===i?void 0:i.include];s[t]=Object.assign(Object.assign({},n),{options:s[t].options}),this._config.filter=Object.assign(Object.assign({},this._config.filter),{include:s}),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_changeSortOptions(t){if(!this._config)return;const e=t.detail.value;this._config=Object.assign(Object.assign({},this._config),{sort:e}),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_changeCardOptions(t){if(!this._config)return;const e=t.detail.value;this._config=Object.assign(Object.assign({},this._config),e),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_showEmptyToggle(){if(!this._config)return;const t=!1===this._config.show_empty;this._config=Object.assign(Object.assign({},this._config),{show_empty:t}),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_changeCardParam(t){if(!this._config)return;const e=""===t.target.value||"entities"===t.target.value?void 0:t.target.value;this._config=Object.assign(Object.assign({},this._config),{card_param:e}),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_getCardConfig(){const t=Object.assign({},this._config.card);return t[this._config.card_param||"entities"]=[],t}_handleCardConfigChanged(t){if(t.stopPropagation(),!this._config)return;const e=Object.assign({},t.detail.config);delete e[this._config.card_param||"entities"],this._config=Object.assign(Object.assign({},this._config),{card:e}),this._cardGUIModeAvailable=t.detail.guiModeAvailable,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_deleteCard(t){this._config&&(this._config=Object.assign({},this._config),delete this._config.card,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}})))}_toggleCardMode(t){var e;null===(e=this._cardEditorEl)||void 0===e||e.toggleMode()}_cardGUIModeChanged(t){t.stopPropagation(),this._cardGUIMode=t.detail.guiMode,this._cardGUIModeAvailable=t.detail.guiModeAvailable}render(){return this.hass&&this._config?N` +
+
+ + + + + + +
+
+ ${[this._renderFilterEditor,this._renderSortEditor,this._renderCardEditor,this._renderHelp][this._selectedTab].bind(this)()} +
+
+ `:N``}_renderHelp(){return N` +
+

Auto entities

+

+ See + + auto-entities on github + + for usage instructions. +

+

Not all options are available in the GUI editor.

+
+ `}_renderFilterEditor(){var t;return(null===(t=this._config.filter)||void 0===t?void 0:t.template)||this._config.entities?N` +
+

+ Your filter method is not handled by the GUI editor. +

+

Please switch to the CODE EDITOR to access all options.

+
+ `:N` + ${this._config.filter.include.map(((t,e)=>N` +
+
+ this._moveFilterGroup(e,-1)} + > + + + this._moveFilterGroup(e,1)} + > + + + this._deleteFilterGroup(e)} + > + + +
+ ${void 0===t.type?N` + {const e=Object.assign({},t);return delete e.options,[...Object.entries(e).map(It),Object.assign(Object.assign({},Nt),{name:"key_new",label:"Select property"})]})(t)} + .data=${(t=>{const e=Object.assign({},t);return delete e.options,Object.assign({},...Object.entries(e).map((([t,e],i)=>({[`key_${i}`]:t,[`value_${i}`]:e}))))})(t)} + .computeLabel=${t=>{var e;return null!==(e=t.label)&&void 0!==e?e:t.name}} + @value-changed=${t=>this._changeFilter(e,t)} + > +

Options:

+ this._changeGroupOptions(e,t)} + > + `:N` + this._changeSpecialEntry(e,t)} + > + `} +
+ `))} + + Add filter group + + + Add non-filter entry + + `}_renderSortEditor(){var t;const e=null!==(t=this._config.sort)&&void 0!==t?t:{method:"none"};return N` +
+ {var e;return null!==(e=t.label)&&void 0!==e?e:t.name}} + @value-changed=${this._changeSortOptions} + > +
+ `}_renderCardEditor(){var t;const e=Object.assign({},this._config);return e.show_empty=null===(t=e.show_empty)||void 0===t||t,N` +
+ {var e;return null!==(e=t.label)&&void 0!==e?e:t.name}} + .data=${e} + @value-changed=${this._changeCardOptions} + > + ${this._config.card?N` +
+ + ${!this._cardEditorEl||this._cardGUIMode?"Show code editor":"Show Visual Editor"} + + + Change card type + +
+ + `:N` + + `} +
+ `}static get styles(){return[r` + mwc-tab-bar { + border-bottom: 1px solid var(--divider-color); + } + + .box { + margin-top: 8px; + border: 1px solid var(--divider-color); + padding: 12px; + } + .option { + display: flex; + align-items: center; + gap: 8px; + } + + .box .toolbar { + display: flex; + justify-content: flex-end; + width: 100%; + gap: 8px; + } + .gui-mode-button { + margin-right: auto; + } + a { + color: var(--primary-color); + } + `]}}t([st()],Lt.prototype,"_config",void 0),t([nt()],Lt.prototype,"lovelace",void 0),t([nt()],Lt.prototype,"hass",void 0),t([st()],Lt.prototype,"_selectedTab",void 0),t([st()],Lt.prototype,"_cardGUIMode",void 0),t([st()],Lt.prototype,"_cardGUIModeAvailable",void 0),t([function(t,e){return(({finisher:t,descriptor:e})=>(i,n)=>{var s;if(void 0===n){const n=null!==(s=i.originalKey)&&void 0!==s?s:i.key,o=null!=e?{kind:"method",placement:"prototype",key:n,descriptor:e(i.key)}:{...i,key:n};return null!=t&&(o.finisher=function(e){t(e,n)}),o}{const s=i.constructor;void 0!==e&&Object.defineProperty(i,n,e(n)),null==t||t(s,n)}})({descriptor:i=>{const n={get(){var e,i;return null!==(i=null===(e=this.renderRoot)||void 0===e?void 0:e.querySelector(t))&&void 0!==i?i:null},enumerable:!0,configurable:!0};if(e){const e="symbol"==typeof i?Symbol():"__"+i;n.get=function(){var i,n;return void 0===this[e]&&(this[e]=null!==(n=null===(i=this.renderRoot)||void 0===i?void 0:i.querySelector(t))&&void 0!==n?n:null),this[e]}}return n}})}("hui-card-element-editor")],Lt.prototype,"_cardEditorEl",void 0),customElements.define("auto-entities-editor",Lt),window.customCards=window.customCards||[],window.customCards.push({type:"auto-entities",name:"Auto Entities",preview:!1,description:"Entity Filter on Steroids. Auto Entities allows you to fill other cards with entities automatically, based on a number of attributes."}),window.queueMicrotask=window.queueMicrotask||(t=>window.setTimeout(t,1));const Wt=["section","divider"];class Gt extends tt{constructor(){super(...arguments),this.empty=!1,this._updateCooldown={timer:void 0,rerun:!1},this._renderer=t=>{this._template="string"==typeof t?t.split(/[\s,]+/):t}}static getConfigElement(){return document.createElement("auto-entities-editor")}static getStubConfig(){return{card:{type:"entities"},filter:{include:[],exclude:[]}}}setConfig(t){var e,i;if(!t)throw new Error("No configuration.");if(!(null===(e=t.card)||void 0===e?void 0:e.type))throw new Error("No card type specified.");if(!t.filter&&!t.entities)throw new Error("No filters specified.");t=JSON.parse(JSON.stringify(t)),this._config=t,(null===(i=this._config.filter)||void 0===i?void 0:i.template)&&ut(this._config.filter.template)&&ft(this._renderer,this._config.filter.template,{config:t}),this._cardBuilt=new Promise((t=>this._cardBuiltResolve=t)),queueMicrotask((()=>this.build_else())),queueMicrotask((()=>this.update_all()))}connectedCallback(){var t,e;super.connectedCallback(),(null===(e=null===(t=this._config)||void 0===t?void 0:t.filter)||void 0===e?void 0:e.template)&&ut(this._config.filter.template)&&ft(this._renderer,this._config.filter.template,{config:this._config})}disconnectedCallback(){super.disconnectedCallback(),pt(this._renderer)}async update_all(){if(this.card&&(this.card.hass=this.hass),this.else&&(this.else.hass=this.hass),this._updateCooldown.timer)return void(this._updateCooldown.rerun=!0);this._updateCooldown.rerun=!1,this._updateCooldown.timer=window.setTimeout((()=>{this._updateCooldown.timer=void 0,this._updateCooldown.rerun&&this.update_all()}),500);const t=await this.update_entities();this.update_card(t)}async build_else(){if(void 0===this._config.else)return;const t=await window.loadCardHelpers();this.else=await t.createCardElement(this._config.else),this.else.hass=this.hass}async update_card(t){var e,i,n,s,o,r,a,l,d,c,h,u;if(this._entities&&_t(t,this._entities)&&_t(this._cardConfig,this._config.card))return;const v=(null===(e=this._cardConfig)||void 0===e?void 0:e.type)!==this._config.card.type;this._entities=t,this._cardConfig=JSON.parse(JSON.stringify(this._config.card));const f=Object.assign({[this._config.card_param||"entities"]:t},this._config.card);if(!this.card||v){const t=await window.loadCardHelpers();console.oldError=console.oldError||[];const e=console.error;console.oldError.push(e),console.error=(...t)=>{var i,n,s,o,r,a;3===t.length&&t[2].message&&((null===(n=(i=t[2].message).startsWith)||void 0===n?void 0:n.call(i,"Entities"))||(null===(o=(s=t[2].message).startsWith)||void 0===o?void 0:o.call(s,"Either entities"))||(null===(a=(r=t[2].message).endsWith)||void 0===a?void 0:a.call(r,"entity")))||e(...t)};try{if(this.card=await t.createCardElement(f),"hui-error-card"===this.card.localName){const t=this.card;await customElements.whenDefined("hui-error-card");let e=10;for(;!t._config&&e;)await new Promise((t=>window.setTimeout(t,100))),e--;if((null===(s=null===(n=null===(i=t._config)||void 0===i?void 0:i.error)||void 0===n?void 0:n.startsWith)||void 0===s?void 0:s.call(n,"Entities"))||(null===(a=null===(r=null===(o=t._config)||void 0===o?void 0:o.error)||void 0===r?void 0:r.startsWith)||void 0===a?void 0:a.call(r,"Either entities"))||(null===(c=null===(d=null===(l=t._config)||void 0===l?void 0:l.error)||void 0===d?void 0:d.endsWith)||void 0===c?void 0:c.call(d,"entity")))return this.card=void 0,this._entities=void 0,this._cardConfig=void 0,void(null===(h=this._cardBuiltResolve)||void 0===h||h.call(this))}}finally{console.error=console.oldError.pop()}}else this.card.setConfig(f);null===(u=this._cardBuiltResolve)||void 0===u||u.call(this),this.card.hass=this.hass,this.empty=0===t.length||t.every((t=>Wt.includes(t.type)));const p=this.empty&&!1===this._config.show_empty&&void 0===this._config.else;this.style.display=p?"none":null,this.style.margin=p?"0":null,this.card.requestUpdate&&(await this.updateComplete,this.card.requestUpdate())}async update_entities(){var t,e,i,n,s,o,r,a;const l=t=>t?"string"==typeof t?{entity:t.trim()}:t:null;let d=[...(null===(e=null===(t=this._config)||void 0===t?void 0:t.entities)||void 0===e?void 0:e.map(l))||[]];if(!this.hass)return d;if(this._template&&(d=d.concat(this._template.map(l))),d=d.filter(Boolean),null===(i=this._config.filter)||void 0===i?void 0:i.include){const t=Object.keys(this.hass.states).map(l);for(const e of this._config.filter.include){if(e.type){d.push(e);continue}let i=[];for(const n of t)await kt(this.hass,e,n.entity)&&i.push(JSON.parse(JSON.stringify(Object.assign(Object.assign({},n),e.options)).replace(/this.entity_id/g,n.entity)));if(e.sort&&(await Et(this.hass),await $t(this.hass),await yt(this.hass),i=i.sort(Ut(this.hass,e.sort)),null!==(n=e.sort.count)&&void 0!==n?n:e.sort.first)){const t=null!==(s=e.sort.first)&&void 0!==s?s:0;i=i.slice(t,t+(null!==(o=e.sort.count)&&void 0!==o?o:1/0))}d=d.concat(i)}}if(null===(r=this._config.filter)||void 0===r?void 0:r.exclude)for(const t of this._config.filter.exclude){const e=[];for(const i of d)void 0!==i.entity&&await kt(this.hass,t,i.entity)||e.push(i);d=e}if(this._config.sort&&(d=d.sort(Ut(this.hass,this._config.sort)),this._config.sort.count)){const t=null!==(a=this._config.sort.first)&&void 0!==a?a:0;d=d.slice(t,t+this._config.sort.count)}if(this._config.unique){let t=[];for(const e of d)"entity"===this._config.unique&&e.entity&&t.some((t=>t.entity===e.entity))||t.some((t=>_t(t,e)))||t.push(e);d=t}return d}async updated(t){(t.has("_template")||t.has("hass")&&this.hass)&&queueMicrotask((()=>this.update_all()))}createRenderRoot(){return this}render(){return N`${this.empty&&(!1===this._config.show_empty||this._config.else)?this.else:this.card}`}async getCardSize(){var t,e;let i=0;return await this._cardBuilt,this.card&&this.card.getCardSize&&(i=await this.card.getCardSize()),1===i&&(null===(t=this._entities)||void 0===t?void 0:t.length)&&(i=this._entities.length),0===i&&(null===(e=this._config.filter)||void 0===e?void 0:e.include)&&(i=Object.keys(this._config.filter.include).length),i||5}}t([nt()],Gt.prototype,"_config",void 0),t([nt()],Gt.prototype,"hass",void 0),t([nt()],Gt.prototype,"card",void 0),t([nt()],Gt.prototype,"else",void 0),t([nt()],Gt.prototype,"_template",void 0),t([st()],Gt.prototype,"empty",void 0),customElements.get("auto-entities")||(customElements.define("auto-entities",Gt),console.groupCollapsed(`%cAUTO-ENTITIES ${Pt} IS INSTALLED`,"color: green; font-weight: bold"),console.log("Readme:","https://github.com/thomasloven/lovelace-auto-entities"),console.groupEnd()); diff --git a/hass/settings/www/community/lovelace-auto-entities/auto-entities.js.gz b/hass/settings/www/community/lovelace-auto-entities/auto-entities.js.gz new file mode 100644 index 0000000..69894b8 Binary files /dev/null and b/hass/settings/www/community/lovelace-auto-entities/auto-entities.js.gz differ diff --git a/hass/settings/www/community/lovelace-auto-entities/rollup.config.js b/hass/settings/www/community/lovelace-auto-entities/rollup.config.js new file mode 100644 index 0000000..b067bae --- /dev/null +++ b/hass/settings/www/community/lovelace-auto-entities/rollup.config.js @@ -0,0 +1,24 @@ +import nodeResolve from "@rollup/plugin-node-resolve"; +import json from "@rollup/plugin-json"; +import typescript from "rollup-plugin-typescript2"; +import terser from "@rollup/plugin-terser"; +import babel from "@rollup/plugin-babel"; + +const dev = process.env.ROLLUP_WATCH; + +export default { + input: "src/main.ts", + output: { + file: "auto-entities.js", + format: "es", + }, + plugins: [ + nodeResolve(), + json(), + typescript(), + babel({ + exclude: "node_modules/**", + }), + !dev && terser({ format: { comments: false } }), + ], +}; diff --git a/hass/settings/www/community/lovelace-auto-entities/rollup.config.js.gz b/hass/settings/www/community/lovelace-auto-entities/rollup.config.js.gz new file mode 100644 index 0000000..f763538 Binary files /dev/null and b/hass/settings/www/community/lovelace-auto-entities/rollup.config.js.gz differ diff --git a/hass/settings/www/community/lovelace-multiple-entity-row/multiple-entity-row.js b/hass/settings/www/community/lovelace-multiple-entity-row/multiple-entity-row.js new file mode 100644 index 0000000..9550673 --- /dev/null +++ b/hass/settings/www/community/lovelace-multiple-entity-row/multiple-entity-row.js @@ -0,0 +1 @@ +(()=>{"use strict";const t=window,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,n=Symbol(),i=new WeakMap;class r{constructor(t,e,i){if(this._$cssResult$=!0,i!==n)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const n=this.t;if(e&&void 0===t){const e=void 0!==n&&1===n.length;e&&(t=i.get(n)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&i.set(n,t))}return t}toString(){return this.cssText}}const o=(t,...e)=>{const i=1===t.length?t[0]:e.reduce(((e,n,i)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+t[i+1]),t[0]);return new r(i,t,n)},s=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const n of t.cssRules)e+=n.cssText;return(t=>new r("string"==typeof t?t:t+"",void 0,n))(e)})(t):t;var a;const c=window,l=c.trustedTypes,u=l?l.emptyScript:"",h=c.reactiveElementPolyfillSupport,f={toAttribute(t,e){switch(e){case Boolean:t=t?u:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let n=t;switch(e){case Boolean:n=null!==t;break;case Number:n=null===t?null:Number(t);break;case Object:case Array:try{n=JSON.parse(t)}catch(t){n=null}}return n}},d=(t,e)=>e!==t&&(e==e||t==t),p={attribute:!0,type:String,converter:f,reflect:!1,hasChanged:d};class m extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this.u()}static addInitializer(t){var e;this.finalize(),(null!==(e=this.h)&&void 0!==e?e:this.h=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((e,n)=>{const i=this._$Ep(n,e);void 0!==i&&(this._$Ev.set(i,n),t.push(i))})),t}static createProperty(t,e=p){if(e.state&&(e.attribute=!1),this.finalize(),this.elementProperties.set(t,e),!e.noAccessor&&!this.prototype.hasOwnProperty(t)){const n="symbol"==typeof t?Symbol():"__"+t,i=this.getPropertyDescriptor(t,n,e);void 0!==i&&Object.defineProperty(this.prototype,t,i)}}static getPropertyDescriptor(t,e,n){return{get(){return this[e]},set(i){const r=this[t];this[e]=i,this.requestUpdate(t,r,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||p}static finalize(){if(this.hasOwnProperty("finalized"))return!1;this.finalized=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,e=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const n of e)this.createProperty(n,t[n])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const n=new Set(t.flat(1/0).reverse());for(const t of n)e.unshift(s(t))}else void 0!==t&&e.push(s(t));return e}static _$Ep(t,e){const n=e.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof t?t.toLowerCase():void 0}u(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)))}addController(t){var e,n;(null!==(e=this._$ES)&&void 0!==e?e:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(n=t.hostConnected)||void 0===n||n.call(t))}removeController(t){var e;null===(e=this._$ES)||void 0===e||e.splice(this._$ES.indexOf(t)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach(((t,e)=>{this.hasOwnProperty(e)&&(this._$Ei.set(e,this[e]),delete this[e])}))}createRenderRoot(){var n;const i=null!==(n=this.shadowRoot)&&void 0!==n?n:this.attachShadow(this.constructor.shadowRootOptions);return((n,i)=>{e?n.adoptedStyleSheets=i.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):i.forEach((e=>{const i=document.createElement("style"),r=t.litNonce;void 0!==r&&i.setAttribute("nonce",r),i.textContent=e.cssText,n.appendChild(i)}))})(i,this.constructor.elementStyles),i}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostConnected)||void 0===e?void 0:e.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostDisconnected)||void 0===e?void 0:e.call(t)}))}attributeChangedCallback(t,e,n){this._$AK(t,n)}_$EO(t,e,n=p){var i;const r=this.constructor._$Ep(t,n);if(void 0!==r&&!0===n.reflect){const o=(void 0!==(null===(i=n.converter)||void 0===i?void 0:i.toAttribute)?n.converter:f).toAttribute(e,n.type);this._$El=t,null==o?this.removeAttribute(r):this.setAttribute(r,o),this._$El=null}}_$AK(t,e){var n;const i=this.constructor,r=i._$Ev.get(t);if(void 0!==r&&this._$El!==r){const t=i.getPropertyOptions(r),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(n=t.converter)||void 0===n?void 0:n.fromAttribute)?t.converter:f;this._$El=r,this[r]=o.fromAttribute(e,t.type),this._$El=null}}requestUpdate(t,e,n){let i=!0;void 0!==t&&(((n=n||this.constructor.getPropertyOptions(t)).hasChanged||d)(this[t],e)?(this._$AL.has(t)||this._$AL.set(t,e),!0===n.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,n))):i=!1),!this.isUpdatePending&&i&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach(((t,e)=>this[e]=t)),this._$Ei=void 0);let e=!1;const n=this._$AL;try{e=this.shouldUpdate(n),e?(this.willUpdate(n),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostUpdate)||void 0===e?void 0:e.call(t)})),this.update(n)):this._$Ek()}catch(t){throw e=!1,this._$Ek(),t}e&&this._$AE(n)}willUpdate(t){}_$AE(t){var e;null===(e=this._$ES)||void 0===e||e.forEach((t=>{var e;return null===(e=t.hostUpdated)||void 0===e?void 0:e.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return!0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,e)=>this._$EO(e,this[e],t))),this._$EC=void 0),this._$Ek()}updated(t){}firstUpdated(t){}}var y;m.finalized=!0,m.elementProperties=new Map,m.elementStyles=[],m.shadowRootOptions={mode:"open"},null==h||h({ReactiveElement:m}),(null!==(a=c.reactiveElementVersions)&&void 0!==a?a:c.reactiveElementVersions=[]).push("1.6.1");const v=window,b=v.trustedTypes,_=b?b.createPolicy("lit-html",{createHTML:t=>t}):void 0,g="$lit$",$=`lit$${(Math.random()+"").slice(9)}$`,w="?"+$,A=`<${w}>`,S=document,O=()=>S.createComment(""),E=t=>null===t||"object"!=typeof t&&"function"!=typeof t,j=Array.isArray,P="[ \t\n\f\r]",k=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,x=/-->/g,C=/>/g,N=RegExp(`>|${P}(?:([^\\s"'>=/]+)(${P}*=${P}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),D=/'/g,T=/"/g,U=/^(?:script|style|textarea|title)$/i,R=t=>(e,...n)=>({_$litType$:t,strings:e,values:n}),H=R(1),M=(R(2),Symbol.for("lit-noChange")),I=Symbol.for("lit-nothing"),z=new WeakMap,L=S.createTreeWalker(S,129,null,!1),F=(t,e)=>{const n=t.length-1,i=[];let r,o=2===e?"":"",s=k;for(let e=0;e"===c[0]?(s=null!=r?r:k,l=-1):void 0===c[1]?l=-2:(l=s.lastIndex-c[2].length,a=c[1],s=void 0===c[3]?N:'"'===c[3]?T:D):s===T||s===D?s=N:s===x||s===C?s=k:(s=N,r=void 0);const h=s===N&&t[e+1].startsWith("/>")?" ":"";o+=s===k?n+A:l>=0?(i.push(a),n.slice(0,l)+g+n.slice(l)+$+h):n+$+(-2===l?(i.push(void 0),e):h)}const a=o+(t[n]||"")+(2===e?"":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==_?_.createHTML(a):a,i]};class B{constructor({strings:t,_$litType$:e},n){let i;this.parts=[];let r=0,o=0;const s=t.length-1,a=this.parts,[c,l]=F(t,e);if(this.el=B.createElement(c,n),L.currentNode=this.el.content,2===e){const t=this.el.content,e=t.firstChild;e.remove(),t.append(...e.childNodes)}for(;null!==(i=L.nextNode())&&a.length0){i.textContent=b?b.emptyScript:"";for(let n=0;nj(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]))(t)?this.T(t):this._(t)}k(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}$(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t))}_(t){this._$AH!==I&&E(this._$AH)?this._$AA.nextSibling.data=t:this.$(S.createTextNode(t)),this._$AH=t}g(t){var e;const{values:n,_$litType$:i}=t,r="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=B.createElement(i.h,this.options)),i);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===r)this._$AH.v(n);else{const t=new W(r,this),e=t.u(this.options);t.v(n),this.$(e),this._$AH=t}}_$AC(t){let e=z.get(t.strings);return void 0===e&&z.set(t.strings,e=new B(t)),e}T(t){j(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let n,i=0;for(const r of t)i===e.length?e.push(n=new J(this.k(O()),this.k(O()),this,this.options)):n=e[i],n._$AI(r),i++;i2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=I}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,e=this,n,i){const r=this.strings;let o=!1;if(void 0===r)t=V(this,t,e,0),o=!E(t)||t!==this._$AH&&t!==M,o&&(this._$AH=t);else{const i=t;let s,a;for(t=r[0],s=0;s{var i,r;const o=null!==(i=null==n?void 0:n.renderBefore)&&void 0!==i?i:e;let s=o._$litPart$;if(void 0===s){const t=null!==(r=null==n?void 0:n.renderBefore)&&void 0!==r?r:null;o._$litPart$=s=new J(e.insertBefore(O(),t),t,void 0,null!=n?n:{})}return s._$AI(t),s})(e,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}render(){return M}}nt.finalized=!0,nt._$litElement$=!0,null===(tt=globalThis.litElementHydrateSupport)||void 0===tt||tt.call(globalThis,{LitElement:nt});const it=globalThis.litElementPolyfillSupport;null==it||it({LitElement:nt}),(null!==(et=globalThis.litElementVersions)&&void 0!==et?et:globalThis.litElementVersions=[]).push("3.3.2");var rt="\\d\\d?",ot="\\d\\d",st="[^\\s]+";function at(t,e){for(var n=[],i=0,r=t.length;i-1?r:null}};function lt(t){for(var e=[],n=1;n3?0:(t-t%10!=10?1:0)*t%10]}},pt=(lt({},dt),function(t){return+t-1}),mt=[null,rt],yt=[null,st],vt=["isPm",st,function(t,e){var n=t.toLowerCase();return n===e.amPm[0]?0:n===e.amPm[1]?1:null}],bt=["timezoneOffset","[^\\s]*?[\\+\\-]\\d\\d:?\\d\\d|[^\\s]*?Z?",function(t){var e=(t+"").match(/([+-]|\d\d)/gi);if(e){var n=60*+e[1]+parseInt(e[2],10);return"+"===e[0]?n:-n}return 0}];ct("monthNamesShort"),ct("monthNames");function _t(){return(_t=Object.assign||function(t){for(var e=1;e0?"".concat(e,":").concat(Dt(n),":").concat(Dt(i)):n>0?"".concat(n,":").concat(Dt(i)):i>0?""+i:null}function Ut(t){return Ut="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Ut(t)}function Rt(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function Ht(t,e,n){return(e=function(t){var e=function(t,e){if("object"!==Ut(t)||null===t)return t;var n=t[Symbol.toPrimitive];if(void 0!==n){var i=n.call(t,"string");if("object"!==Ut(i))return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"===Ut(e)?e:String(e)}(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var Mt=function(t,e,n){var i=e?function(t){switch(t.number_format){case"comma_decimal":return["en-US","en"];case"decimal_comma":return["de","es","it"];case"space_comma":return["fr","sv","cs"];case"system":return;default:return t.language}}(e):void 0;if(Number.isNaN=Number.isNaN||function t(e){return"number"==typeof e&&t(e)},"none"!==(null==e?void 0:e.number_format)&&!Number.isNaN(Number(t))&&Intl)try{return new Intl.NumberFormat(i,It(t,n)).format(Number(t))}catch(e){return console.error(e),new Intl.NumberFormat(void 0,It(t,n)).format(Number(t))}return"string"==typeof t?t:"".concat(function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:2;return Math.round(t*Math.pow(10,e))/Math.pow(10,e)}(t,null==n?void 0:n.maximumFractionDigits).toString()).concat("currency"===(null==n?void 0:n.style)?" ".concat(n.currency):"")},It=function(t,e){var n=function(t){for(var e=1;e-1?t.split(".")[1].length:0;n.minimumFractionDigits=i,n.maximumFractionDigits=i}return n},zt=Number.isNaN||function(t){return"number"==typeof t&&t!=t};function Lt(t,e){if(t.length!==e.length)return!1;for(var n=0;ne.hide_if.above)return!0;e.hide_if.value&&(i=i.concat(e.hide_if.value))}else i=i.concat(e.hide_if);return i.some((function(t){return"number"==typeof t?t===+n:t===n}))};function te(t){return te="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},te(t)}function ee(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function ne(t){for(var e=1;e\n
\n ',"","\n
\n "])),this._hass,this.config,this.renderSecondaryInfo(),!1,this.config.column?"entities-column":"entities-row",this.entities.map((function(e){return t.renderEntity(e.stateObj,e)})),this.renderMainEntity()):this.renderWarning():H(oe||(oe=we([""])))}},{key:"renderSecondaryInfo",value:function(){if(!this.config.secondary_info||"string"==typeof(t=this.config.secondary_info)&&Ct.includes(t)||Xt(this.info,this.config.secondary_info))return null;var t;if("string"==typeof this.config.secondary_info)return H(ae||(ae=we(["",""])),this.config.secondary_info);var e=_e(this.info,this.config.secondary_info);return H(ce||(ce=we([""," ",""])),e,this.renderValue(this.info,this.config.secondary_info))}},{key:"renderMainEntity",value:function(){return!1===this.config.show_state?null:H(le||(le=we(['
\n ',"\n
","
\n
"])),ge(this.config),this.onRowClick,this.config.state_header&&H(ue||(ue=we(["",""])),this.config.state_header),this.renderValue(this.stateObj,this.config))}},{key:"renderEntity",value:function(t,e){if(!t||Xt(t,e))return e.default?H(he||(he=we(['
\n ',"\n
","
\n
"])),ge(e),e.name,e.default):null;var n=this.clickHandler(t.entity_id,e.tap_action);return H(fe||(fe=we(['
\n ',"\n
","
\n
"])),ge(e),n,_e(t,e),e.icon?this.renderIcon(t,e):this.renderValue(t,e))}},{key:"renderValue",value:function(t,e){if(!0===e.toggle)return H(de||(de=we([''])),t,this._hass);var n;if(e.attribute&&[Pt,kt].includes(e.attribute))return H(pe||(pe=we([""])),this._hass,t[null===(n=e.attribute)||void 0===n?void 0:n.replace("-","_")]);if(e.format&&xt.includes(e.format)){var i,r=e.attribute?null!==(i=t.attributes[e.attribute])&&void 0!==i?i:t[e.attribute]:t.state,o=new Date(r);return o instanceof Date&&!isNaN(o.getTime())?H(me||(me=we([""])),this._hass,o,e.format):r}return function(t,e,n){if(Qt(e))return t.localize("state.default.".concat(e.state));var i=n.attribute?e.attributes[n.attribute]:e.state,r=!1===n.unit?void 0:void 0!==n.attribute?n.unit:n.unit||e.attributes.unit_of_measurement;if(n.format){if(isNaN(parseFloat(i))||!isFinite(i));else if("brightness"===n.format)i=Math.round(i/255*100),r="%";else if("duration"===n.format)i=Tt(i),r=void 0;else if("duration-m"===n.format)i=Tt(i/1e3),r=void 0;else if("duration-h"===n.format)i=Tt(3600*i),r=void 0;else if(n.format.startsWith("precision")){var o=parseInt(n.format.slice(-1),10);i=Mt(parseFloat(i),t.locale,{minimumFractionDigits:o,maximumFractionDigits:o})}else"kilo"===n.format?i=Mt(i/1e3,t.locale,{maximumFractionDigits:2}):"invert"===n.format?i=Mt(i-2*i,t.locale):"position"===n.format?i=Mt(100-i,t.locale):"celsius_to_fahrenheit"===n.format?i=Mt(1.8*i+32,t.locale,{maximumFractionDigits:0}):"fahrenheit_to_celsius"===n.format&&(i=Mt(5*(i-32)/9,t.locale,{maximumFractionDigits:1}));return"".concat(i).concat(r?" ".concat(r):"")}if(n.attribute)return"".concat(isNaN(i)?i:Mt(i,t.locale)).concat(r?" ".concat(r):"");var s=ne(ne({},e),{},{attributes:ne(ne({},e.attributes),{},{unit_of_measurement:r})});return function(t,e,n,i,r){var o=e.state;if(o===Et||o===Ot)return t("state.default.".concat(o));if(function(t){return!!t.attributes.unit_of_measurement||!!t.attributes.state_class}(e)){if("monetary"===e.attributes.device_class)try{return Mt(o,n,{style:"currency",currency:e.attributes.unit_of_measurement})}catch(t){}return"".concat(Mt(o,n)).concat(e.attributes.unit_of_measurement?" "+e.attributes.unit_of_measurement:"")}var s,a=function(t){return t.entity_id.substr(0,t.entity_id.indexOf("."))}(e);if("input_datetime"===a)return e.attributes.has_date&&e.attributes.has_time?(s=new Date(e.attributes.year,e.attributes.month-1,e.attributes.day,e.attributes.hour,e.attributes.minute),Jt(s,n)):e.attributes.has_date?(s=new Date(e.attributes.year,e.attributes.month-1,e.attributes.day),Bt(s,n)):e.attributes.has_time?((s=new Date).setHours(e.attributes.hour,e.attributes.minute),Zt(s,n)):e.state;if("humidifier"===a&&"on"===o&&e.attributes.humidity)return"".concat(e.attributes.humidity," %");if("counter"===a||"number"===a||"input_number"===a)return Mt(o,n);if("button"===a||"sensor"===a&&"timestamp"===e.attributes.device_class)return Jt(new Date(o),n);var c=i&&i[e.entity_id];return(null==c?void 0:c.translation_key)&&t("component.".concat(c.platform,".entity.").concat(a,".").concat(c.translation_key,".state.").concat(o))||e.attributes.device_class&&t("component.".concat(a,".entity_component.").concat(e.attributes.device_class,".state.").concat(o))||t("component.".concat(a,".entity_component._.state.").concat(o))||o}(t.localize,s,t.locale,t.entities)}(this._hass,t,e)}},{key:"renderIcon",value:function(t,e){return H(ye||(ye=we([''])),this._hass,t,!0===e.icon?t.attributes.icon||null:e.icon,e.state_color)}},{key:"renderWarning",value:function(){return H(ve||(ve=we(["\n ","\n "])),this._hass.localize("ui.panel.lovelace.warning.entity_not_found","entity",this.config.entity))}},{key:"clickHandler",value:function(t,e){var n=this;return function(){return function(t,e,n,i,r){var o;if(n.tap_action&&(o=n.tap_action),o||(o={action:"more-info"}),!o.confirmation||o.confirmation.exemptions&&o.confirmation.exemptions.some((function(t){return t.user===e.user.id}))||confirm(o.confirmation.text||"Are you sure you want to "+o.action+"?"))switch(o.action){case"more-info":(o.entity||n.entity||n.camera_image)&&(At(t,"hass-more-info",{entityId:o.entity?o.entity:n.entity?n.entity:n.camera_image}),o.haptic&&St(o.haptic));break;case"navigate":o.navigation_path&&(function(t,e,n){void 0===n&&(n=!1),n?history.replaceState(null,"",e):history.pushState(null,"",e),At(window,"location-changed",{replace:n})}(0,o.navigation_path),o.haptic&&St(o.haptic));break;case"url":o.url_path&&window.open(o.url_path),o.haptic&&St(o.haptic);break;case"toggle":n.entity&&(function(t,e){(function(t,e,n){void 0===n&&(n=!0);var i,r=function(t){return t.substr(0,t.indexOf("."))}(e),o="group"===r?"homeassistant":r;switch(r){case"lock":i=n?"unlock":"lock";break;case"cover":i=n?"open_cover":"close_cover";break;default:i=n?"turn_on":"turn_off"}t.callService(o,i,{entity_id:e})})(t,e,wt.includes(t.states[e].state))}(e,n.entity),o.haptic&&St(o.haptic));break;case"call-service":if(!o.service)return;var s=o.service.split(".",2),a=s[0],c=s[1],l=_t({},o.service_data);"entity"===l.entity_id&&(l.entity_id=n.entity),e.callService(a,c,l),o.haptic&&St(o.haptic);break;case"fire-dom-event":At(t,"ll-custom",o),o.haptic&&St(o.haptic)}}(n,n._hass,{entity:t,tap_action:e})}}}],i=[{key:"properties",get:function(){return{_hass:Object,config:Object,stateObj:Object}}},{key:"styles",get:function(){return o(re||(t=["\n .icon-small {\n width: auto;\n }\n .entity {\n text-align: center;\n cursor: pointer;\n }\n .entity span {\n font-size: 10px;\n color: var(--secondary-text-color);\n }\n .entities-row {\n flex-direction: row;\n display: inline-flex;\n justify-content: space-between;\n align-items: center;\n }\n .entities-row .entity {\n margin-right: 16px;\n }\n .entities-row .entity:last-of-type {\n margin-right: 0;\n }\n .entities-column {\n flex-direction: column;\n display: flex;\n align-items: flex-end;\n justify-content: space-evenly;\n }\n .entities-column .entity div {\n display: inline-block;\n vertical-align: middle;\n }\n"],e||(e=t.slice(0)),re=Object.freeze(Object.defineProperties(t,{raw:{value:Object.freeze(e)}}))));var t,e}}],n&&Ee(e.prototype,n),i&&Ee(e,i),Object.defineProperty(e,"prototype",{writable:!1}),c}(nt);customElements.define("multiple-entity-row",xe)})(); \ No newline at end of file diff --git a/hass/settings/www/community/lovelace-multiple-entity-row/multiple-entity-row.js.gz b/hass/settings/www/community/lovelace-multiple-entity-row/multiple-entity-row.js.gz new file mode 100644 index 0000000..c81639e Binary files /dev/null and b/hass/settings/www/community/lovelace-multiple-entity-row/multiple-entity-row.js.gz differ diff --git a/hass/settings/www/danil.jpg b/hass/settings/www/danil.jpg new file mode 100644 index 0000000..f269b09 Binary files /dev/null and b/hass/settings/www/danil.jpg differ diff --git a/hass/settings/www/svetlana.jpg b/hass/settings/www/svetlana.jpg new file mode 100644 index 0000000..0ce1ee1 Binary files /dev/null and b/hass/settings/www/svetlana.jpg differ diff --git a/homeconnect2mqtt/docker/Dockerfile b/homeconnect2mqtt/docker/Dockerfile new file mode 100644 index 0000000..a148744 --- /dev/null +++ b/homeconnect2mqtt/docker/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.10 +WORKDIR /app + +ADD ./src/requirements.txt /app/requirements.txt +RUN pip3 install -r requirements.txt + +ADD ./src /app + +CMD [ "python3", "-u", "hc2mqtt.py", "config.json" ] diff --git a/homeconnect2mqtt/docker/src/HCDevice.py b/homeconnect2mqtt/docker/src/HCDevice.py new file mode 100644 index 0000000..03912a8 --- /dev/null +++ b/homeconnect2mqtt/docker/src/HCDevice.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +# Parse messages from a Home Connect websocket (HCSocket) +# and keep the connection alive +# +# Possible resources to fetch from the devices: +# +# /ro/values +# /ro/descriptionChange +# /ro/allMandatoryValues +# /ro/allDescriptionChanges +# /ro/activeProgram +# /ro/selectedProgram +# +# /ei/initialValues +# /ei/deviceReady +# +# /ci/services +# /ci/registeredDevices +# /ci/pairableDevices +# /ci/delregistration +# /ci/networkDetails +# /ci/networkDetails2 +# /ci/wifiNetworks +# /ci/wifiSetting +# /ci/wifiSetting2 +# /ci/tzInfo +# /ci/authentication +# /ci/register +# /ci/deregister +# +# /ce/serverDeviceType +# /ce/serverCredential +# /ce/clientCredential +# /ce/hubInformation +# /ce/hubConnected +# /ce/status +# +# /ni/config +# +# /iz/services + +import sys +import json +import re +import time +import io +import traceback +from datetime import datetime +from base64 import urlsafe_b64encode as base64url_encode +from Crypto.Random import get_random_bytes + + +def now(): + return datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + +class HCDevice: + def __init__(self, ws, features): + self.ws = ws + self.features = features + self.session_id = None + self.tx_msg_id = None + self.device_name = "hcpy" + self.device_id = "0badcafe" + self.debug = False + + def parse_values(self, values): + if not self.features: + return values + + result = {} + + for msg in values: + uid = str(msg["uid"]) + value = msg["value"] + value_str = str(value) + + name = uid + status = None + + if uid in self.features: + status = self.features[uid] + + if status: + name = status["name"] + if "values" in status \ + and value_str in status["values"]: + value = status["values"][value_str] + + # trim everything off the name except the last part + name = re.sub(r'^.*\.', '', name) + result[name] = value + + return result + + def recv(self): + try: + buf = self.ws.recv() + if buf is None: + return None + except Exception as e: + print("receive error", e, traceback.format_exc()) + return None + + try: + return self.handle_message(buf) + except Exception as e: + print("error handling msg", e, buf, traceback.format_exc()) + return None + + # reply to a POST or GET message with new data + def reply(self, msg, reply): + self.ws.send({ + 'sID': msg["sID"], + 'msgID': msg["msgID"], # same one they sent to us + 'resource': msg["resource"], + 'version': msg["version"], + 'action': 'RESPONSE', + 'data': [reply], + }) + + # send a message to the device + def get(self, resource, version=1, action="GET", data=None): + msg = { + "sID": self.session_id, + "msgID": self.tx_msg_id, + "resource": resource, + "version": version, + "action": action, + } + + if data is not None: + msg["data"] = [data] + + self.ws.send(msg) + self.tx_msg_id += 1 + + def handle_message(self, buf): + msg = json.loads(buf) + if self.debug: + print(now(), "RX:", msg) + sys.stdout.flush() + + + resource = msg["resource"] + action = msg["action"] + + values = {} + + if "code" in msg: + #print(now(), "ERROR", msg["code"]) + values = { + "error": msg["code"], + "resource": msg.get("resource", ''), + } + elif action == "POST": + if resource == "/ei/initialValues": + # this is the first message they send to us and + # establishes our session plus message ids + self.session_id = msg["sID"] + self.tx_msg_id = msg["data"][0]["edMsgID"] + + self.reply(msg, { + "deviceType": "Application", + "deviceName": self.device_name, + "deviceID": self.device_id, + }) + + # ask the device which services it supports + self.get("/ci/services") + + # the clothes washer wants this, the token doesn't matter, + # although they do not handle padding characters + # they send a response, not sure how to interpet it + token = base64url_encode(get_random_bytes(32)).decode('UTF-8') + token = re.sub(r'=', '', token) + self.get("/ci/authentication", version=2, data={"nonce": token}) + + self.get("/ci/info", version=2) # clothes washer + self.get("/iz/info") # dish washer + #self.get("/ci/tzInfo", version=2) + self.get("/ni/info") + #self.get("/ni/config", data={"interfaceID": 0}) + self.get("/ei/deviceReady", version=2, action="NOTIFY") + self.get("/ro/allDescriptionChanges") + self.get("/ro/allDescriptionChanges") + self.get("/ro/allMandatoryValues") + #self.get("/ro/values") + else: + print(now(), "Unknown resource", resource, file=sys.stderr) + + elif action == "RESPONSE" or action == "NOTIFY": + if resource == "/iz/info" or resource == "/ci/info": + # we could validate that this matches our machine + pass + + elif resource == "/ro/descriptionChange" \ + or resource == "/ro/allDescriptionChanges": + # we asked for these but don't know have to parse yet + pass + + elif resource == "/ni/info": + # we're already talking, so maybe we don't care? + pass + + elif resource == "/ro/allMandatoryValues" \ + or resource == "/ro/values": + values = self.parse_values(msg["data"]) + elif resource == "/ci/registeredDevices": + # we don't care + pass + + elif resource == "/ci/services": + self.services = {} + for service in msg["data"]: + self.services[service["service"]] = { + "version": service["version"], + } + #print(now(), "services", self.services) + + # we should figure out which ones to query now +# if "iz" in self.services: +# self.get("/iz/info", version=self.services["iz"]["version"]) +# if "ni" in self.services: +# self.get("/ni/info", version=self.services["ni"]["version"]) +# if "ei" in self.services: +# self.get("/ei/deviceReady", version=self.services["ei"]["version"], action="NOTIFY") + + #self.get("/if/info") + + else: + print(now(), "Unknown", msg) + + # return whatever we've parsed out of it + return values diff --git a/homeconnect2mqtt/docker/src/HCSocket.py b/homeconnect2mqtt/docker/src/HCSocket.py new file mode 100644 index 0000000..5b4fb3b --- /dev/null +++ b/homeconnect2mqtt/docker/src/HCSocket.py @@ -0,0 +1,170 @@ +# Create a websocket that wraps a connection to a +# Bosh-Siemens Home Connect device +import socket +import ssl +import sslpsk +import websocket +import sys +import json +import re +import time +import io +from base64 import urlsafe_b64decode as base64url +from datetime import datetime +from Crypto.Cipher import AES +from Crypto.Hash import HMAC, SHA256 +from Crypto.Random import get_random_bytes + +# Convience to compute an HMAC on a message +def hmac(key,msg): + mac = HMAC.new(key, msg=msg, digestmod=SHA256).digest() + return mac + +def now(): + return datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + +# Monkey patch for sslpsk in pip using the old _sslobj +def _sslobj(sock): + if (3, 5) <= sys.version_info <= (3, 7): + return sock._sslobj._sslobj + else: + return sock._sslobj +sslpsk.sslpsk._sslobj = _sslobj + + +class HCSocket: + def __init__(self, host, psk64, iv64=None): + self.host = host + self.psk = base64url(psk64 + '===') + self.debug = False + + if iv64: + # an HTTP self-encrypted socket + self.http = True + self.iv = base64url(iv64 + '===') + self.enckey = hmac(self.psk, b'ENC') + self.mackey = hmac(self.psk, b'MAC') + self.port = 80 + self.uri = "ws://" + host + ":80/homeconnect" + else: + self.http = False + self.port = 443 + self.uri = "wss://" + host + ":443/homeconnect" + + # don't connect automatically so that debug etc can be set + #self.reconnect() + + # restore the encryption state for a fresh connection + # this is only used by the HTTP connection + def reset(self): + if not self.http: + return + self.last_rx_hmac = bytes(16) + self.last_tx_hmac = bytes(16) + + self.aes_encrypt = AES.new(self.enckey, AES.MODE_CBC, self.iv) + self.aes_decrypt = AES.new(self.enckey, AES.MODE_CBC, self.iv) + + # hmac an inbound or outbound message, chaining the last hmac too + def hmac_msg(self, direction, enc_msg): + hmac_msg = self.iv + direction + enc_msg + return hmac(self.mackey, hmac_msg)[0:16] + + def decrypt(self,buf): + if len(buf) < 32: + print("Short message?", buf.hex(), file=sys.stderr) + return None + if len(buf) % 16 != 0: + print("Unaligned message? probably bad", buf.hex(), file=sys.stderr) + + # split the message into the encrypted message and the first 16-bytes of the HMAC + enc_msg = buf[0:-16] + their_hmac = buf[-16:] + + # compute the expected hmac on the encrypted message + our_hmac = self.hmac_msg(b'\x43' + self.last_rx_hmac, enc_msg) + + if their_hmac != our_hmac: + print("HMAC failure", their_hmac.hex(), our_hmac.hex(), file=sys.stderr) + return None + + self.last_rx_hmac = their_hmac + + # decrypt the message with CBC, so the last message block is mixed in + msg = self.aes_decrypt.decrypt(enc_msg) + + # check for padding and trim it off the end + pad_len = msg[-1] + if len(msg) < pad_len: + print("padding error?", msg.hex()) + return None + + return msg[0:-pad_len] + + def encrypt(self, clear_msg): + # convert the UTF-8 string into a byte array + clear_msg = bytes(clear_msg, 'utf-8') + + # pad the buffer, adding an extra block if necessary + pad_len = 16 - (len(clear_msg) % 16) + if pad_len == 1: + pad_len += 16 + pad = b'\x00' + get_random_bytes(pad_len-2) + bytearray([pad_len]) + + clear_msg = clear_msg + pad + + # encrypt the padded message with CBC, so there is chained + # state from the last cipher block sent + enc_msg = self.aes_encrypt.encrypt(clear_msg) + + # compute the hmac of the encrypted message, chaining the + # hmac of the previous message plus direction 'E' + self.last_tx_hmac = self.hmac_msg(b'\x45' + self.last_tx_hmac, enc_msg) + + # append the new hmac to the message + return enc_msg + self.last_tx_hmac + + def reconnect(self): + self.reset() + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((self.host,self.port)) + + if not self.http: + sock = sslpsk.wrap_socket( + sock, + ssl_version = ssl.PROTOCOL_TLSv1_2, + ciphers = 'ECDHE-PSK-CHACHA20-POLY1305', + psk = self.psk, + ) + + print(now(), "CON:", self.uri) + self.ws = websocket.WebSocket() + self.ws.connect(self.uri, + socket = sock, + origin = "", + ) + + def send(self, msg): + buf = json.dumps(msg, separators=(',', ':') ) + # swap " for ' + buf = re.sub("'", '"', buf) + if self.debug: + print(now(), "TX:", buf) + if self.http: + self.ws.send_binary(self.encrypt(buf)) + else: + self.ws.send(buf) + + def recv(self): + buf = self.ws.recv() + if buf is None or buf == "": + return None + + if self.http: + buf = self.decrypt(buf) + if buf is None: + return None + + if self.debug: + print(now(), "RX:", buf) + return buf diff --git a/homeconnect2mqtt/docker/src/hc2mqtt.py b/homeconnect2mqtt/docker/src/hc2mqtt.py new file mode 100644 index 0000000..9e153f9 --- /dev/null +++ b/homeconnect2mqtt/docker/src/hc2mqtt.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# Contact Bosh-Siemens Home Connect devices +# and connect their messages to the mqtt server +import json +import sys +import time +import os +import datetime +from threading import Thread + +import click +import paho.mqtt.client as mqtt + +from HCDevice import HCDevice +from HCSocket import HCSocket, now + +def restart_script(): + """Перезапускает скрипт""" + print(f"{now()} Перезапуск скрипта...") + python = sys.executable + os.execl(python, python, *sys.argv) + +@click.command() +@click.argument("config_file") +@click.option("-h", "--mqtt_host", default="localhost") +@click.option("-p", "--mqtt_prefix", default="homeconnect/") +@click.option("-u", "--mqtt_user", default="") +@click.option("-pw", "--mqtt_password", default="") +def hc2mqtt(config_file: str, mqtt_host: str, mqtt_prefix: str, mqtt_user: str, mqtt_password: str): + click.echo(f"Hello {config_file=} {mqtt_host=} {mqtt_prefix=} {mqtt_user=}") + + with open(config_file, "r") as f: + devices = json.load(f) + + client = mqtt.Client() + if mqtt_user != "" and mqtt_password != "": + client.username_pw_set(mqtt_user, mqtt_password) + client.connect(host=mqtt_host, port=1883, keepalive=70) + + for device in devices: + mqtt_topic = mqtt_prefix + device["name"] + thread = Thread(target=client_connect, args=(client, device, mqtt_topic)) + thread.start() + + last_restart = datetime.datetime.now() + + while True: + current_time = datetime.datetime.now() + # Проверяем, прошло ли 24 часа с последнего перезапуска + if (current_time - last_restart).total_seconds() >= 86400: # 86400 секунд = 24 часа + restart_script() + time.sleep(60) # Проверяем каждую минуту + client.loop(timeout=1.0) + + +# Map their value names to easier state names +topics = { + "OperationState": "state", + "DoorState": "door", + "RemainingProgramTime": "remaining", + "PowerState": "power", + "LowWaterPressure": "lowwaterpressure", + "AquaStopOccured": "aquastop", + "InternalError": "error", + "FatalErrorOccured": "error", + "MachineCareReminder": "machinecarereminder", + "ActiveProgram": "activeprogram" #8215 - machine care +} + + + +def client_connect(client, device, mqtt_topic): + host = device["host"] + + state = {} + for topic in topics: + state[topics[topic]] = None + + while True: + try: + ws = HCSocket(host, device["key"], device.get("iv",None)) + dev = HCDevice(ws, device.get("features", None)) + + ws.debug = True + ws.reconnect() + + while True: + msg = dev.recv() + if msg is None: + break + if len(msg) > 0: + print(now(), msg) + + update = False + for topic in topics: + value = msg.get(topic, None) + if value is None: + continue + + # Convert "On" to True, "Off" to False + if value == "On": + value = True + elif value == "Off": + value = False + + new_topic = topics[topic] + if new_topic == "remaining": + state["remainingseconds"] = value + value = "%d:%02d" % (value / 60 / 60, (value / 60) % 60) + + state[new_topic] = value + update = True + + if not update: + continue + + msg = json.dumps(state) + print("publish", mqtt_topic, msg) + client.publish(mqtt_topic + "/state", msg, retain=True) + + except Exception as e: + print("ERROR", host, e, file=sys.stderr) + + time.sleep(5) + +if __name__ == "__main__": + hc2mqtt(auto_envvar_prefix='HC') diff --git a/homeconnect2mqtt/docker/src/requirements.txt b/homeconnect2mqtt/docker/src/requirements.txt new file mode 100644 index 0000000..ff8a012 --- /dev/null +++ b/homeconnect2mqtt/docker/src/requirements.txt @@ -0,0 +1,9 @@ +bs4 +requests +pycryptodome +websocket-client +sslpsk +paho.mqtt +lxml +click +requests \ No newline at end of file diff --git a/homeconnect2mqtt/settings/homeconnect2mqtt.env.sample b/homeconnect2mqtt/settings/homeconnect2mqtt.env.sample new file mode 100644 index 0000000..0d17ff7 --- /dev/null +++ b/homeconnect2mqtt/settings/homeconnect2mqtt.env.sample @@ -0,0 +1,3 @@ +HC_MQTT_HOST=xxxxxxxxx +HC_MQTT_USER=xxxxxxxxx +HC_MQTT_PASSWORD=xxxxxxxxx diff --git a/keenetic/docker/Dockerfile b/keenetic/docker/Dockerfile new file mode 100644 index 0000000..c9abc66 --- /dev/null +++ b/keenetic/docker/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Установка зависимостей +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Копирование кода +COPY main.py . + +# Порт приложения +EXPOSE 8000 + +# Запуск +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/keenetic/docker/main.py b/keenetic/docker/main.py new file mode 100644 index 0000000..7a61d67 --- /dev/null +++ b/keenetic/docker/main.py @@ -0,0 +1,123 @@ +import os +import asyncssh +from fastapi import FastAPI, HTTPException, Depends, status +from fastapi.security import HTTPBasic, HTTPBasicCredentials +from pydantic import BaseModel +import secrets +import uvicorn + +app = FastAPI(title="Keenetic SSH Controller") +security = HTTPBasic() + +# --- КОНФИГУРАЦИЯ ИЗ ENV --- +ROUTER_HOST = os.getenv("ROUTER_HOST", "192.168.1.1") +ROUTER_PORT = int(os.getenv("ROUTER_PORT", "22")) +ROUTER_USER = os.getenv("ROUTER_USER", "admin") + +# SSH Auth +SSH_KEY_PATH = os.getenv("SSH_KEY_PATH") +SSH_PASSWORD = os.getenv("SSH_PASSWORD") + +# API Auth +API_USER = os.getenv("API_USER", "api_user") +API_PASS = os.getenv("API_PASS", "strong_password") + +# --- ЖЕСТКИЙ ВАЙТЛИСТ КОМАНД --- +# Разрешены только команды, начинающиеся с этих префиксов +COMMAND_WHITELIST = [ + "ip hotspot host", + "system configuration save" +] + +# Опасные символы, которые запрещены в любой команде +DANGEROUS_CHARS = [";", "|", "&", "$", "`", ">", "<", "\n", "\r", "!"] + +class CommandRequest(BaseModel): + command: str + +def get_current_user(credentials: HTTPBasicCredentials = Depends(security)): + correct_username = secrets.compare_digest(credentials.username, API_USER) + correct_password = secrets.compare_digest(credentials.password, API_PASS) + if not (correct_username and correct_password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect credentials", + headers={"WWW-Authenticate": "Basic"}, + ) + return credentials.username + +def validate_command(command: str) -> bool: + """ + Проверяет команду against жесткий вайтлист. + """ + if not command or not command.strip(): + return False + + command = command.strip() + + # 1. Проверка на опасные символы (защита от инъекций) + for char in DANGEROUS_CHARS: + if char in command: + return False + + # 2. Проверка по вайтлисту (префикс) + for allowed_pattern in COMMAND_WHITELIST: + if command.startswith(allowed_pattern): + return True + + return False + +async def execute_ssh_command(command: str): + # Валидация команды + if not validate_command(command): + raise HTTPException( + status_code=403, + detail=f"Command not allowed. Contact administrator to whitelist this command." + ) + + connect_kwargs = { + "host": ROUTER_HOST, + "port": ROUTER_PORT, + "username": ROUTER_USER, + "known_hosts": None + } + + if SSH_KEY_PATH and os.path.exists(SSH_KEY_PATH): + connect_kwargs["client_keys"] = [SSH_KEY_PATH] + elif SSH_PASSWORD: + connect_kwargs["password"] = SSH_PASSWORD + else: + raise HTTPException(status_code=500, detail="SSH Auth not configured (Key or Password required)") + + try: + async with asyncssh.connect(**connect_kwargs) as conn: + result = await conn.run(command) + return { + "stdout": result.stdout, + "stderr": result.stderr, + "exit_code": result.exit_status + } + except asyncssh.Error as exc: + raise HTTPException(status_code=503, detail=f"SSH Connection failed: {str(exc)}") + except Exception as exc: + raise HTTPException(status_code=500, detail=f"Internal error: {str(exc)}") + +@app.post("/run") +async def run_command(request: CommandRequest, username: str = Depends(get_current_user)): + return await execute_ssh_command(request.command) + +@app.get("/health") +async def health_check(): + return {"status": "ok"} + +@app.get("/allowed-commands") +async def get_allowed_commands(username: str = Depends(get_current_user)): + """ + Возвращает список разрешенных паттернов команд. + """ + return { + "whitelist": COMMAND_WHITELIST + } + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/keenetic/docker/requirements.txt b/keenetic/docker/requirements.txt new file mode 100644 index 0000000..ed7fd0a --- /dev/null +++ b/keenetic/docker/requirements.txt @@ -0,0 +1,4 @@ +fastapi +uvicorn +asyncssh +python-multipart \ No newline at end of file diff --git a/keenetic/settings/keenetic.env.sample b/keenetic/settings/keenetic.env.sample new file mode 100644 index 0000000..e4f69ee --- /dev/null +++ b/keenetic/settings/keenetic.env.sample @@ -0,0 +1,9 @@ +# Настройки роутера +ROUTER_HOST=xxxxxxxxx +ROUTER_PORT=xxxxxxxxx +ROUTER_USER=xxxxxxxxx +SSH_PASSWORD=xxxxxxxxx + +# Настройки API +API_USER=xxxxxxxxx +API_PASS=xxxxxxxxx diff --git a/matter-bridge/settings/matter-bridge.env.sample b/matter-bridge/settings/matter-bridge.env.sample new file mode 100644 index 0000000..fe44f98 --- /dev/null +++ b/matter-bridge/settings/matter-bridge.env.sample @@ -0,0 +1,4 @@ +HAMH_HOME_ASSISTANT_URL=xxxxxxxxx +HAMH_HOME_ASSISTANT_ACCESS_TOKEN=xxxxxxxxx +HAMH_LOG_LEVEL=xxxxxxxxx +HAMH_WEB_PORT=xxxxxxxxx diff --git a/mosquitto/docker/Dockerfile b/mosquitto/docker/Dockerfile new file mode 100644 index 0000000..7d26c65 --- /dev/null +++ b/mosquitto/docker/Dockerfile @@ -0,0 +1,8 @@ +FROM eclipse-mosquitto:1.6 + +ADD run.sh /run.sh + +RUN chmod +x /run.sh + +#CMD ["/run.sh"] +ENTRYPOINT ["/run.sh"] \ No newline at end of file diff --git a/mosquitto/docker/run.sh b/mosquitto/docker/run.sh new file mode 100644 index 0000000..9c417d1 --- /dev/null +++ b/mosquitto/docker/run.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +if [ "$MQTT_USERNAME" != "" ] && [ "$MQTT_PASSWORD" != "" ]; +then + sed -i -e 's/#allow_anonymous true/allow_anonymous false/g' /mosquitto/config/mosquitto.conf + sed -i -e 's/#password_file/password_file \/mosquitto\/passwd/g' /mosquitto/config/mosquitto.conf + touch /mosquitto/passwd + mosquitto_passwd -b /mosquitto/passwd $MQTT_USERNAME $MQTT_PASSWORD +fi +mosquitto -c /mosquitto/config/mosquitto.conf -v diff --git a/mosquitto/settings/mqtt.env.sample b/mosquitto/settings/mqtt.env.sample new file mode 100644 index 0000000..6d54f2c --- /dev/null +++ b/mosquitto/settings/mqtt.env.sample @@ -0,0 +1,2 @@ +MQTT_USERNAME=xxxxxxxxx +MQTT_PASSWORD=xxxxxxxxx \ No newline at end of file diff --git a/neptun2mqtt/docker/Dockerfile b/neptun2mqtt/docker/Dockerfile new file mode 100644 index 0000000..772b287 --- /dev/null +++ b/neptun2mqtt/docker/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.8-slim + +ADD ./src/requirements.txt /src/requirements.txt +RUN pip3 install -r /src/requirements.txt + +ADD ./src /src + +CMD ["python", "-u", "/src/neptun2mqtt.py"] diff --git a/neptun2mqtt/docker/src/neptun2mqtt.py b/neptun2mqtt/docker/src/neptun2mqtt.py new file mode 100644 index 0000000..eb5dac5 --- /dev/null +++ b/neptun2mqtt/docker/src/neptun2mqtt.py @@ -0,0 +1,131 @@ +from pymodbus.client import ModbusTcpClient, ModbusTlsClient, ModbusSerialClient +from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder +from pymodbus.constants import Endian + +import paho.mqtt.client as mqtt +import os +import time +import threading +import pymodbus +import time + +MQTT_SERVER = os.getenv('MQTT_SERVER', "") +MQTT_PORT = os.getenv('MQTT_PORT', 1883) +MQTT_USER = os.getenv('MQTT_USER', "") +MQTT_PASS = os.getenv('MQTT_PASS', "") +QUERY_TIME = os.getenv('QUERY_TIME', 60) +PREFIX = os.getenv('PREFIX', 'home/watercontrol') +NEPTUN_IP = os.getenv('NEPTUN_IP', '192.168.1.147') +NEPTUN_ID = os.getenv('NEPTUN_ID', 240) +COUNTERS_SLOT = int(os.getenv('NEPTUN_WATER_COUNTERS_SLOT', 2)) + +############################################################### +slot_config = {1: [107, 109], 2:[111, 113], 3:[115, 117], 4:[119, 121]} + +def prepare_mqtt(): + print("Connecting to MQTT server", MQTT_SERVER, ":", MQTT_PORT, "with username", MQTT_USER,":",MQTT_PASS) + client = mqtt.Client() + if (MQTT_USER != "" and MQTT_PASS != ""): + client.username_pw_set(MQTT_USER, MQTT_PASS) + client.connect(MQTT_SERVER, MQTT_PORT, 60) + + return client + +def push_data(client, model, sid, data): + for key, value in data.items(): + path = PATH_FMT.format(model=model, + sid=sid, + prop=key) + client.publish(path, payload=value, qos=0, retain=True) + +def on_mqtt_message(client, userdata, msg): + print(msg.topic+" "+str(msg.payload)) + parts = msg.topic.replace(PREFIX, "").strip("/").split("/") + print(parts) + if (len(parts) != 2): + return + if (parts[0] == 'valves' and parts[1] == 'set'): + value = msg.payload == b'ON' + print('value: {}'.format(value)) + + modbus = ModbusTcpClient(NEPTUN_IP, port=503, method='rtu') + response = modbus.read_holding_registers(address=0, count=1, slave=NEPTUN_ID) + decoder = BinaryPayloadDecoder.fromRegisters(response.registers, byteorder=Endian.Big, wordorder=Endian.Big) + bits = decoder.decode_bits() + bits[0] = value + bits[1] = value + builder = BinaryPayloadBuilder(wordorder=Endian.Big,byteorder=Endian.Big) + builder.add_bits(bits) + registers = builder.to_registers() + print("new payload: {}".format(registers)) + modbus.write_register(0, registers[0], slave=NEPTUN_ID) + modbus.close() + value_str = "OFF" + if value: + value_str = "ON" + client.publish(PREFIX + "/valves", payload=value_str, qos=0, retain=True) + +def on_connect(client, userdata, rc, kwargs): + client.subscribe(PREFIX + "/valves/set") + client.subscribe(PREFIX + "/counter3_1/set") + client.subscribe(PREFIX + "/counter3_2/set") + config = '{"~": "' + PREFIX + '/valves", "name": "watercontrol_valves", "device_class": "switch", "state_topic": "~", "command_topic":"~/set","unique_id": "watercontrol_neptun_' + NEPTUN_IP + '"}' + client.publish("homeassistant/switch/watercontrol/valves/config", payload=config, qos=0, retain=True) + +def refresh_loop(client): + while True: + try: + modbus = ModbusTcpClient(NEPTUN_IP, port=503, method='rtu') + response = modbus.read_holding_registers(address=0, count=1, slave=NEPTUN_ID) + decoder = BinaryPayloadDecoder.fromRegisters(response.registers, byteorder=Endian.Big, wordorder=Endian.Big) + bits = decoder.decode_bits() + valve1 = bits[0] + valve2 = bits[1] + print("config_decoded: {}, valve1: {}, valve2: {}".format(bits, valve1, valve2)) + value = "OFF" + if valve1 and valve2: + value = "ON" + client.publish(PREFIX + "/valves", payload=value, qos=0, retain=True) + + reg_config = slot_config[COUNTERS_SLOT] + reg_cold = reg_config[0] + reg_hot = reg_config[1] + + response = modbus.read_holding_registers(reg_cold, count=2, slave=NEPTUN_ID) + decoder = BinaryPayloadDecoder.fromRegisters(response.registers, byteorder=Endian.Big, wordorder=Endian.Big) + value = decoder.decode_32bit_uint()/1000 + print("counter_cold: {}".format(value)) + client.publish("{}/counter{}_1".format(PREFIX, COUNTERS_SLOT), payload=value, qos=0, retain=True) + + response = modbus.read_holding_registers(reg_hot, count=2, slave=NEPTUN_ID) + decoder = BinaryPayloadDecoder.fromRegisters(response.registers, byteorder=Endian.Big, wordorder=Endian.Big) + value = decoder.decode_32bit_uint()/1000 + print("counter_hot: {}".format(value)) + client.publish("{}/counter{}_2".format(PREFIX, COUNTERS_SLOT), payload=value, qos=0, retain=True) + + alert_status_response = modbus.read_holding_registers(address=3, count=1, slave=NEPTUN_ID) + decoder = BinaryPayloadDecoder.fromRegisters(alert_status_response.registers, byteorder=Endian.Big, wordorder=Endian.Big) + bits = decoder.decode_bits() + for i in range(1,5): + print("alert_{}: {}".format(i, bits[i])) + client.publish("{}/alert_{}".format(PREFIX, i), payload=bits[i-1], qos=0, retain=True) + + modbus.close() + except Exception as e: + print("ERROR", NEPTUN_IP, e) + time.sleep(QUERY_TIME) + +if __name__ == "__main__": + client = prepare_mqtt() + + client.on_message = on_mqtt_message + client.on_connect = on_connect + + #start thread for lamp refresh loop + t1 = threading.Thread(target=refresh_loop, args=[client]) + t1.start() + + # and process mqtt messages in this thread + client.loop_forever() + + diff --git a/neptun2mqtt/docker/src/requirements.txt b/neptun2mqtt/docker/src/requirements.txt new file mode 100644 index 0000000..d8a01c2 --- /dev/null +++ b/neptun2mqtt/docker/src/requirements.txt @@ -0,0 +1,2 @@ +paho-mqtt==1.5.0 +pymodbus==3.2.1 diff --git a/neptun2mqtt/settings/neptun.env.sample b/neptun2mqtt/settings/neptun.env.sample new file mode 100644 index 0000000..20a2ccb --- /dev/null +++ b/neptun2mqtt/settings/neptun.env.sample @@ -0,0 +1,4 @@ +MQTT_SERVER=xxxxxxxxx +MQTT_USER=xxxxxxxxx +MQTT_PASS=xxxxxxxxx +NEPTUN_IP=xxxxxxxxx \ No newline at end of file diff --git a/nginx/settings/site-confs/default.conf b/nginx/settings/site-confs/default.conf new file mode 100644 index 0000000..eed11f1 --- /dev/null +++ b/nginx/settings/site-confs/default.conf @@ -0,0 +1,29 @@ +server { + listen 80 default_server; + #listen 443 default ssl; + + server_name _; + + #ssl_certificate /config/keys/cert.crt; + #ssl_certificate_key /config/keys/cert.key; + + client_max_body_size 0; + + location ~/.well-known/acme-challenge/(.*)$ { + proxy_pass http://certbot; + } + + location / { + real_ip_header X-Forwarded-For; + set_real_ip_from 0.0.0.0/0; + + proxy_pass http://hass; + proxy_set_header Host $host; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Host $remote_addr; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } +} diff --git a/nginx/settings/site-confs/gitea.conf b/nginx/settings/site-confs/gitea.conf new file mode 100644 index 0000000..fab8d20 --- /dev/null +++ b/nginx/settings/site-confs/gitea.conf @@ -0,0 +1,39 @@ +upstream gitea { + server 192.168.1.6:3000; +} +upstream certbot2 { + server 192.168.1.6:8081; +} + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 80; + #listen 443 ssl; + server_name gitea.yandex5.ru; + + #ssl_certificate /config/keys/live/gitea.yandex5.ru/fullchain.pem; + #ssl_certificate_key /config/keys/live/gitea.yandex5.ru/privkey.pem; + + #access_log off; + location ~/.well-known/acme-challenge/(.*)$ { + proxy_pass http://certbot2; + } + + location / { + real_ip_header X-Forwarded-For; + set_real_ip_from 0.0.0.0/0; + + proxy_pass http://gitea; + proxy_set_header Host $host; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Host $remote_addr; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } +} diff --git a/nginx/settings/site-confs/home-fnv.conf b/nginx/settings/site-confs/home-fnv.conf new file mode 100644 index 0000000..9e563c7 --- /dev/null +++ b/nginx/settings/site-confs/home-fnv.conf @@ -0,0 +1,65 @@ +upstream hass { + server 192.168.1.6:8123; +} +upstream certbot { + server 192.168.1.6:8081; +} +upstream z2m { + server 192.168.1.6:8080; +} + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 80; + listen 443 ssl; + server_name home-fnv.yandex5.ru; + + ssl_certificate /config/keys/live/home-fnv.yandex5.ru/fullchain.pem; + ssl_certificate_key /config/keys/live/home-fnv.yandex5.ru/privkey.pem; + + #access_log off; + location ~/.well-known/acme-challenge/(.*)$ { + proxy_pass http://certbot; + } + + #access_log off; + location /z2m { + auth_basic "Administrator’s Area"; + auth_basic_user_file conf.d/z2m_htpasswd; + + proxy_pass http://192.168.1.6:8080/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /z2m/api { + auth_basic "Administrator’s Area"; + auth_basic_user_file conf.d/z2m_htpasswd; + + proxy_pass http://192.168.1.6:8080/api; + proxy_set_header Host $host; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location / { + real_ip_header X-Forwarded-For; + set_real_ip_from 0.0.0.0/0; + + proxy_pass http://hass; + proxy_set_header Host $host; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Host $remote_addr; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } +} \ No newline at end of file diff --git a/node-red/docker/Dockerfile b/node-red/docker/Dockerfile new file mode 100644 index 0000000..f845406 --- /dev/null +++ b/node-red/docker/Dockerfile @@ -0,0 +1,7 @@ +FROM nodered/node-red + +WORKDIR /usr/src/node-red +RUN npm install node-red-contrib-home-assistant-websocket +RUN npm install node-red-contrib-tuya-smart-device +RUN npm install node-red-node-base64 + diff --git a/node-red/settings/data/flows.json b/node-red/settings/data/flows.json new file mode 100644 index 0000000..4de5dea --- /dev/null +++ b/node-red/settings/data/flows.json @@ -0,0 +1,186 @@ +[ + { + "id": "32f338f62fe84201", + "type": "tab", + "label": "Doorbell", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "9d88782117d11324", + "type": "server", + "name": "Home Assistant", + "version": 5, + "addon": false, + "rejectUnauthorizedCerts": true, + "ha_boolean": "y|yes|true|on|home|open", + "connectionDelay": true, + "cacheJson": true, + "heartbeat": false, + "heartbeatInterval": "30", + "areaSelector": "friendlyName", + "deviceSelector": "friendlyName", + "entitySelector": "friendlyName", + "statusSeparator": ": ", + "statusYear": "hidden", + "statusMonth": "short", + "statusDay": "numeric", + "statusHourCycle": "default", + "statusTimeFormat": "h:m", + "enableGlobalContextStore": false + }, + { + "id": "bac739064a0752d3", + "type": "tuya-smart-device", + "z": "32f338f62fe84201", + "deviceName": "Doorbell", + "disableAutoStart": false, + "deviceId": "", + "deviceKey": "", + "storeAsCreds": true, + "deviceIp": "192.168.1.113", + "retryTimeout": 1000, + "findTimeout": 10000, + "tuyaVersion": "3.3", + "eventMode": "event-both", + "x": 120, + "y": 80, + "wires": [ + [ + "9d1686a6ee7e63cc", + "7e8087c473a88f87" + ], + [] + ] + }, + { + "id": "9d1686a6ee7e63cc", + "type": "function", + "z": "32f338f62fe84201", + "name": "Add timestamp to LOG", + "func": "var now = new Date();\n// Create formatted time\nvar yyyy = now.getFullYear();\nvar mm = now.getMonth() < 9 ? \"0\" + (now.getMonth() + 1) : (now.getMonth() + 1); // getMonth() is zero-based\nvar dd = now.getDate() < 10 ? \"0\" + now.getDate() : now.getDate();\nvar hh = now.getHours() < 10 ? \"0\" + now.getHours() : now.getHours();\nvar mmm = now.getMinutes() < 10 ? \"0\" + now.getMinutes() : now.getMinutes();\nvar ss = now.getSeconds() < 10 ? \"0\" + now.getSeconds() : now.getSeconds();\nvar time = yyyy + \"-\" + mm + \"-\" + dd + \" \" + hh + \":\" + mmm + \":\" + ss;\n\nmsg.payload = { Time: time, Message: msg.payload }\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 400, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "66848b1b1b258fcd", + "type": "inject", + "z": "32f338f62fe84201", + "name": "Fake message", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"data\":{\"dps\":{\"154\":\"aHR0cHM6Ly9yZWdpb3dlYmNhbS5kZS9maWxlYWRtaW4vdXNlcl91cGxvYWQvMDFfc3RvcmNoX2tpcmNoemFydGVuMjAyMS5qcGc/dmVyPTE2MzkzMDIyNjYzNDk=\"},\"t\":1664969089},\"deviceId\":\"bfd52cb7f15a171ef3qznk\",\"deviceName\":\"Dorbell pietro\"}", + "payloadType": "json", + "x": 160, + "y": 160, + "wires": [ + [ + "7e8087c473a88f87" + ] + ] + }, + { + "id": "7e8087c473a88f87", + "type": "switch", + "z": "32f338f62fe84201", + "name": "If contains picture", + "property": "payload.data.dps", + "propertyType": "msg", + "rules": [ + { + "t": "hask", + "v": "154", + "vt": "str" + }, + { + "t": "else" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 370, + "y": 160, + "wires": [ + [ + "ef4a301c9859d91a" + ], + [] + ] + }, + { + "id": "ef4a301c9859d91a", + "type": "change", + "z": "32f338f62fe84201", + "name": "Extract base64 adress", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "payload.data.dps[\"154\"]", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 600, + "y": 160, + "wires": [ + [ + "5936f72cfa385634" + ] + ] + }, + { + "id": "5936f72cfa385634", + "type": "base64", + "z": "32f338f62fe84201", + "name": "Decode base64 adress", + "action": "b64", + "property": "payload", + "x": 830, + "y": 160, + "wires": [ + [ + "e8ac1a2ea3533224" + ] + ] + }, + { + "id": "e8ac1a2ea3533224", + "type": "ha-fire-event", + "z": "32f338f62fe84201", + "name": "Fire Event in Hass", + "server": "9d88782117d11324", + "version": 0, + "event": "doorbell_call", + "data": "{\"doorbell_pic\":payload}", + "dataType": "jsonata", + "x": 1050, + "y": 160, + "wires": [ + [] + ] + } +] \ No newline at end of file diff --git a/node-red/settings/data/node-red-contrib-home-assistant-websocket.json b/node-red/settings/data/node-red-contrib-home-assistant-websocket.json new file mode 100644 index 0000000..f7a835f --- /dev/null +++ b/node-red/settings/data/node-red-contrib-home-assistant-websocket.json @@ -0,0 +1,3 @@ +{ + "nodes": {} +} \ No newline at end of file diff --git a/node-red/settings/data/settings.js b/node-red/settings/data/settings.js new file mode 100644 index 0000000..80b5590 --- /dev/null +++ b/node-red/settings/data/settings.js @@ -0,0 +1,560 @@ +/** + * This is the default settings file provided by Node-RED. + * + * It can contain any valid JavaScript code that will get run when Node-RED + * is started. + * + * Lines that start with // are commented out. + * Each entry should be separated from the entries above and below by a comma ',' + * + * For more information about individual settings, refer to the documentation: + * https://nodered.org/docs/user-guide/runtime/configuration + * + * The settings are split into the following sections: + * - Flow File and User Directory Settings + * - Security + * - Server Settings + * - Runtime Settings + * - Editor Settings + * - Node Settings + * + **/ + +module.exports = { + +/******************************************************************************* + * Flow File and User Directory Settings + * - flowFile + * - credentialSecret + * - flowFilePretty + * - userDir + * - nodesDir + ******************************************************************************/ + + /** The file containing the flows. If not set, defaults to flows_.json **/ + flowFile: 'flows.json', + + /** By default, credentials are encrypted in storage using a generated key. To + * specify your own secret, set the following property. + * If you want to disable encryption of credentials, set this property to false. + * Note: once you set this property, do not change it - doing so will prevent + * node-red from being able to decrypt your existing credentials and they will be + * lost. + */ + //credentialSecret: "a-secret-key", + + /** By default, the flow JSON will be formatted over multiple lines making + * it easier to compare changes when using version control. + * To disable pretty-printing of the JSON set the following property to false. + */ + flowFilePretty: true, + + /** By default, all user data is stored in a directory called `.node-red` under + * the user's home directory. To use a different location, the following + * property can be used + */ + //userDir: '/home/nol/.node-red/', + + /** Node-RED scans the `nodes` directory in the userDir to find local node files. + * The following property can be used to specify an additional directory to scan. + */ + //nodesDir: '/home/nol/.node-red/nodes', + +/******************************************************************************* + * Security + * - adminAuth + * - https + * - httpsRefreshInterval + * - requireHttps + * - httpNodeAuth + * - httpStaticAuth + ******************************************************************************/ + + /** To password protect the Node-RED editor and admin API, the following + * property can be used. See https://nodered.org/docs/security.html for details. + */ + //adminAuth: { + // type: "credentials", + // users: [{ + // username: "admin", + // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", + // permissions: "*" + // }] + //}, + + /** The following property can be used to enable HTTPS + * This property can be either an object, containing both a (private) key + * and a (public) certificate, or a function that returns such an object. + * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener + * for details of its contents. + */ + + /** Option 1: static object */ + //https: { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + //}, + + /** Option 2: function that returns the HTTP configuration object */ + // https: function() { + // // This function should return the options object, or a Promise + // // that resolves to the options object + // return { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + // } + // }, + + /** If the `https` setting is a function, the following setting can be used + * to set how often, in hours, the function will be called. That can be used + * to refresh any certificates. + */ + //httpsRefreshInterval : 12, + + /** The following property can be used to cause insecure HTTP connections to + * be redirected to HTTPS. + */ + //requireHttps: true, + + /** To password protect the node-defined HTTP endpoints (httpNodeRoot), + * including node-red-dashboard, or the static content (httpStatic), the + * following properties can be used. + * The `pass` field is a bcrypt hash of the password. + * See https://nodered.org/docs/security.html#generating-the-password-hash + */ + //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + +/******************************************************************************* + * Server Settings + * - uiPort + * - uiHost + * - apiMaxLength + * - httpServerOptions + * - httpAdminRoot + * - httpAdminMiddleware + * - httpNodeRoot + * - httpNodeCors + * - httpNodeMiddleware + * - httpStatic + * - httpStaticRoot + ******************************************************************************/ + + /** the tcp port that the Node-RED web server is listening on */ + uiPort: process.env.PORT || 1880, + + /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. + * To listen on all IPv6 addresses, set uiHost to "::", + * The following property can be used to listen on a specific interface. For + * example, the following would only allow connections from the local machine. + */ + //uiHost: "127.0.0.1", + + /** The maximum size of HTTP request that will be accepted by the runtime api. + * Default: 5mb + */ + //apiMaxLength: '5mb', + + /** The following property can be used to pass custom options to the Express.js + * server used by Node-RED. For a full list of available options, refer + * to http://expressjs.com/en/api.html#app.settings.table + */ + //httpServerOptions: { }, + + /** By default, the Node-RED UI is available at http://localhost:1880/ + * The following property can be used to specify a different root path. + * If set to false, this is disabled. + */ + //httpAdminRoot: '/admin', + + /** The following property can be used to add a custom middleware function + * in front of all admin http routes. For example, to set custom http + * headers. It can be a single function or an array of middleware functions. + */ + // httpAdminMiddleware: function(req,res,next) { + // // Set the X-Frame-Options header to limit where the editor + // // can be embedded + // //res.set('X-Frame-Options', 'sameorigin'); + // next(); + // }, + + + /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. + * By default, these are served relative to '/'. The following property + * can be used to specify a different root path. If set to false, this is + * disabled. + */ + //httpNodeRoot: '/red-nodes', + + /** The following property can be used to configure cross-origin resource sharing + * in the HTTP nodes. + * See https://github.com/troygoode/node-cors#configuration-options for + * details on its contents. The following is a basic permissive set of options: + */ + //httpNodeCors: { + // origin: "*", + // methods: "GET,PUT,POST,DELETE" + //}, + + /** If you need to set an http proxy please set an environment variable + * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. + * For example - http_proxy=http://myproxy.com:8080 + * (Setting it here will have no effect) + * You may also specify no_proxy (or NO_PROXY) to supply a comma separated + * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk + */ + + /** The following property can be used to add a custom middleware function + * in front of all http in nodes. This allows custom authentication to be + * applied to all http in nodes, or any other sort of common request processing. + * It can be a single function or an array of middleware functions. + */ + //httpNodeMiddleware: function(req,res,next) { + // // Handle/reject the request, or pass it on to the http in node by calling next(); + // // Optionally skip our rawBodyParser by setting this to true; + // //req.skipRawBodyParser = true; + // next(); + //}, + + /** When httpAdminRoot is used to move the UI to a different root path, the + * following property can be used to identify a directory of static content + * that should be served at http://localhost:1880/. + * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * to move httpAdminRoot + */ + //httpStatic: '/home/nol/node-red-static/', //single static source + /** + * OR multiple static sources can be created using an array of objects... + * Each object can also contain an options object for further configuration. + * See https://expressjs.com/en/api.html#express.static for available options. + */ + //httpStatic: [ + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, + // {path: '/home/nol/videos/', root: "/vid/", options: {maxAge: '1d'}} + //], + + /** + * All static routes will be appended to httpStaticRoot + * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" + * then "/home/nol/docs" will be served at "/static/" + * e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}] + * and httpStaticRoot = "/static/" + * then "/home/nol/pics/" will be served at "/static/img/" + */ + //httpStaticRoot: '/static/', + +/******************************************************************************* + * Runtime Settings + * - lang + * - runtimeState + * - diagnostics + * - logging + * - contextStorage + * - exportGlobalContextKeys + * - externalModules + ******************************************************************************/ + + /** Uncomment the following to run node-red in your preferred language. + * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko + * Some languages are more complete than others. + */ + // lang: "de", + + /** Configure diagnostics options + * - enabled: When `enabled` is `true` (or unset), diagnostics data will + * be available at http://localhost:1880/diagnostics + * - ui: When `ui` is `true` (or unset), the action `show-system-info` will + * be available to logged in users of node-red editor + */ + diagnostics: { + /** enable or disable diagnostics endpoint. Must be set to `false` to disable */ + enabled: true, + /** enable or disable diagnostics display in the node-red editor. Must be set to `false` to disable */ + ui: true, + }, + /** Configure runtimeState options + * - enabled: When `enabled` is `true` flows runtime can be Started/Stopped + * by POSTing to available at http://localhost:1880/flows/state + * - ui: When `ui` is `true`, the action `core:start-flows` and + * `core:stop-flows` will be available to logged in users of node-red editor + * Also, the deploy menu (when set to default) will show a stop or start button + */ + runtimeState: { + /** enable or disable flows/state endpoint. Must be set to `false` to disable */ + enabled: false, + /** show or hide runtime stop/start options in the node-red editor. Must be set to `false` to hide */ + ui: false, + }, + /** Configure the logging output */ + logging: { + /** Only console logging is currently supported */ + console: { + /** Level of logging to be recorded. Options are: + * fatal - only those errors which make the application unusable should be recorded + * error - record errors which are deemed fatal for a particular request + fatal errors + * warn - record problems which are non fatal + errors + fatal errors + * info - record information about the general running of the application + warn + error + fatal errors + * debug - record information which is more verbose than info + info + warn + error + fatal errors + * trace - record very detailed logging + debug + info + warn + error + fatal errors + * off - turn off all logging (doesn't affect metrics or audit) + */ + level: "info", + /** Whether or not to include metric events in the log output */ + metrics: false, + /** Whether or not to include audit events in the log output */ + audit: false + } + }, + + /** Context Storage + * The following property can be used to enable context storage. The configuration + * provided here will enable file-based context that flushes to disk every 30 seconds. + * Refer to the documentation for further options: https://nodered.org/docs/api/context/ + */ + //contextStorage: { + // default: { + // module:"localfilesystem" + // }, + //}, + + /** `global.keys()` returns a list of all properties set in global context. + * This allows them to be displayed in the Context Sidebar within the editor. + * In some circumstances it is not desirable to expose them to the editor. The + * following property can be used to hide any property set in `functionGlobalContext` + * from being list by `global.keys()`. + * By default, the property is set to false to avoid accidental exposure of + * their values. Setting this to true will cause the keys to be listed. + */ + exportGlobalContextKeys: false, + + /** Configure how the runtime will handle external npm modules. + * This covers: + * - whether the editor will allow new node modules to be installed + * - whether nodes, such as the Function node are allowed to have their + * own dynamically configured dependencies. + * The allow/denyList options can be used to limit what modules the runtime + * will install/load. It can use '*' as a wildcard that matches anything. + */ + externalModules: { + // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ + // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ + // palette: { /** Configuration for the Palette Manager */ + // allowInstall: true, /** Enable the Palette Manager in the editor */ + // allowUpdate: true, /** Allow modules to be updated in the Palette Manager */ + // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ + // allowList: ['*'], + // denyList: [], + // allowUpdateList: ['*'], + // denyUpdateList: [] + // }, + // modules: { /** Configuration for node-specified modules */ + // allowInstall: true, + // allowList: [], + // denyList: [] + // } + }, + + +/******************************************************************************* + * Editor Settings + * - disableEditor + * - editorTheme + ******************************************************************************/ + + /** The following property can be used to disable the editor. The admin API + * is not affected by this option. To disable both the editor and the admin + * API, use either the httpRoot or httpAdminRoot properties + */ + //disableEditor: false, + + /** Customising the editor + * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes + * for all available options. + */ + editorTheme: { + /** The following property can be used to set a custom theme for the editor. + * See https://github.com/node-red-contrib-themes/theme-collection for + * a collection of themes to chose from. + */ + //theme: "", + + /** To disable the 'Welcome to Node-RED' tour that is displayed the first + * time you access the editor for each release of Node-RED, set this to false + */ + //tours: false, + + palette: { + /** The following property can be used to order the categories in the editor + * palette. If a node's category is not in the list, the category will get + * added to the end of the palette. + * If not set, the following default order is used: + */ + //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], + }, + + projects: { + /** To enable the Projects feature, set this value to true */ + enabled: false, + workflow: { + /** Set the default projects workflow mode. + * - manual - you must manually commit changes + * - auto - changes are automatically committed + * This can be overridden per-user from the 'Git config' + * section of 'User Settings' within the editor + */ + mode: "manual" + } + }, + + codeEditor: { + /** Select the text editor component used by the editor. + * As of Node-RED V3, this defaults to "monaco", but can be set to "ace" if desired + */ + lib: "monaco", + options: { + /** The follow options only apply if the editor is set to "monaco" + * + * theme - must match the file name of a theme in + * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme + * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" + */ + // theme: "vs", + /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. + * for the full list, see https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IStandaloneEditorConstructionOptions.html + */ + //fontSize: 14, + //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", + //fontLigatures: true, + } + }, + + markdownEditor: { + mermaid: { + /** enable or disable mermaid diagram in markdown document + */ + enabled: true + } + }, + + }, + +/******************************************************************************* + * Node Settings + * - fileWorkingDirectory + * - functionGlobalContext + * - functionExternalModules + * - functionTimeout + * - nodeMessageBufferMaxLength + * - ui (for use with Node-RED Dashboard) + * - debugUseColors + * - debugMaxLength + * - execMaxBufferSize + * - httpRequestTimeout + * - mqttReconnectTime + * - serialReconnectTime + * - socketReconnectTime + * - socketTimeout + * - tcpMsgQueueSize + * - inboundWebSocketTimeout + * - tlsConfigDisableLocalFiles + * - webSocketNodeVerifyClient + ******************************************************************************/ + + /** The working directory to handle relative file paths from within the File nodes + * defaults to the working directory of the Node-RED process. + */ + //fileWorkingDirectory: "", + + /** Allow the Function node to load additional npm modules directly */ + functionExternalModules: true, + + /** Default timeout, in seconds, for the Function node. 0 means no timeout is applied */ + functionTimeout: 0, + + /** The following property can be used to set predefined values in Global Context. + * This allows extra node modules to be made available with in Function node. + * For example, the following: + * functionGlobalContext: { os:require('os') } + * will allow the `os` module to be accessed in a Function node using: + * global.get("os") + */ + functionGlobalContext: { + // os:require('os'), + }, + + /** The maximum number of messages nodes will buffer internally as part of their + * operation. This applies across a range of nodes that operate on message sequences. + * defaults to no limit. A value of 0 also means no limit is applied. + */ + //nodeMessageBufferMaxLength: 0, + + /** If you installed the optional node-red-dashboard you can set it's path + * relative to httpNodeRoot + * Other optional properties include + * readOnly:{boolean}, + * middleware:{function or array}, (req,res,next) - http middleware + * ioMiddleware:{function or array}, (socket,next) - socket.io middleware + */ + //ui: { path: "ui" }, + + /** Colourise the console output of the debug node */ + //debugUseColors: true, + + /** The maximum length, in characters, of any message sent to the debug sidebar tab */ + debugMaxLength: 1000, + + /** Maximum buffer size for the exec node. Defaults to 10Mb */ + //execMaxBufferSize: 10000000, + + /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ + //httpRequestTimeout: 120000, + + /** Retry time in milliseconds for MQTT connections */ + mqttReconnectTime: 15000, + + /** Retry time in milliseconds for Serial port connections */ + serialReconnectTime: 15000, + + /** Retry time in milliseconds for TCP socket connections */ + //socketReconnectTime: 10000, + + /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ + //socketTimeout: 120000, + + /** Maximum number of messages to wait in queue while attempting to connect to TCP socket + * defaults to 1000 + */ + //tcpMsgQueueSize: 2000, + + /** Timeout in milliseconds for inbound WebSocket connections that do not + * match any configured node. Defaults to 5000 + */ + //inboundWebSocketTimeout: 5000, + + /** To disable the option for using local files for storing keys and + * certificates in the TLS configuration node, set this to true. + */ + //tlsConfigDisableLocalFiles: true, + + /** The following property can be used to verify WebSocket connection attempts. + * This allows, for example, the HTTP request headers to be checked to ensure + * they include valid authentication information. + */ + //webSocketNodeVerifyClient: function(info) { + // /** 'info' has three properties: + // * - origin : the value in the Origin header + // * - req : the HTTP request + // * - secure : true if req.connection.authorized or req.connection.encrypted is set + // * + // * The function should return true if the connection should be accepted, false otherwise. + // * + // * Alternatively, if this function is defined to accept a second argument, callback, + // * it can be used to verify the client asynchronously. + // * The callback takes three arguments: + // * - result : boolean, whether to accept the connection or not + // * - code : if result is false, the HTTP error status to return + // * - reason: if result is false, the HTTP reason string to return + // */ + //}, +} diff --git a/postgres/settings/postgres.env.sample b/postgres/settings/postgres.env.sample new file mode 100644 index 0000000..0b0bf8b --- /dev/null +++ b/postgres/settings/postgres.env.sample @@ -0,0 +1,3 @@ +POSTGRES_USER=xxxxxxxxx +POSTGRES_PASSWORD=xxxxxxxxx +POSTGRES_DB=xxxxxxxxx \ No newline at end of file diff --git a/services/docker-wol.service b/services/docker-wol.service new file mode 100644 index 0000000..fca309b --- /dev/null +++ b/services/docker-wol.service @@ -0,0 +1,11 @@ +[Unit] +Description=Listen for wake-on-lan from docker via pipe + +[Service] +Restart=always +User=nobody +Group=nogroup +ExecStart=/usr/bin/wakeonlan -f /dev/hass-wolpipe + +[Install] +WantedBy=multi-user.target diff --git a/services/install.sh b/services/install.sh new file mode 100755 index 0000000..e0d9143 --- /dev/null +++ b/services/install.sh @@ -0,0 +1,10 @@ +#!/bin/bash +sudo apt install -y wakeonlan +sudo mkfifo /dev/hass-wolpipe + +sudo chmod 644 /dev/hass-wolpipe +sudo cp -R docker-wol.service /etc/systemd/system/docker-wol.service + +sudo systemctl daemon-reload +sudo systemctl start docker-wol +sudo systemctl enable docker-wol diff --git a/zigbee2mqtt/settings/DIYRuZ_FreePad.js b/zigbee2mqtt/settings/DIYRuZ_FreePad.js new file mode 100644 index 0000000..81a1420 --- /dev/null +++ b/zigbee2mqtt/settings/DIYRuZ_FreePad.js @@ -0,0 +1,132 @@ +const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); +const genMultistateInputClusterId = 18; + +const getKey = (object, value) => { + for (const key in object) { + if (object[key] == value) return key; + } +}; +const bind = async (endpoint, target, clusters) => { + for (const cluster of clusters) { + await endpoint.bind(cluster, target); + } +}; + +const fz = { + diyruz_freepad_clicks: { + cluster: 'genMultistateInput', + type: ['readResponse', 'attributeReport'], + convert: (model, msg, publish, options, meta) => { + const button = getKey(model.endpoint(msg.device), msg.endpoint.ID); + const lookup = { + 0: 'hold', + 1: 'single', + 2: 'double', + 3: 'triple', + 4: 'quadruple', + 255: 'release', + }; + const clicks = msg.data['presentValue']; + const action = lookup[clicks] ? lookup[clicks] : `many_${clicks}`; + return { + action: `${button}_${action}`, + }; + }, + }, +}; + +const tz = { + diyruz_freepad_on_off_config: { + key: ['switch_type', 'switch_actions'], + convertSet: async (entity, key, value, meta) => { + const switchTypesLookup = { + toggle: 0x00, + momentary: 0x01, + multifunction: 0x02, + }; + const switchActionsLookup = { + on: 0x00, + off: 0x01, + toggle: 0x02, + }; + const intVal = parseInt(value, 10); + const switchType = switchTypesLookup.hasOwnProperty(value) ? switchTypesLookup[value] : intVal; + const switchActions = switchActionsLookup.hasOwnProperty(value) ? switchActionsLookup[value] : intVal; + + const payloads = { + switch_type: { + switchType, + }, + switch_actions: { + switchActions, + }, + }; + + await entity.write('genOnOffSwitchCfg', payloads[key]); + }, + }, +}; + +const device = { + zigbeeModel: ['DIYRuZ_FreePad'], + model: 'DIYRuZ_FreePad', + vendor: 'DIYRuZ', + description: '[DiY 8/12/20 button keypad](http://modkam.ru/?p=1114)', + supports: 'single, double, triple, quadruple, many, hold/release', + fromZigbee: [fz.diyruz_freepad_clicks, zigbeeHerdsmanConverters.fromZigbeeConverters.battery], + toZigbee: [tz.diyruz_freepad_on_off_config, zigbeeHerdsmanConverters.toZigbeeConverters.factory_reset], + meta: { + configureKey: 1, + }, + configure: async (device, coordinatorEndpoint) => { + const endpoint = device.getEndpoint(1); + await bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); + if (device.applicationVersion < 3) { //Legacy PM2 firmwares + const payload = [{ + attribute: 'batteryPercentageRemaining', + minimumReportInterval: 0, + maximumReportInterval: 3600, + reportableChange: 0, + }, { + attribute: 'batteryVoltage', + minimumReportInterval: 0, + maximumReportInterval: 3600, + reportableChange: 0, + }]; + await endpoint.configureReporting('genPowerCfg', payload); + } + + + device.endpoints.forEach(async (ep) => { + if (ep.outputClusters.includes(genMultistateInputClusterId)) { + await bind(ep, coordinatorEndpoint, ['genMultistateInput']); + } + }); + }, + endpoint: (device) => { + return { + button_1: 1, + button_2: 2, + button_3: 3, + button_4: 4, + button_5: 5, + button_6: 6, + button_7: 7, + button_8: 8, + button_9: 9, + button_10: 10, + button_11: 11, + button_12: 12, + button_13: 13, + button_14: 14, + button_15: 15, + button_16: 16, + button_17: 17, + button_18: 18, + button_19: 19, + button_20: 20, + }; + }, +}; + +module.exports = device; \ No newline at end of file diff --git a/zigbee2mqtt/settings/configuration_backup_v1.yaml b/zigbee2mqtt/settings/configuration_backup_v1.yaml new file mode 100644 index 0000000..347b5cd --- /dev/null +++ b/zigbee2mqtt/settings/configuration_backup_v1.yaml @@ -0,0 +1,235 @@ +homeassistant: true +permit_join: false +frontend: + port: 8080 +experimental: + new_api: true +mqtt: + base_topic: z2m-tcp + server: mqtt://192.168.1.6:1883 + user: hass + password: mqttpass +serial: + port: tcp://192.168.1.132:6638 +advanced: + log_level: info + channel: 11 + pan_id: 6754 + last_seen: ISO_8601_local + homeassistant_legacy_entity_attributes: false + legacy_api: false + legacy_availability_payload: false + transmit_power: 20 +external_converters: + - ptvo_counter_2ch.js +device_options: + legacy: false +devices: + '0x00124b001f315241': + friendly_name: light_3 + '0x00124b001d3a3a12': + friendly_name: light_4 + '0xa4c138b6bab321de': + friendly_name: washer + '0xa4c138bc0d05b7bf': + friendly_name: light_hall_long + '0xa4c13809d74d5e1f': + friendly_name: dishwasher + '0x00124b001f2f63d9': + friendly_name: light_2 + '0x00124b001d644d03': + friendly_name: light_1 + '0x54ef4410000d6708': + friendly_name: brightness_bath + '0x54ef4410001ffbb1': + friendly_name: shower_light_sensor + '0x00158d00012d9ad7': + friendly_name: temperature_balcony + '0xa4c1388a7a97e475': + friendly_name: temperature_outside_bedroom + '0x00158d00089761d4': + friendly_name: temperature_bedroom + '0x54ef4410003138d4': + friendly_name: plug_water_heater + '0x000b3cfffefadaaa': + friendly_name: light_bedroom_2 + '0x086bd7fffe0042b0': + friendly_name: light_bedroom_7 + '0x00158d00035356c6': + friendly_name: button_test + '0x00158d000392b34f': + friendly_name: door_shower + '0x54ef441000673f2a': + friendly_name: radiator_kitchen + '0x54ef4410006fe989': + friendly_name: radiator_bedroom + '0x54ef4410006fe51e': + friendly_name: radiator_bedroom_balcony + '0x00158d000913e2e7': + friendly_name: temperature_mainroom + '0x00158d000944bbdf': + friendly_name: temperature_kitchen + '0xa4c1385ae827ec1c': + friendly_name: light_bath + '0x00158d00053f1e20': + friendly_name: curtain_mainroom + '0x00158d000398d9e7': + friendly_name: motion_bath + '0x00158d0003941e67': + friendly_name: door_bath + '0x00158d00079836a0': + friendly_name: curtain_kitchen + '0x000d6f0012060764': + friendly_name: smoke_detector_kitchen + '0x9035eafffe541813': + friendly_name: shower_mirror + '0x00158d0003969a5d': + friendly_name: door_locker_hall_left + '0x54ef4410001ff4ec': + friendly_name: brightness_hall + '0x00158d000398fa90': + friendly_name: motion_shower_center + '0x00158d00053f38ef': + friendly_name: curtain_bedroom + '0x00158d0002b0c46a': + friendly_name: curtain_bedroom_balcony + '0x00158d000398f9a1': + friendly_name: motion_hall_1 + '0x00158d000396c997': + friendly_name: door_kitchen + '0x00158d000396981f': + friendly_name: door_cloakroom + '0x00158d0003a10728': + friendly_name: door_childroom + '0x00158d000396c880': + friendly_name: door_bedroom + '0x00158d0003f1b8ee': + friendly_name: motion_childroom + '0x00158d00091c43c7': + friendly_name: temperature_bath + '0x00158d0008e1b6bc': + friendly_name: water_leak_bath_bath + '0x00158d00072a2198': + friendly_name: water_leak_bath_sink + '0x00158d0008e019d0': + friendly_name: water_leak_kitchen_fridge + '0x9035eafffe891816': + friendly_name: light_bath_mirror + '0x000d6f00131ca650': + friendly_name: smoke_detector_hall + '0x54ef4410002c8a61': + friendly_name: plug_server + '0x00158d0003a3a0f8': + friendly_name: button_shower_sink + '0x00158d0006ca2293': + friendly_name: water_leak_radiator_hub + '0xe0798dfffebca4cc': + friendly_name: switch_mirror_bath_heater + '0x54ef4410001952e3': + friendly_name: plug_yolka + '0x00158d000398de0b': + friendly_name: motion_hall_2 + '0x00158d00044bac2c': + friendly_name: motion_bath_2 + '0xe0798dfffec0fba2': + friendly_name: switch_mirror_shower_heater + '0xa4c138e184df9c4b': + friendly_name: light_mainroom_mainlamp + '0xa4c1382016d75a72': + friendly_name: light_mainroom_spotlights + '0xa4c13858fba9206a': + friendly_name: light_mainroom_pc_spotlight + '0xa4c138f30d13e363': + friendly_name: light_balcony_spotlight + '0xa4c13872c98a123d': + friendly_name: light_bedroom_nightlight_left + '0xa4c138ec247343ac': + friendly_name: light_bedroom_spotlights + '0xa4c138ebc36bcef7': + friendly_name: light_bedroom_pc_spotlight + '0xa4c138665085e34c': + friendly_name: light_bedroom_mainlight + '0xa4c138c4615096df': + friendly_name: light_bedroom_pc_nightlight + '0xa4c1381b0d3a90a8': + friendly_name: light_bedroom_nightlight_right + '0x54ef441000313804': + friendly_name: light_curtain_mainroom + '0x54ef44100031341f': + friendly_name: light_curtain_kitchen + '0xa4c138e15f46046e': + friendly_name: light_kitchen_table_lamp + '0xa4c138881ad5f2a7': + friendly_name: light_kitchen_bar_lamp + '0xa4c1389d59a26238': + friendly_name: light_kitchen_set + '0xa4c1385ff0c3da73': + friendly_name: light_kitchen_spotlights + '0xa4c138227e28f0f5': + friendly_name: light_childroom_main + '0xa4c138af504f84c7': + friendly_name: light_childroom_bed + '0xa4c13835b22ecd89': + friendly_name: light_childroom_spotlights + '0xa4c138a0cfb8e117': + friendly_name: ventilation_bath + '0xa4c1382b628aacb6': + friendly_name: light_bath_shelf + '0xa4c1385548a794f5': + friendly_name: ventilation_shower + '0xa4c138b789359794': + friendly_name: light_shower_main_spotlights + '0xa4c1385a75956001': + friendly_name: light_shower_shower_spotlights + '0xa4c138dbf32f44f8': + friendly_name: light_shower_shelf_light + '0x00158d000398f9f6': + friendly_name: motion_shower_shower + '0x00158d0003a106ea': + friendly_name: door_locker2_hall_right + '0x00158d0003536fac': + friendly_name: door_locker2_hall_left + '0x00158d0003a10585': + friendly_name: door_locker3_hall_left + '0xa4c138f709069275': + friendly_name: curtain_bedroom_bobbinet_balcony + '0x00158d00090e5cf7': + friendly_name: temperature_childroom + '0x00158d000396c7ab': + friendly_name: door_locker_hall_right + '0x00158d000396c934': + friendly_name: door_locker3_hall_right + '0x00158d0002113158': + friendly_name: water_leak_water_hub + '0xa4c1381710c7c92a': + friendly_name: curtain_mainroom_bobbinet + '0x54ef4410005d97df': + friendly_name: presence_shower + '0x00158d0003cc41a2': + friendly_name: motion_kitchenset_right + '0x00158d0000f6b2fa': + friendly_name: motion_kitchenset_left + '0xa4c1381257c87bdc': + friendly_name: curtain_kitchen_bobbinet + '0x00124b0022145c82': + friendly_name: co2_bedroom + '0x00158d00039d4fea': + friendly_name: door_entrance + '0x54ef4410006ff081': + friendly_name: radiator_mainroom + '0x54ef441000703fc3': + friendly_name: radiator_childroom + '0x00158d0008e038bc': + friendly_name: water_leak_shower_sink + '0x00158d00091c7eac': + friendly_name: temperature_winecooler + '0x00158d0003a5d05d': + friendly_name: lock_door + '0x00158d00090e6601': + friendly_name: temperature_shower +groups: {} +availability: + active: + timeout: 1440 + passive: + timeout: 1440 diff --git a/zigbee2mqtt/settings/configuration_backup_v2.yaml b/zigbee2mqtt/settings/configuration_backup_v2.yaml new file mode 100644 index 0000000..3fc65ee --- /dev/null +++ b/zigbee2mqtt/settings/configuration_backup_v2.yaml @@ -0,0 +1,227 @@ +homeassistant: true +frontend: + port: 8080 +mqtt: + base_topic: z2m-tcp + server: mqtt://192.168.1.6:1883 + user: hass + password: mqttpass +serial: + port: tcp://192.168.1.132:6638 +advanced: + log_level: info + channel: 11 + pan_id: 6754 + last_seen: ISO_8601_local + transmit_power: 20 +device_options: {} +devices: + '0x00124b001f315241': + friendly_name: light_3 + '0x00124b001d3a3a12': + friendly_name: light_4 + '0xa4c138b6bab321de': + friendly_name: washer + '0xa4c138bc0d05b7bf': + friendly_name: light_hall_long + '0xa4c13809d74d5e1f': + friendly_name: dishwasher + '0x00124b001f2f63d9': + friendly_name: light_2 + '0x00124b001d644d03': + friendly_name: light_1 + '0x54ef4410000d6708': + friendly_name: brightness_bath + '0x54ef4410001ffbb1': + friendly_name: shower_light_sensor + '0x00158d00012d9ad7': + friendly_name: temperature_balcony + '0xa4c1388a7a97e475': + friendly_name: temperature_outside_bedroom + '0x00158d00089761d4': + friendly_name: temperature_bedroom + '0x54ef4410003138d4': + friendly_name: plug_water_heater + '0x000b3cfffefadaaa': + friendly_name: light_bedroom_2 + '0x086bd7fffe0042b0': + friendly_name: light_bedroom_7 + '0x00158d00035356c6': + friendly_name: button_test + '0x00158d000392b34f': + friendly_name: door_shower + '0x54ef441000673f2a': + friendly_name: radiator_kitchen + '0x54ef4410006fe989': + friendly_name: radiator_bedroom + '0x54ef4410006fe51e': + friendly_name: radiator_bedroom_balcony + '0x00158d000913e2e7': + friendly_name: temperature_mainroom + '0x00158d000944bbdf': + friendly_name: temperature_kitchen + '0xa4c1385ae827ec1c': + friendly_name: light_bath + '0x00158d00053f1e20': + friendly_name: curtain_mainroom + '0x00158d000398d9e7': + friendly_name: motion_bath + '0x00158d0003941e67': + friendly_name: door_bath + '0x00158d00079836a0': + friendly_name: curtain_kitchen + '0x000d6f0012060764': + friendly_name: smoke_detector_kitchen + '0x9035eafffe541813': + friendly_name: shower_mirror + '0x00158d0003969a5d': + friendly_name: door_locker_hall_left + '0x54ef4410001ff4ec': + friendly_name: brightness_hall + '0x00158d000398fa90': + friendly_name: motion_shower_center + '0x00158d00053f38ef': + friendly_name: curtain_bedroom + '0x00158d0002b0c46a': + friendly_name: curtain_bedroom_balcony + '0x00158d000398f9a1': + friendly_name: motion_hall_1 + '0x00158d000396c997': + friendly_name: door_kitchen + '0x00158d000396981f': + friendly_name: door_cloakroom + '0x00158d0003a10728': + friendly_name: door_childroom + '0x00158d000396c880': + friendly_name: door_bedroom + '0x00158d0003f1b8ee': + friendly_name: motion_childroom + '0x00158d00091c43c7': + friendly_name: temperature_bath + '0x00158d0008e1b6bc': + friendly_name: water_leak_bath_bath + '0x00158d00072a2198': + friendly_name: water_leak_bath_sink + '0x00158d0008e019d0': + friendly_name: water_leak_kitchen_fridge + '0x9035eafffe891816': + friendly_name: light_bath_mirror + '0x000d6f00131ca650': + friendly_name: smoke_detector_hall + '0x54ef4410002c8a61': + friendly_name: plug_server + '0x00158d0003a3a0f8': + friendly_name: button_shower_sink + '0x00158d0006ca2293': + friendly_name: water_leak_radiator_hub + '0xe0798dfffebca4cc': + friendly_name: switch_mirror_bath_heater + '0x54ef4410001952e3': + friendly_name: plug_yolka + '0x00158d000398de0b': + friendly_name: motion_hall_2 + '0x00158d00044bac2c': + friendly_name: motion_bath_2 + '0xe0798dfffec0fba2': + friendly_name: switch_mirror_shower_heater + '0xa4c138e184df9c4b': + friendly_name: light_mainroom_mainlamp + '0xa4c1382016d75a72': + friendly_name: light_mainroom_spotlights + '0xa4c13858fba9206a': + friendly_name: light_mainroom_pc_spotlight + '0xa4c138f30d13e363': + friendly_name: light_balcony_spotlight + '0xa4c13872c98a123d': + friendly_name: light_bedroom_nightlight_left + '0xa4c138ec247343ac': + friendly_name: light_bedroom_spotlights + '0xa4c138ebc36bcef7': + friendly_name: light_bedroom_pc_spotlight + '0xa4c138665085e34c': + friendly_name: light_bedroom_mainlight + '0xa4c138c4615096df': + friendly_name: light_bedroom_pc_nightlight + '0xa4c1381b0d3a90a8': + friendly_name: light_bedroom_nightlight_right + '0x54ef441000313804': + friendly_name: light_curtain_mainroom + '0x54ef44100031341f': + friendly_name: light_curtain_kitchen + '0xa4c138e15f46046e': + friendly_name: light_kitchen_table_lamp + '0xa4c138881ad5f2a7': + friendly_name: light_kitchen_bar_lamp + '0xa4c1389d59a26238': + friendly_name: light_kitchen_set + '0xa4c1385ff0c3da73': + friendly_name: light_kitchen_spotlights + '0xa4c138227e28f0f5': + friendly_name: light_childroom_main + '0xa4c138af504f84c7': + friendly_name: light_childroom_bed + '0xa4c13835b22ecd89': + friendly_name: light_childroom_spotlights + '0xa4c138a0cfb8e117': + friendly_name: ventilation_bath + '0xa4c1382b628aacb6': + friendly_name: light_bath_shelf + '0xa4c1385548a794f5': + friendly_name: ventilation_shower + '0xa4c138b789359794': + friendly_name: light_shower_main_spotlights + '0xa4c1385a75956001': + friendly_name: light_shower_shower_spotlights + '0xa4c138dbf32f44f8': + friendly_name: light_shower_shelf_light + '0x00158d000398f9f6': + friendly_name: motion_shower_shower + '0x00158d0003a106ea': + friendly_name: door_locker2_hall_right + '0x00158d0003536fac': + friendly_name: door_locker2_hall_left + '0x00158d0003a10585': + friendly_name: door_locker3_hall_left + '0xa4c138f709069275': + friendly_name: curtain_bedroom_bobbinet_balcony + '0x00158d00090e5cf7': + friendly_name: temperature_childroom + '0x00158d000396c7ab': + friendly_name: door_locker_hall_right + '0x00158d000396c934': + friendly_name: door_locker3_hall_right + '0x00158d0002113158': + friendly_name: water_leak_water_hub + '0xa4c1381710c7c92a': + friendly_name: curtain_mainroom_bobbinet + '0x54ef4410005d97df': + friendly_name: presence_shower + '0x00158d0003cc41a2': + friendly_name: motion_kitchenset_right + '0x00158d0000f6b2fa': + friendly_name: motion_kitchenset_left + '0xa4c1381257c87bdc': + friendly_name: curtain_kitchen_bobbinet + '0x00124b0022145c82': + friendly_name: co2_bedroom + '0x00158d00039d4fea': + friendly_name: door_entrance + '0x54ef4410006ff081': + friendly_name: radiator_mainroom + '0x54ef441000703fc3': + friendly_name: radiator_childroom + '0x00158d0008e038bc': + friendly_name: water_leak_shower_sink + '0x00158d00091c7eac': + friendly_name: temperature_winecooler + '0x00158d0003a5d05d': + friendly_name: lock_door + '0x00158d00090e6601': + friendly_name: temperature_shower +groups: {} +availability: + active: + timeout: 1440 + passive: + timeout: 1440 +version: 2 diff --git a/zigbee2mqtt/settings/configuration_backup_v3.yaml b/zigbee2mqtt/settings/configuration_backup_v3.yaml new file mode 100644 index 0000000..99b9335 --- /dev/null +++ b/zigbee2mqtt/settings/configuration_backup_v3.yaml @@ -0,0 +1,230 @@ +homeassistant: + enabled: true +frontend: + enabled: true + port: 8080 +mqtt: + base_topic: z2m-tcp + server: mqtt://192.168.1.6:1883 + user: hass + password: mqttpass +serial: + port: tcp://192.168.1.132:6638 +advanced: + log_level: info + channel: 11 + pan_id: 6754 + last_seen: ISO_8601_local + transmit_power: 20 +device_options: {} +devices: + '0x00124b001f315241': + friendly_name: light_3 + '0x00124b001d3a3a12': + friendly_name: light_4 + '0xa4c138b6bab321de': + friendly_name: washer + '0xa4c138bc0d05b7bf': + friendly_name: light_hall_long + '0xa4c13809d74d5e1f': + friendly_name: dishwasher + '0x00124b001f2f63d9': + friendly_name: light_2 + '0x00124b001d644d03': + friendly_name: light_1 + '0x54ef4410000d6708': + friendly_name: brightness_bath + '0x54ef4410001ffbb1': + friendly_name: shower_light_sensor + '0x00158d00012d9ad7': + friendly_name: temperature_balcony + '0xa4c1388a7a97e475': + friendly_name: temperature_outside_bedroom + '0x00158d00089761d4': + friendly_name: temperature_bedroom + '0x54ef4410003138d4': + friendly_name: plug_water_heater + '0x000b3cfffefadaaa': + friendly_name: light_bedroom_2 + '0x086bd7fffe0042b0': + friendly_name: light_bedroom_7 + '0x00158d00035356c6': + friendly_name: button_test + '0x00158d000392b34f': + friendly_name: door_shower + '0x54ef441000673f2a': + friendly_name: radiator_kitchen + '0x54ef4410006fe989': + friendly_name: radiator_bedroom + '0x54ef4410006fe51e': + friendly_name: radiator_bedroom_balcony + '0x00158d000913e2e7': + friendly_name: temperature_mainroom + '0x00158d000944bbdf': + friendly_name: temperature_kitchen + '0xa4c1385ae827ec1c': + friendly_name: light_bath + '0x00158d00053f1e20': + friendly_name: curtain_mainroom + '0x00158d000398d9e7': + friendly_name: motion_bath + '0x00158d0003941e67': + friendly_name: door_bath + '0x00158d00079836a0': + friendly_name: curtain_kitchen + '0x000d6f0012060764': + friendly_name: smoke_detector_kitchen + '0x9035eafffe541813': + friendly_name: shower_mirror + '0x00158d0003969a5d': + friendly_name: door_locker_hall_left + '0x54ef4410001ff4ec': + friendly_name: brightness_hall + '0x00158d000398fa90': + friendly_name: motion_shower_center + '0x00158d00053f38ef': + friendly_name: curtain_bedroom + '0x00158d0002b0c46a': + friendly_name: curtain_bedroom_balcony + '0x00158d000398f9a1': + friendly_name: motion_hall_1 + '0x00158d000396c997': + friendly_name: door_kitchen + '0x00158d000396981f': + friendly_name: door_cloakroom + '0x00158d0003a10728': + friendly_name: door_childroom + '0x00158d000396c880': + friendly_name: door_bedroom + '0x00158d0003f1b8ee': + friendly_name: motion_childroom + '0x00158d00091c43c7': + friendly_name: temperature_bath + '0x00158d0008e1b6bc': + friendly_name: water_leak_bath_bath + '0x00158d00072a2198': + friendly_name: water_leak_bath_sink + '0x00158d0008e019d0': + friendly_name: water_leak_kitchen_fridge + '0x9035eafffe891816': + friendly_name: light_bath_mirror + '0x000d6f00131ca650': + friendly_name: smoke_detector_hall + '0x54ef4410002c8a61': + friendly_name: plug_server + '0x00158d0003a3a0f8': + friendly_name: button_shower_sink + '0x00158d0006ca2293': + friendly_name: water_leak_radiator_hub + '0xe0798dfffebca4cc': + friendly_name: switch_mirror_bath_heater + '0x54ef4410001952e3': + friendly_name: plug_yolka + '0x00158d000398de0b': + friendly_name: motion_hall_2 + '0x00158d00044bac2c': + friendly_name: motion_bath_2 + '0xe0798dfffec0fba2': + friendly_name: switch_mirror_shower_heater + '0xa4c138e184df9c4b': + friendly_name: light_mainroom_mainlamp + '0xa4c1382016d75a72': + friendly_name: light_mainroom_spotlights + '0xa4c13858fba9206a': + friendly_name: light_mainroom_pc_spotlight + '0xa4c138f30d13e363': + friendly_name: light_balcony_spotlight + '0xa4c13872c98a123d': + friendly_name: light_bedroom_nightlight_left + '0xa4c138ec247343ac': + friendly_name: light_bedroom_spotlights + '0xa4c138ebc36bcef7': + friendly_name: light_bedroom_pc_spotlight + '0xa4c138665085e34c': + friendly_name: light_bedroom_mainlight + '0xa4c138c4615096df': + friendly_name: light_bedroom_pc_nightlight + '0xa4c1381b0d3a90a8': + friendly_name: light_bedroom_nightlight_right + '0x54ef441000313804': + friendly_name: light_curtain_mainroom + '0x54ef44100031341f': + friendly_name: light_curtain_kitchen + '0xa4c138e15f46046e': + friendly_name: light_kitchen_table_lamp + '0xa4c138881ad5f2a7': + friendly_name: light_kitchen_bar_lamp + '0xa4c1389d59a26238': + friendly_name: light_kitchen_set + '0xa4c1385ff0c3da73': + friendly_name: light_kitchen_spotlights + '0xa4c138227e28f0f5': + friendly_name: light_childroom_main + '0xa4c138af504f84c7': + friendly_name: light_childroom_bed + '0xa4c13835b22ecd89': + friendly_name: light_childroom_spotlights + '0xa4c138a0cfb8e117': + friendly_name: ventilation_bath + '0xa4c1382b628aacb6': + friendly_name: light_bath_shelf + '0xa4c1385548a794f5': + friendly_name: ventilation_shower + '0xa4c138b789359794': + friendly_name: light_shower_main_spotlights + '0xa4c1385a75956001': + friendly_name: light_shower_shower_spotlights + '0xa4c138dbf32f44f8': + friendly_name: light_shower_shelf_light + '0x00158d000398f9f6': + friendly_name: motion_shower_shower + '0x00158d0003a106ea': + friendly_name: door_locker2_hall_right + '0x00158d0003536fac': + friendly_name: door_locker2_hall_left + '0x00158d0003a10585': + friendly_name: door_locker3_hall_left + '0xa4c138f709069275': + friendly_name: curtain_bedroom_bobbinet_balcony + '0x00158d00090e5cf7': + friendly_name: temperature_childroom + '0x00158d000396c7ab': + friendly_name: door_locker_hall_right + '0x00158d000396c934': + friendly_name: door_locker3_hall_right + '0x00158d0002113158': + friendly_name: water_leak_water_hub + '0xa4c1381710c7c92a': + friendly_name: curtain_mainroom_bobbinet + '0x54ef4410005d97df': + friendly_name: presence_shower + '0x00158d0003cc41a2': + friendly_name: motion_kitchenset_right + '0x00158d0000f6b2fa': + friendly_name: motion_kitchenset_left + '0xa4c1381257c87bdc': + friendly_name: curtain_kitchen_bobbinet + '0x00124b0022145c82': + friendly_name: co2_bedroom + '0x00158d00039d4fea': + friendly_name: door_entrance + '0x54ef4410006ff081': + friendly_name: radiator_mainroom + '0x54ef441000703fc3': + friendly_name: radiator_childroom + '0x00158d0008e038bc': + friendly_name: water_leak_shower_sink + '0x00158d00091c7eac': + friendly_name: temperature_winecooler + '0x00158d0003a5d05d': + friendly_name: lock_door + '0x00158d00090e6601': + friendly_name: temperature_shower +groups: {} +availability: + enabled: true + active: + timeout: 1440 + passive: + timeout: 1440 +version: 3 diff --git a/zigbee2mqtt/settings/ptvo_counter_2ch.js b/zigbee2mqtt/settings/ptvo_counter_2ch.js new file mode 100644 index 0000000..f404f3e --- /dev/null +++ b/zigbee2mqtt/settings/ptvo_counter_2ch.js @@ -0,0 +1,37 @@ +const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); + +const exposes = zigbeeHerdsmanConverters.exposes; +const ea = exposes.access; +const e = exposes.presets; +const fz = zigbeeHerdsmanConverters.fromZigbeeConverters; +const tz = zigbeeHerdsmanConverters.toZigbeeConverters; + +const ptvo_switch = zigbeeHerdsmanConverters.findByDevice({modelID: 'ptvo.switch'}); +fz.legacy = ptvo_switch.meta.tuyaThermostatPreset; + +const device = { + zigbeeModel: ['ptvo_counter_2ch'], + model: 'ptvo_counter_2ch', + vendor: 'Custom devices (DiY)', + description: '[Configurable firmware](https://ptvo.info/zigbee-configurable-firmware-features/)', + fromZigbee: [fz.ignore_basic_report, fz.battery, fz.ptvo_switch_analog_input, fz.on_off,], + toZigbee: [tz.ptvo_switch_trigger, tz.ptvo_switch_analog_input, tz.on_off,], + exposes: [e.battery(), + exposes.numeric('l3', ea.STATE_SET).withDescription('Counter value. Write zero or positive value to set a counter value. Write a negative value to set a wakeup interval in minutes'), + exposes.numeric('l5', ea.STATE_SET).withDescription('Counter value. Write zero or positive value to set a counter value. Write a negative value to set a wakeup interval in minutes'), + e.switch().withEndpoint('l6'), + e.battery_voltage(), +], + meta: { + multiEndpoint: true, + + }, + endpoint: (device) => { + return { + l3: 3, l5: 5, l6: 6, + }; + }, + icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAYAAAA8AXHiAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAG5ISURBVHhe7b0HnF1XeS2+bq9zp/c+mhmNqi03uclywQbbYNxUbAyBQEgg+b28l7z08E8BEhJSXhJISGihW5KNHao7Nu5qVpdGbUbT+9yZ2/t/rX3mygIMGHfDfPLxuXPuuafsvc761vftcrBoi7Zoi7Zoi7Zoi7Zoi7Zoi7Zoi7Zoi7Zoi/YmMNvCetHe4PbFL/zXBf39/XcODw/7vF4vli/tea6srOz/3f6e9z2wsMsbyhaB9Sawj/3lR/7pu/c/8DsHDx506W+73Y6A34sLL7ywcMkllzzX1NT0906b/a6bN96WMz94A9gisN7g9nt/9Pv/584vf+2fJmfCQCHHCisgXwCcDjs8Hg98Xg/OO+88rLv44qfa2tq+4HA47rx187tiCz9/3WwRWG8wu/OrXyo7evTonwdCwWdqKhqe+crWO0/ue26Xq8Tvh9tTglQ6g1gsjGgiglQyZX7jdrtR4vNizZo1uOiii57o6ur6mMvleuyWTbcnzQ6vgy0C6w1k//h3f+Pr6+u7f8eOHesuu/KKv1jW3PbYjr27H21qbkRFZQnc3hCyuTzGxwZwcP9h7H5uH8YmppAgwBx2mwGY3x/AWatXJq68/PLhltaWD3m83uO3bLitf+EUr5ktAusNYv/56X+95uGHH/7X7du3L7300kv33HjNZR9OTw1/JppJrA7ULCFw3PDkZ5GOTMPlr0Y+UI7B0Ql894GHceDQEUxPTqFQKICuEC6nA4GAFytWrIiuX7/+8e6urnt8Hu+Xb7x1k0Vxr4EtAusNYB//64/8+g8f/cG/79qz15PJFvD7H3zXdEV2PJTPJF31S8+Cwx1EbvY4MjPHYY+NIF+/Do7WdXCXhOB2efDNu+/CA4/8EDNTU0ilUgQYCLAFDebz4dw1Z+HiCy+8v7Oz8xuMKB+7acPmV53BHAvrRXsd7L+/+Fl3c1PDR7/3ve99cu++g85oLIGGkANtvoi/zD7vaGyohjMXQ2JgF1zZaZS5bUimsvC1r4ONrOVwOjE3P4eGunp4PQ6MjI4hkUghl8txySOTySJNTTYyPIznntvdOTwycqPNbq//7Q9/qPn22zdv37L1LkLw1bFFxnqd7O8+8fHg/gMHvv3E449fPkpA5HMFNNfX4OZr12FFexW8BJTdZofD5YLfWUB0pg/5uVHYyrrgW/o22P2lSMRmEZ2bRYbgKVBj3f/os3jksacQmZ9n5Pg8ZvgV7GQwn9dnGGzV6hVYu3btQ6tXrDzssNn+9tbb7hhd2PUVs0VgvQ5GkX7ZQw8//Pmdz+3pDIfnkM/nsaK7A9dffiE6muvgDZbA63ISEDbYnC4DEkd0FPH+J1DWcSlQ2oTE9ClERnuRCo8jZ/MitPJqJN2V+NR/fhGHDx5EJvujKS3lvoprj9dJke/DymUrcM1b3pJqaW7+XYr8LzOKTJidXgFbBNZrZPfetcVBcV3yxBNPvJvLx3qPHgvNx5Kgzsb5552Ft119JRqqqxDwelkpohg73PYc4pEwHN4S+NxALjwAW3QKkbF+JKITCHpcSGdt8NZ0ou6izXjuYC/2HTqGe7/9XcyTtSTmX8iEMWIWLpcbgWAAy3uW4Yr1lx3vWtLx23e87zdekUz+IrBeI/viFz7n3rd37wP333ff+oHBQSSTKTjdHly+/lJcecVlqKqsoE7ywUPXZ2e1FGwFpBNRFHIZuHxB2LylcKXnkBjcD8wPY54g89gdKK3thrtuOQ4OTiOWTCNKjfWd792P3hN9PxVYlhUsF0l3G2IEGQz4sbRrSXbt2vOfWL3q3E+7nK5v3Xj7HemFnX9hWwTWa2B/9/GPdT+7ffs3n3zmmRXRSJSgSiLAirz2+rfhwgsuQFkoRPfkg83hhIN04hCwkKcLzJm/9V0+lYB+m85mqcfycMwNoM6dQJrgOjSeQSbnNEI9Gotj/5GjePSZnUbE/zi49LdTOS+nHSU+D0HlI6A9qKgsRXllOaamJ1FdWY9LL1s/2NzW8XsOj2fHLbfefmrh5y/aFoH1Ktju7c+6z7lgbfqbd29x/+ChH9z82GOP/fvRY8fK06xoFXllZSXe8Y63U0QvQ3lpOVnKTVARTgKVdBWxIGBpWyhUilg0wuhvHjnqpnwuy2NQ6NdWY3bPAxga6AequxFL2xgRCnzzGBibxENP7zCpBxcZUNl65SDk/uRmy8hQZQQ2skl46Yrz/MJDkHUvX4qVq1digsHErj37UV7bGF23/srdS9qW/BHF//ZbN92RNzf4ImwRWK+CEVg+Aivxvvf92n9++3++/f7ZmVlHgSUt9mluasQ7b3gHli5dCl/Qb3JNNlasRHWBOxU/O7h/RXk5wuGw0UtZMpUW8c/Snh6MnTiAY49+G76KcsTtZdRcccQIrNnwPKbmI3hi916e0A0nQRuenYWTAYKHiPV5vPA4bUhlsnC7XfC5bCB+4fW64KK+KzhcWLNyKYKhMnznwUeRyNlxzpo1ifXr1z/Q0tLyKYLrIesuf7YtAutVsj/8/d+77L+//LVHpmdnHLl81oDqrNVrcP11b0VzcwOjMi8r1GWApGy5qqLotZQSqKqowOjIMKLRqIkaBSoBrrGhHmOjI5gZOYVUZBqpAmVQLEsXmMDA8Bhm5iJIcd/Dp4ZRsHvhjc1iabkNHVVeVAUJNFsekXgBo9E0xjN2RG0ulJI125rr0UftF0lmkM2k6aqDmEtk0D86ZZi0pKQEl1122cjb3/72TwW8vs/xMhM3btgcta74J20xQfoqWU931//es3f/xel0EiVeN65bdyneed01qK2uhJ/AcZEtbNRHAovAlVeXBZoqsKqqCkOqZLk/uk8By+50oYbbB/qOY3Zq3OSu5FlTqSQK8SjGJyZx5OgxTM3MYS6egZP6rM0ZwfVdHqxv82J5lR0dZQW0ltnQWQUsr/eio8IPL6+vb2wGsZwDNc3tGBwYwHyU2o3HdzjdBGECEQYEWbrhUwMDJalk8qquzs6DdLG779z20xOsi8B6lYzMcn1/36m1pR4Hblx7Ps4KlsB1sg+u6VkEfX7YykqNhpKpdgQuv99v9FdfXx8S8bgR2oUCtRa/qyCDneo/iWSMDEZGUVbdQdeXT0SQJLD6Tw0gnswins5ynwjOqffj7csCaA/EEfJRrLsYBTqBHIV7Tm4ZOZS4cmgudxFsDvQNTWLvsWFekwsRAitHoOeo5zJUVUkeU5l8LaMjIygNhZa3tbd/bcu2u39q3msRWK+SdbQ1vxvx2Kp3di9F8+g4vP0D8I1NID94EvMnTiDY0ogCdYyEs83moPZqps7xor+/n2yRNpGffKN0kJ+R2xgrNEG3aGeFF8ge6VQc6XgYmdg8ItRQw+NhHJ2ahS0bx+XtAbyt248adwxeZ546y8FFbtdukq4LUDYurpDPUWcBLYwKU3SD43Np0OkyGCCgC4xAiUa54Tx/o54V+m0kGqk895xz5u/91rcf172+kC0C61Ww+7d81Z8Ihz/yljUrq7s6mtG8YhmCXV1wUrfk58JwcMnSvXmXroCDbrJn2XJ43W4wcjSpCLk+/o8gKCBYEsDI8BBZKYaC2gAzKaQTceSSMaQjYSQic5gmC45NRUgtcVzTXYYrOksRdGYFHdhdZEcCl87WMJ8WMEiQFfJkL54qQ8D58lE0VJYgGk8jknFiPp0ziVS5w1QmY7L/AmaOQEzyPOefe17LAw89/G/mQC9gVp5/0V5RGxka+N+rm5uWJ/b2YuS7j2HsqZ0ERBKlPR0scDtc1DPxmQhDfSdWr15t0gL79u6zmGpBqDtUqTzW8EA/MqzIHCs3JxeYTCBDXZWN0wWKreYIrJlZ5LIJ3HzRGqw79yy4qyqRC4Zg8/vgJsCcNoLMMJVIkC5RiCnYeR66Oy52LmoBcvAaL2oqQbuf0aPcJuEo4DnVPKDfctFRFGzMzMyYbT/NFhnrFbR7vvLZhrdedNGnnan0//ERGWWsHX8JQ/hxurHn9mB+fy/syXmk3BTK512ANRtuxfDEOE7QNWZSaWoYCXIrKSo3OEdmSxNMyk9NTkwgTVDRRyJHN5ian0V4eppsNQ23x4u333ITutatg6e1B+6GVVyfD0dVF7LOUgJIbpXuLE9Rz+sUiDIUTzlu52ngyBUQzzmppxzwED4ZirDxghMJRozKzLsZwarXBJ2wQVZ5WRnOP++8vkd/+MR/WXf+k7YIrFfItn3+c60zvce/vu+hH7zjxDM7EB4eMXmhihVL+fR7kTs6Qn2UgoNuKbDybFz18b/GsbFRDDAKS5OxJJSV/NSiz/FYDBky1+TUNJ7bsxepdMpEfimCy86Ib5aAmpocQ0nAahYqq6rG04f68L3Hd2BiNk62KoW/thWB6iY4apbQ3VFnJecMmMSMBblbokTAUlecTMaG+UgK4VgO0wjgVIrO0+1DPpvCPAMJCXftL42mHq2XXnLJJ75//4Pbzc2/gC0C6xWwbV/5UujA3n3f377/4IUP7dmFGeqZY6cGMUnN5N57FI5jfcg7U4DHDf+FF2DVr70fh+ejGBsfNW7PiGOlFRjiZ+jysqYfVdos+/btM64rWBJCaXklRfwYo8IMmWoSNeVBXHrRWpTXNeDxPUdw6EQ/Vp21BmNkwccefxz7Dvei4AmitL4d3qpO2DyVSMwME0kxAsVyfxlqqRiZKkxQTSUcOJX0YOc0WZLabJ7gTiWSyDEyddBtKyWiYOKC889LXnLxxX+w9a5vTi8UwU/YIrBepn35i58veXbn9q/PhsPrw6yEFeeej3VXXIljvcdwTkcr6ktLAIpiOz8vu/UOrPrgb2I/2Ww+PIM0mUmgMm16AhcBlRW45BZZ60MU7dJcg4ODOHlqCH2MLAWyLDVWe105Ljh3NUK1zRiM2nDw1DhuvPkm9HR1YA1127JlyxCbD2Pg1CmsWNqNjDOAjL8SDpcHmbkpJAmoLB1fknpvJpxAOAmcSHuwI1JA0ltCUFltmgwh4PG44PX5TMN5WVkpLr30ku1L2jv++c6td4nGXtAsRbdoL8m2fuPr58Visc/f98D3V1fXVONrX/0a2pf1IEfXccPl63He2SvhpAspeAOor2tGeXMz9h46jCgFdy5HzULQpCXKCaoM2SkrtiKgTP6IYNu1cyfC1FmRWBz19U3YsWu3idwuXd2DSy84H7PUPU/u3s+IcA7j01N0nWmN0sFVV1wFH92whotZ7q5gtFoum0GB53CMHISdQn350g58//OfphCP4GjchsMMLPPBSrrSWbrnpHGTHh4nFArA5XRhmA9Ed2cnfufDH/7P2tra/3vTYub9lbdP/9u/nHfwwOEfbNl2V/OEhDV10vnnnoM1HUtw3VWr0Bwg89DteHxuNK86F77SSjy3fx9FsLSTRDrFs6I8Asq4vwWXWPysjP3A0JBppklTF0Uo2Cs9BVzc3YSzu5qRcJTg81vvxtp169HZuQRHjvaa8YVPPvmkydw3NTYxmnOb3JOOKfAKtAwAkXaE4CBY+rY/g1MnT2AP4dGbtCHpK8HszDSZKW1SC2rHLCkJoqy01NxzPB7DhResxdoLLvj4zZtu32c2/hRbTDe8BPvHf/zkW5/dvv0HTo8zeOPNN2LdukvovlImY7582Vnwzp9EYfoESsrKESoNEHRR7COoBJYsQZXPq5KfB5RcodzOyMgITtF1zZIx5unG7E4nYnSv1XQ/QQrvt1xwLi5Y/xZkG5ZiaG4e1fWNmItG8dUt38DVV19NN1VmjqUeDUovWAC1ziMGzIgJuS3KiHD7/v147sAJ7Ao7cCTqQaLgx9xUmG42bdytkrWlBJSOWc51jm66vLQM3V1dUX7/cxuiFxnrF7CvfvXLtnPPPef9jz766Ff27t3rz9LVqB/TOWevxiVrz8PanhbYZw/Al08gZwtgyXnrMTV0AkNDA8hR46jbjIBlmEmuj3pKn5VO2L17N8bHxw0gFCnq2KMTU4jTFbaX+3H5ymZ0083OUhN96jNfRFt7B65+y9tw/PgxpOjWDh86gtHRUVx11VU4/7zzTwcApq2R/xRV5uh6Y/EETg2dQjyawY4TQxhKEnQU54lI1AogKNLVCF4ElfqKuQlwNTHV1tTikksu+fR73v/Bby8UyU+1RWD9AtZY3/A3DPk/2dTY6KyvqcHgQB8ef+oJuAI+dFWEEDnyHborMMKax+rLbsZQfz9GxqfhKatHuuAwob4R6GSQPAFV4N9yUb3UXeNT09x3HEs62pFnRQ/TvYbn59BT5cU1Z3WjoWslUNaIiUgGBxgpHu7thdPlpLtzUn814Pzz1+KGG26gW+xGiqwj7ZZVzotgUeY8S7ZSl5rBoWHMkBGf2b4DYYIlzeuJRWOmJUAM6fUSVKES4/5KggEE/H5zvWKwhoYGabhPbdl29/6FIvmptijeX4RtufPr1U8/9fQ/33fffe/qWb6C7m6puvHC6XdhZmwKDeUBuPNpRMYJstkcWukOh+ejDON9sPtKkWL0JWAVMuolYDGJ1ooEk3ShTzzxJKZnpgyrlITKkaBw9joLaKiqwGWrulBWXYOdRwdw4Fg/XKx4r9OOQ71HTZ/1trY247quu+7t5jODidPHLy7SdHFqu6HBEczNRXi+xynM89RM8dN943UMNYKrd0V5WcispbGcZDD6bsOq69atk46ruPHWTbMLRfNTbZGxfo596UtfXBsOhx+hm7lET+3RY7344Q9/gL6Tx3H2ylWodicxdfDryEcjKK/oQNvy1eg92cvoqh3TOSdsbh+1jRLmBS5WnsoS02StXJrubgKnGG0pshvh4mQE6bHlsaq1DusuvBBV1FH3Pbsb8YIT6y+/HJcz2lyz5myyTti4vve+971GX5WXV5i+Wzp2sVlIbKUlwmsbHRsl4DI4duy4yejPMTIVsGTSZMFg8LT7k2DXcH2BTcl2uW0dc+3atfeFQqEv3vkixiMuAuun2Latd9re+Y53vPfub97zqWPHj7WcvWYNCz6EDrqq7q5OrLtkLWorfPA7XAyXxlHqpD7hkz/cPwR3WTv1yynMUc+osmyKAinO1QGvWPF5uql0KoHhsXGcIpPMzjE0Y3UFEcf6pbW4cPVyVHedhbFEFuFwBCdOnMQjDz+MQ3SbnkAJOhl9ikWuvfZagsBjgFJkKoFJjCRTIDA+PoYEz79jx05qshMGUNrX9H+n+xNT6d50rUF171kYeqbvrWgyayLNs88++883ves9P9cNyhaB9VPsiisuv+rQ4SP/8a3v3Nd0+PAhHDi4DxOT44gwcrrqouUoTR5DenA3klNHUFrTZSU1S9uQr+rCCHXQ6OQs6mrqMToyCq/fZ8S4MubSM2NkKYX0Gk4/OTlpKnF4aATljjxuuZxBwKXrEPVX4ek9++kaQ1i1erXYAkuWdJp0wu5dO9HS0orf+q0P8UptRvSrx4FpFiJgtSgImJmZJrBmCOYsjvYeMwynv/W9QCNWClJHlYaCdH8EVcBPUFmQECXlszyWmoAI1M6VPejs7j5219a7HzY7/BxbBNYL2N9+/KO/vf/AgS/m8rmypd0tBNUBXH7Fenzw19+LC8gk3vggUsP70NJ6DgLlnQiPnkChvBVJW4h6ymlmgPEFAsbtiOEmJycMC5w8eRJHKLpVUacY+U2HZ8liGbJHEvbEDDZdeT5WdC9DsKUbTxJUBw4cwMMPPYSDGoAqUFI/HTlyxLi/DRs2GkBNTU3xO7kqCyzFXgwCkRjNuML5CPr6qM/o8qYYJNjtNqOfArxGsVRpaYmJBMVeOobYThGkri3FY0eSUYzEp+EMeNa97wPvPfmdb377Z+awZL+ywPr//vRP3m8rFL53/bXXPrpj167RB7733Z6bb7wxe/ll6558+plnfr336FHX0u6laG9dgpGRcbRUV2BFRyMSk8eRGT+E6rpmRGJJODxJxPN2JEuXYHBqHrv3HDDNH3JNJXQrJ0+eoPYqN39PkJ16VixHgNunpqdMz4RCNo36oAPXX7AaS1csw7DLg0TGjlUrVuG8Cwi0FSswNDRkBL7SEBLQH/jAB/h58PQgC7nWouuTqQlIXXHUvUVgVAJ3iIyoa4iSMdXeJ3FuaapyAsy30O+e/EdcKu2Qof6LpeOYic5jJDWPOX8OR4dPoDFUdfXTP3jqb83OP8N+5YD1uf/6rKO6svKP77///n/gk+y46qqr/v2Bhx6euu7aa7sffuSRf+sfGFy3ffuztgvWXoCddDnjk2PYvPFGnL20CpGTT8OXmEAgPYNcJo9Q83JMhTNA5QpE40n0D4+bLiwhVph6J8iftJOx5HKO9B4xeajBgSFW8jBODQ0iQ93TUV2Gq9Z0o7GlGWN5P778jXvw/YcepJY6SLYpoKam1rCM7M/+7M9xx7vvQC9ZzxLqz4NKTCV2Ug9UrcVORZYbo44bHhkhwCbNcbxej9FMEur6LAYTmCy2KvB3KTPiZ2J22rCqzUfNFXKilFrukhXnTT5y/w/+yRzoZ9ivVOb9v//rv6oee/QH33nkB4/+zcjouKu5te3Y//3jPzn8xS987t3f+d53t/UeO/rWAwf248Yb34Hq6nIMDw9gZU8PaoNeeNyVqAyUIT03DE9tB9yNBMnMLGL+OkzG03AxmqutroKHFTVGF9XC0L+xocFUmoSy+o3PJRPY/uxOnDreh4DDjrNqK3Dd2rMQrG3Co4eHsOWe76K2tha//eEP082248EHH8YnPvH3eOqpZ0w64fwL1mLXzt2IRJRS0HRFAoPaAwuGuZS1FzAEtiECVy5YzTBqQ7SZLl76joLd4UQlWdTvI6jIUAKiXJ+y8mqaikTjmCCYZ8JzyNoKDBZcCDhdWNG6bLa2uuEPrdL82fYrw1j/9Pd/t/w73/3ut5546qlLpmdmTPKwsqpq+rd+8zeqvF7vvz7zzDMl+/cfwAyf6pMnBjA6PIIN73wHzltSgbl9W1Gg67JRi5Q1rsRcNAdHxRKkPNU8jgeHjh5FKcWvhy5wfHzCDIiwXItVyZMU0b1HezFANpkOh5Hhud9yxTqsv2wtAtWN+O+t30FJWTWZLoTn9uzBpesuNeL++uuvN+1/t99+O4OJK7Br1y7j4sREYqkiiAQM6Tcxo3SXNJm+l6sVs4m1YrE4ogQkf4KmpkaEQiULbKc0CCM/HkMN1fPxKCYp8Geic8g77fCVheAN+nFWRw+uXHvpBxpDNd/auvWunzv0/lcCWH/5Fx95x/fv+/63du7a1TYXiZhh6koNNzQ01LS1tV5REgxi1apVpptwgYC4bfNteOdVF6AsN4qAuwz55ATKCtMIT48hnqA8r+7BPMpw3yOP8Th21DXU49jxk6aCZcoJqcJUWVrGpyZQ39iAVIJCmLro9uuvxkWNjRg5cAz/fc93MBqbx1uvvQbbn9mOK668kuyRw9e//nWsXLHS/N3AfQUWgUpgsBZFf1a+Sm2UyrGpvfEoQS5dliQ7SpBrHz1I6iWRoLtWv6q2tmbTS7Uo9OUG0xTqApXc33wiZkDlLgkgQAAua+3E9euu/mhDRe2nNm2840XNCvhLDaxvbt1ir62q+p37Hrj/iweOHA6kWPCsDzgpkOvr60xX3/GxEfMka5zeN77+DfhdWVy6ugMYeBKFib1wV7MSalawUmNoWH4N4qUd6B2chI9hejLDiqIQVme8srIKI6YFqj1kneHhYew9eAg2l91EZ8loBtlYCrdedA4ayCozT+/AKKO+NIEe5fLo9u2Iz8dw0YUX4Zkd20l2dnzoQ79tNNBxgsW0LS6kEyymypnjStBrHghtO8TztTQ3m7mwtut48biJ/MJz8wiH5w0w/dy3vrbGgKpQHOJFUEXoMifIrNF4gqgQqPwEVQDN1Hg3Xn4NmivrR71O98Nb77r7RU2Y+0sNrKDf/7n7H37wT08NDtnzKYXQBXjddlxz5TqsWLkaFdX1mGKk9tSTT2MnK/PC81fhPTdfD3t8Ao5AEP7yGoz0HUfrkjUI1nWifzqFGR5nejZsRgp7KNT37z+Ic845l0xgNxUskIn9NA7w+KkBJGdi0GwLIYqZ9fV0d/v3wzYTRusN16F97YVYWlWDDoK9OpVGOBXBjmefgncuib/6m08gUFqGvv6TyMr95ZRYtfSULJGIM1odNsARK6l3hQa5VvK8hsnIQor6pO/S1HeDg8MEXw51dTVmEhABS22SageM8FiTU1NmbXM64DWgCqKK0euK5nbqK7dOudLlcv+vO27ffJzgOmgu4meYxYW/JHbPtq0qAcfgyHD9o48+uuXJJ588T5NpaJ4CG+OUOq8Db3/bFTj7/HNRGqqA3RNiQVKXTIcxeOoozumpReboAygrrUDOWwlnWTPKyus1yhN9CeoRCubdBw6ZihNDNDW3YIjM1EyW0LY9e/eis6vTVJq0zvGjR5CO29BUW4ar3U44Th6GLUpGyNng7GxFyVnnwrNyOWyFDKLPbsfIs3uo2RxYvfkmZJtaEGUUFksnYVPnP53Tuk2K94gR50oZmEw+QSQ2fvzxx01+yi/Q0zVKqEci8zh27CRGGRkqkFi2vNvM36BjiQEj1F9T07OIasokJ+CT+1OzjiJHXwBtdQ3UZC1k+EYE/EExcoIu9qxbNt1+zLqaF7ZfKsbavHGD48DBg+sfeOjB7z719NNdcgHq3Ca0tXvtuLHLhxX2EbjHDsA2P85Iz20KsoRurLGpGa5CCunxXsQicThZ9HaXD56W1Tg1EcUQXaY/UIIKCnOxUmdnJ0ZGR1DPyE8mraOM+szsHFwExDgrUmywvLMJF8ZTcBw5Cnd9E5wXnIusmkx4vGzvYcweOY7A8hVwtLfDTZZYw+9PEiApjaKpYBRKlrGbFJUYpmCGXQm00k/SVGIkabsk3aKA1tTUZHJV83TvY2NjJpgYGRk1D0JILFRVaWazkY4z7o+aKk4XTc8Lt98Nf9CHoNePkNvLKDiImqpq03tCDL2Q0nDR3n/75k33bNl295S5+RewXxpgSU8xavqd++6779/37t9XoUkyNGqYHgorgi5s7Ayhp5zuwZNF0JaCN84nePwQsrODsPNJdPrIXg4CqbqT+qkGgZoO2APlGJmKmBEugwP9yNucJp0gNzdP1tCTbOJ4Vvr09Ax2734OvUeOsBI1p0Iaa1avwbKAA4GndyKZiiPY0UoQLUPFxRfBsawHeTvDfbJDqK0eI9sPo7ulFQcfvA/pIbqttjbYamqQ48F0PKoq0/wzNTUJJ0N/NetItA8PDRl96CGY1U8+yfO6CApNFakkqroTC1TS6TV0g26vi0yWYxCSMCmFGMupwPvzaq6ssiBZyQMfj1VGkNYSVB0dHQRsmQGVRk67PW5l6F266W133/M9U/gvYL8UwNp659cbHn74kY8+8MADf3Hs+HFPPCGR7oDflse6Sjdu6PChvSxBN5GFm1GR3WEzTSwuugZHfBKpoT1I5QhCur6spwydy9eYjnmjcyk8/IPHKXxn0d6xxGTRFdIr2VhXV29cjXSb9IzEenl5uWEJuaf169djKaM6/+QMUrsYBCAN++QYUvuPYH54EH5WcsnZa+DqaEMqGkcjg4C+B78H5/AppO0uuC+5CHm6IpmYamR01KQO5OqefvpptLe2orWlxUSDR3t7EaTWUkNzjJGfALBv3wFzTQKVBI+XgKiurSJAC4bppijUlQTVgyGwlVCoB/1ealAXykKlqK2uRndnl7knh91pgKUyoyscJWM9Y7fbP0GtNW4u8AXsTQ+sz/7Hvwfve/CBex5++OGNA9Qd6rhWKLhQ4cjh+kYXrmp3oC6YhdNN4LhccLIg7YzUJLY1Y16efsGZjSMfHkGSIqP73CsQThfQNzLOqEkTlrGQWFF76f6qySBqXmlhhUrn9PWfMsykLLdyRorCGhsb8Za3vMW4SkWfrgoyIbVdNhymq+X5kIBzbgaJvYcQP3gU2fFR1JJBJh55DNH5UdjS1FM9y+FZtdqIfpk1tWTSgEhRoNyg5s6SHTt2DFUEgcSyRtEkM2nseW6vAX/R7KzlhsY6so3HTMKm9IN6ksr9uTwEUnnIJEsD/L6ihKCqrMXSrqUmH2cnm6n7TLEXBIX/QYfD+cGbN952aOHwL2hvavH+Z3/8R0updx55ZvuzDeG5OaOnNF9BAyO/d7a4cG5tgboB8DgcVsLSWYBGKBdYUHqSJbjzZLWCKwg7XWDnpW/H8SMnkChvRcRXZYa1q9fCQw89hNVrziYz5Vm4PuylSFdBl1dU4vDhI4ZFVOk6x8UXX4xqVTSfcLud+sjhhZulHJyi/tq9E6mD++BiQMEvESwNoKq5FSc1EQhBaWMgn1nSgcB11yDrZrTHe+w71W96dwpMShdI37VTjykKLCZADavwgTnZ14/DR4+RYef40FhlJFCoFaGqusJk/6dn5gxTFXjfDpZTWUUpr90JHx+CmrJKur8adC9Zyu0Mbvhb9bwQSEKhkIlABRmNjuZ3v0FwaZ6sF7Q3LbDe9773Xb9z587/pHtqVI/LQsENdyGLVh9wwxIvlpUXEHSnyVIqBD51pHFNQeAhO0TpCm1kJzNVBp/UYOuF6Lruduy558uYO/Iso8F2xC54BxmjzDSHqB/TitWrjd45eGCfaXaRIBZbKXclppIWueyydeaplqlS1PnSSbcm10SHxqUAbyxKChqCbXaW3+UwdWIQGc0/RVcVoK7KdbYh6fQa99p3qo96yW0YUZ30xFoPPvigEegC8pHDh03ytLa2jgCL8zp3mJcIWOcX04DBRplJQagHxPT8nBnVrElC9GCUkU09dIM+jwNVBFVdRQ16OpeaAMVoqjMWgUqLOa7RlZjm9j/i8tWbNmz+iaTpm9IV3nDDDR+j6/sXuoVyDRpwFHizhMHKkBO3dlahu4IFQXnpIjtpQguHi27QycJgASUpmLMscU2b6A5VINh+Lpbf8H4cfvgBzOy9j5TByDDCYCdlZ1TWiAS1V6is1BxLw9s1zL2+vh7f//730dt7FKWlZejp6TFMpTyWKkGg0lpTB5nzyp/ybwexlSKA7M318HV1IVPTCEdHF2zd3ch3dSCuqYRYJdlk2rg8F/cVC1q5KGsOB7GsokDpJ4FLCVmBezfdnyJAmc6v+66sqmCgoYcjixlNOUlQF0gl6itfVkoGIvtq/tNqMl5tJUHV1YPyMrIff6vrL5o+65zSlxpfKD7itiF+eB+XHKPDR7XfmWag92ayz33mP9bu2rnzz1iYHrkzMX6Od9HlceL8Sj811hym6B6ivLUMCzDPJ9Pu8ZnprcU4NopoP39kr+pE4LybCarfxbN3fwHhZ+5FgSAqUHg4kIH9xC4UBunycowgFUkV8ug7fsKASqmFnp5lJg/U3t6CSy69iEzlFXZMJQgMYoRiL0yZSVraCyZ6c9t9ZJg0omS8CBk0RgZN57lv0oZsIo3hyXG4Az4TBKgNUI3LYkWdV11f5BLVM0EMos/K9CsFIdN5JbJraqoYzQUISLrM8Azmk3HetNUPq6y8hID0wc/PdRVVqK+sw7IuRqvllfy9+/Q16160CKhiYjEn2ffv+VU9t1/J5f/j5zaz84/Zm84V/tVH/nzdP//bv/1wfj5yugBKWJjLa4KoslNIc1M+RSfms6G9yoHutkrUNFQgOtEHGyuXChYuHyOqq25D1dKVeGbrZ5E6uoNENcWKIwBzaWoaslWWQthdAdsltyEVCFEU2/DEY09icnqC580bzXHe+WS75cvgtgqc9aYnXYHBgqajjpEJVKocN6MrJSnVAB5jRWeTVnOKuv/m6OoSqQRmCRyBTx0B1XN1cHDIMEVrawuam1sMk8nEVtJXe/fuYyBBJuKDZIGKgQtZKkDgyI1H6P5mNQsgr0u/URdkt9eOgNeHmtJqS6gz0FBXH4FKRWSji5YVmVe/U47MTXazO+wVN96y+ecOpnjDM9aXPv8599ZvfK1en+/e8nVHdbUiFT1NC9NLc3G7HBiKJXAynsEU3eJg1oF98wU8NpDGfc9N4eDRWdg85SgEK+neunHWb/0N6laei2e//I+IHX4CuRQrJucmDnLWZGTKZhNcTm5H/x5GUmluS8HH6O6Siy9CS2sT3nrN1Th79TnwOQlUXpvDRrfnkKuQlpN647WR/fJZumHlv9wUfxJdXOJ0dalEBmlWvFhJQ7WidLOzdGnqv67mlWPHj5t2O03I4fZ6jJZSb1Gr52cJkqkcdu7ag/lI3IDKR62opba2mmxE4c9AY46AmxHTEVTKxGvehYDPjRIK9Voj1KuxtLsLpXR/eiCsfl18MuUvuehB8Xn9CAZKeT+aFMS278WASvaGBtZffuTPGv7n298a+Lu///tT6y65eMuzO3ZeUFtXn5ADtMiqYDLJ7V2drDA3nAFqBUY0F7/lMpy/fj2iFPT9LIb7d03gkUPziHir0X7xVUhE5/HwZz+J6PHdgBmdnEM2o54COVZ01jTO2sQuLM748HG45yf4fRZZFraAfNmllxoBXeAfORY+RRz0IkolY/WUF01MJQYR4yhlIYZVxJYgYOTW0nSFApZSF9JJ6idV7KEwOTVp8mf6jfbR7z3UXPpbPUKfeuopai6rPViMoukgKyrKCSANq6fL5D0qUtY16PW+pXpJAa/D6/GjuqoWtTV1BNVShEpC5hgyXbsY1VqTYbm/3K3YV3/z3B3f3LrlpoXdf6a9IcX7Pdu2+lYuX7bkB48++uCTTz3VMjY+7mAhrZiemb3D5fG89cC+PeV1jMw6GZqrh0JkfpauJIup2XnMxGJYcfZZ6O5hyMzQeVfvcYxQZ2iantT4FDKj/RjZfj9sE8fM1IuW26Mg5pLROC3DNQ7DAmqfo0hh6O/BvDPI7Xact3o5St1WctWpAEBClyA0hb9AoapMLXJN0ib6TkBSRQs4+mzmblCvAoJKolz7CkD6XumMIF2PQKiHR6kGAUMgnZqaMZpKOSu5KZPqIFPJ/WlWGLHO7FwY01xE7T6eXwMllCANCFQV1agnqHoIqvISCnVBgPvpWGearlvuXt1srDLRrdnUOrZh88aNj2/ZdtfPfOfhG5Wxcjt37/r6oSOHO5OsWE1bXRIq5Z3ZPTt37u5YsWKlGVSpYU0qDz5LuPTi8/Fr73kXEtz/S9/4Jr753fsQJqA85WWYz9hxIuLEkyNp7Dw8gvg0n2Q+1Wkyk8J4NYmYHJgJo7nQhRmwZUiLPF5kuB9lfieuuXQtnIl5BHJJpKZHkJwZRWpmxLxCxKb8GH+tBmGZwCQgiGEU0WkYloS2wKNF3VW0TTpJ9abvBCq1BYq9kmQjaTe9mUKaS00wap7Ztfs5k+aw8ZwClVisqrKSQKCrpam/WbTYS4EBRai0hKBy0QV6zHTejXX1JqMeom7kVZt/5sLPMIGqmLOSia2KwNP9cLFO9jPsDclY6952cfPU7NSfD44NeubIQnJNErlOMoO0Qzaf4VPqNW9cqOSTevVVV+OZZ59FluL33bdvwiif6n37D2FklEKbx1NfJj3J8ynqjjhZhltLJYygXgosLIMFCW8yC/cjpggsa47OJNmlfmkP6petRpzH0RseHBrbzEoLsHJSc6PUUREGBCHYF5jKTt2lVIMqW7039XaJIqgUtmutDP7efXux/8AB81kVJpemfZVC6GLlz0zPGsGsprnp6TDvSZ39BCq6KWoluba6mlrTDKNG5TBBpcEPin517jI+fG6Pg6DyooYRX4PcX1uHmcRNx6BnN9Gy/gk4RQYsKSk1TFXcVjQBjPb/bt1826fMhp9hbyhg3XnvV2wXXbb22oGRgR+Gs9OB0soyJOMpRMNRk2mORMMUvlGjRVQRJlHIwp0YHsKvv//X0T8wjP/5znex7uK1iCVS6B8cRsowkXI3LHxW6hzd3RyFb4BPdJmSpwSqncJboj2bKxBYavsTuKifCLqmtgaKb2vAZlDuKDEDZzaKFMHuLKRhzzKMj07A7/IhMTdt3jXo8ASM+1JEpsZqMZbRVFyLlTTg4fHHn8DBA4cwTr0kJtIACVWcjyBQI7eZGon7q2KVYjhxss9cv7WP9ZYwJWrVKCyXPcsoWboqx38adVNeXmom8wj6gwRVFZrqG8hUSwlaPgAWQE6b2FXlKU2l6M9yf6eBdBpgXB7htg/RDf7cXqRvKGCdc9FZf9o3euILk9EJl5ocnKTwKkYuHp8LkXgEWeqoFAFjzd9EdcAbryivQCX3UXb8nHPWUJhW4e57v2WGXOlpV38shdpiLpadKSwb47h8soBylxMlbmkjMpWZ7FWCm26QEVDG4cHSs1Yzykty7zTmhg8jMX4UGbpAT3YeSBBEDZ1IsUJDpW0YH+tDaV0TUtQ+5aVBA4KpmVkDDn0WoMRmO3fuNDPLaIiWJozN8J4sjZfD2NgoampqTOWquUbMpkZtAU+ZeFWuWE0utq62xugybZeempoNk3/zBlTKU6mhPegPmGx6c12DyagHGU1aU3NbVgSMQCWwSlOJHSUJTDnxuzPA9RABuOHmjZsk/H6uvSGA9dVvfnHpsjVdnxyeGPr9+TRdH+nZofl9eWMurwvlNWUU4iETukdnYyzwPGIMpcUCJBaG43pCKzAyPGjeM1NZW4+DB4+YhlXWppmSURSvtIGfLkTCWb0ZNJNwbdANG49r/laJB4LIlFRhySXriMI4mS7HgDNAsLHSOs5C+aqrkZnqg2t8Pxw1K2Gv7aGumUJF8wqEqhtREfTg6P5dZDQ74qmFSTkIEGmpXbt248CBgwYscmnF4Vta5I6ULVdl1tXWETxuHDnSa0b8qHeDojWBSmMC6+rq+NBYoNJ8o8qoqxwCIb8FKmIhSJ1UTaZqqW9CT1c3AgSZ8X08vpK9xT7vCjiU4ighkwmoBnTcZlb8J9Bxv+0E1Y0E1YtKNched2D9y+c/ef6h44e3jIVHr05l0+bmlf9RGejJlEgt2HKmZV7v0xOLKTmaTWVM2D4fS5C6NW+sG41NajerRZwFLfE6PDxq0hERVqpJQhJQ/mCALsxuckcCXYiiu4QVkbXnUGCUlC6vQdsll6MkM4dU/3Omh0O+5XyUnn0NcvVnwRYeQvr4ExRhykMRbJVNcJXVo6qxhRFYAEcP7jLdiLOOgAkOBCyTyNy3j9HefjO2zwQMZ4BKFSxQWZFYqWmC0RwNavdTxcpVSaSr/7sYTT0zBCp1pXFxm5pgNO13iEyptIOmHqqrqkVTXSO6OjrNcdXmoP3UnCPY6JyicJP8pFY0ZX36H80ClIB33O6wb7pl0+ZBbX6x9roB60tf/azjimvX/f7RU71fmI5ONLIOzJNkXJa1iylUU/AElwYIOOkS/RV+0wYWJXiSMWqXRA4z4RkzBZDX46ULrDBZZIlmZaSVyQ7Qtaifks6RYKXqaVV3GZ3NTmapYcSXpUv0dp+PhrVvBRiSRwZ2w1O3Ep5zbkK2cSVy3hD8mQgS+74DZ4rHbFmNwngv4owMfdRVGljad6LPZLntJRVkSWo6ujd1DVZ6QMvMgmsU2LTo/mQmZaBcE69b7khphlOnBsz3ckXSPl6vewFUVtvh2NgIgew1ZRMs8TMCZMBAN1ei8Y28/mYyVdeSbnNsuTaxUbGHgh7CImCL7q94LoGpuPD7NNfn37J583Fzob+AvS7A+td/+YeqkemBkUP9B98Ry8Q8Kl8lG3k3BlTWR/PcmJsVezEk5Gcnnzg3CyOA9u4lfFrdmJ4kA7ACxWLTM9NGW/mDfvjcPhZ40Iw80Zg6dchTbkrgKtA16vDqOalAwEv3V3/eWtSccxnZh5Xnostp6wHqe8xrc/XmrZzTa+ZrsJ98gpoviJS7nKQVR/mSs9Cy+lIK8V5EGBjkPUFkbZa71XSPGi1z+PBhE+0p91SMDFWRukcxhfRNCSM1jfWLk4XVx0plou+NUCc45P4EDkWXcqUClRkMwWhQjd9Olw0hCvW6ylq0N7Whs70DPo+PD5HLnENTE4mdDGBYphaYra4+PNPp8papzLmc4NJ58+bNVsv2L2ivC7CWr+r+1/3Hdl9kJ1PkdRO6DBaSbk33p5s8czHhMPdyO90IePV0euF0O1FaGUJFfTmZgG4rlkSSYvjUwClqsSzp322GZlUyzC6wkqNkMPX4lOmIypILxWouaVu1BqiswwyDArXu6a1cdiejI1YICGR1IXanyIq7t8KXnjNRmN0XQuvFN6FkxTU4PhFHJO9Agk9AjovOoy7ETz31NLXeIcxSWAsQcoFGTxlQWa5eaQGxhvJNajw2/dUZcLh4f/pOrq2+rpYPgMu4yLHxMT4IBD5BlkgmGEEy+iPblhAkNZXVWNK2hEzVxW1eU5jSagKVRhSJuUyZsizV9cV05zFP7Y+W9wKwbrlp06ZelddLsdcFWJ1drf/Ue2RfRZwF4yc1q3uIBLu5KXPjMuv/xnjz+qeKkL6Q6alXl5SSshI0tNZjPjxPYa9XrhUwPj7J8HuOgr4M1ZUV6OxsNe5klqyhOUANvPg/vSa3mmF4PwXwnsNH0HviOI4fO0FGmGBF2hlFqULU69QaJePOUXCrdyr11pJrfxPxsi6cGByGZsmTZhOgxCIalvX0U0/h6LHjxh0LUFqyC+5PLCEQCThqFBZTqY2RdW1mhlFTjdo/XQRMY2ODcXckJvPCAR9/oxHLGbr+CmpOM8EHdWM9NVVnRxfa2tp5fKUL5P70kks1LivSswBjQGTK9sxlIVpW2dtsGV7fJq7vv3PbNutJfAn2mgPro//4Fx85cvTgO8OzE/YkaT8WiViUzAJSgVtPs3WTMvO33cpiCxwyPfUyq6BYGYk502lNT/XczDzdQwGxaAITBJjdnic4nKiqqEQ9ddA8AacZi1ViOmeSAJ2cnUOUlWle+sjvxyfGMDhEjUOwB+mKvKzkgpvCuLIDniWXoKzzfPQPjmCUUah+J/emXp6K3vpP9Zuh8MpVaZSQvjPfE1QLFWfuQw3FSitokYuT6ZbFunKjemg0qFb3qDKYCTMg4/cCnMYU1lBnCowCf211Hdpb29HKRSDSjmIqMaHKTefUMWRa6+Etll3xmrRwGyMa/C7d35dfDqhkrxmwvvTV/2pYdm73lr7RE7/hCjgcBYeN7iuBjCK7mTAj/xxCDHm1Xa5P5aCbFdhUESqgojYpRlJaNA+UV3RfHkR5TSVKyVIz05NWo3KKwn56hpWaN6/C7WxvMwyh0HxOkWWuYCI7rUk0ZAVron31m4/QJenlkhLcdgJWv9eUP36f32i58Hwc8RSFOPWaeiAo831Cc6ZTpB8/ftxoKh1H1yxQyVSZ0jaK0gQogUv3ZYzXxNsxGkvM10BQKYrTvepY5DrzOZNOGBZ2OW08RgjV1bUmS9/a3MLjq1uO2i2dpr+Wyq0IqGJ5WWXqvIfl+mv8/BS/v45rkrftW7y+PyaovmZ+8DLtNQHWp/7j7y4+NnDwnvHw+IXpQlaPBoKMsqaHp5CIxAzVx8gaCeogHyvOQfejtjezH4Hg0BPNQpfANzqJFakhTHojlmb3lcAVAB0EZaDMh/rWOkaMCUTDmuXOhmlGhhK8atapI/j0wm8dRv2/VfGqMAHDWtSUo7+phbhTWHN9Dg+ZaYk6u5fSbXswQddpZs/TdbDiNH5QgluToqm7i7LtypkJULpewxICFRlSvTbVe1Puz/TjWqjsIrJ0LwI/acUI8zmNjeQxzLXlMqax2eWyo5xsVENQLelYwvtpIVtb0Z/ApN8Xmer541vA5nIPP773ls239W256649mzZsCPL7CJcPE1Q7zI6vgFlnfJXs7m9/w3Vy4OiHB0f6Pjabng8q9+Pjkxfg0+acnkI+BhyenMN0gPqHrsXOytakZY0drahtaWL05UeckZTG8onF5FK4QjIeN+2ECr9VaAKHzLhI3pH6OeXi1Fp9k9jz7B6CNykfYPTS8p4uNDXWE6xejFJLHek9ithC70yZIkdxJiFsPpUEGLExWNCo53MvuACrVq5g5TiQYyUKVAr91c1Fi6I/pTm0zYr+nmcLuXsfo1i5J7UFnmaqBTNMzKX4WQnZ6Vmyre6ZJv0lLaUG7zKC0hrz10kNpgk+LKbSMcWExWOfCSgdk3/fzQfwN27+BRKdL9VeNcb62pc+09V/svffx8f6f6+QnXM35OPoJHKWsMiqU3G4IvMI+/gUtzazoKrhYaEpssslMwjTfcX51Pv9AcNWEqrSRYqENIGGBK7mHFdhCRAqOBWitIp0hzq8KZryhRgp1VeTbchesbgRwOOT04ZRpNskbGsZxguYxfwS680sxD6Px9okWKmhzRD2ebpHMaMCDjGRggFpKUVxaqpRm56OIVAZkBOYujaxkEl+MjqzxLSlqYqm+zDGezAA429nyeDamkwlTCpB84S6eG6N+auhUO/q7GYZNBldKdcnprJyUs8f27oGA7Acr+O7/PjuWzffNm82vsr2qgBr25c+dcHUwIG7MHNyXSOiaGUkFiCQ4v3DOHVYQ5QGcJLcxXAOSbeHmocuzONHZHrOMJEmrYhy/8nJKdPzMcAKkevRRGLqqaD2w+LTqIooVoyeVFPxbuV+3NQ+GbjIhjWMGiVqZydnDYuoZ+YcXZzYQCK3rr7eDNlSe14iHkM5XUlDfa3JupeKJehidWy5Jc3LrtBdFKKpH1V5GpFcnEmv6FpluhZpKpOnIksp7Ne24jX/tEXA17g/uWNFhqWlYioYUNWph0LnUtQ38J4IKrX9mUn/CVgdu/iQaSl+5vYnCKi3brv77p87r9UrZVbtvAx7+M7/apjuP/pHXmf8yngyOhj0h9Zl56c9yelxV3h8CtOjsxTQEUTpBjMZJ8K82XxzFUI9ndAIXCHby4rzp2LIhKMYjLBiKqpx6OAB06PB7nWilprIQWBU1lTQPar97/lC01IoWLehp9VJJqoMlRsRPheNIEm24070wjZMjM1g+2PPIDJOxtIxXOqBWm0iKrkQDVjQW9oDjCIvv/h8lPD6nnr6cYxNTMNOsE7PzpveBLVkis7uHjNQdD4yh507duPkyX4DKC0ChypZjCcXKCFt3N8Cm+h7/p9rC4AWoLQGZvhARXgd+s7Hc5WFArz8PCpCFaYNUZpKD4FAJZesfJWagX6cBVlI5rh8IB7kNV2/4bbbLZ/6GtlLBtZdX/qsoyw79aH46LF/s0fH4cpKhFN7UCxPjcewf/dxzBJQ6imQU3RCNzjn4dPTVoNKiusqFnSQsU4pMihxkEWmw2CVIt3chRkCJTIzgxPULFG6GwnX1vZ21Hc0Iy8dT1Don8ClilThihnEFqZZJ1hKZtG86gQzAW1FWhL+NiSiCex+ehf6D/WBpzbAFKg0CNR0w2UF6Q0StRV0OZWl2LFjO4ZGxhHm75TJ17WIITqWLEFHZ6fpFbp7117DrnKBRbBbTKVBCEEDLJnpYUp7IWDJtc5Ho5iZD8NGQLr5IElLyf3p5UhmyHtXt+lSY7l8Rcpyf2UGYD9uKhpexxfJVB9Y2PSa2kt2hbfd8s76qd7n7q1OD7ntSBo9kKf+7Ts8hL3PHqMOUm6HGshWQMqehbPEibbuGvQ0laLLXUBlchyF8XGMnxzF/sODGMj5EFx1HhnNYTr12VlwFVUVyKezmJmYMqJY+RsTohOoRbMq0RpfJ3elHpxqOtE00kqvKzrT1Ix6hGwEt8PjQH1znZnacWJYfdktfaXenDKvx2em0tYg2KHRcYxNzbCyo7ymPJIEjsCgvl16q7xYKUFW1cwyypgXQSWWshqUNTNxyGyTFVlW9jy4LJ2odxKO0bUSYqYHRhk1lZusVE7gNNY3mBdbasRzkZnUq1SaSqJdVjyubKHf+h8RVH+8sOk1t5cMrC33fCvylnOWX5sYH2zVVEAayrR/51H0HR01k3ZVNpajtpFPWmMI7Z11WL28DeVkpuzkPIZPjONk3xRODc6jP5ZHqq4JteediyhFqsbB2XJqeHbA4XIa0SwtpMx2jK4tapo71CWXAp2FrMqScJWGUrOJTLklVZuOpVepqelC5a5FTRkmP6RxzuVliM3HjLiXnolGogRkxmzXORxODwKl5dyXbMcDmtE61Gj03Ebg65rU/KN2SrGiWgWUCRfzqdI1rk/Xd9oIIgFJ12FYiot6XczzvOpSLP7SeL+ysiBduh1VFVWWpupaatpAixpKKRl1c5Fot8BaPIfuWmb7LK/lz7ds+/mvJnm17GWJ92svu/C6w0/uXJ6L5nCyt596ah7LzurGmouXonVJPUN0FkxdKUooXo8dGcSRo0NkAdL9XBphNRzzaSs0NqB2zWoUvH4r2ai5FFhrbhbYUN8xuOlO6luakCcv5uJJIJrC/MwsbHSXYh0VtpWGUBnaTXuaKs0wwkKx5siaxu1wm6pgZmqarOPkcemSG6uRKWQxN8MoLGujaI5ianocAV6z11fCOlLm3gM7z6PmGfWNF8jkcnV46SB1i1GfdDGmGFVMpeYaq/VJe1mLRSpye1aiV1MNaciX2EoPkoa7VwjULJeq8irU19ZjWU8PSni8olBXD1DNVeXg37y4hUV5MnPwU1z/KUH117ds2mzlYF4ne8nAuvver6zYseupT9koNqcHp03ZLV21BF0rm2D35Hnj3MRt4fk0du46hGPDM4hnWJjcmGDBRv1BZOiSKnu6+DRWIUUtpBnBKA1Q4OcTJ3tRWVsNPwtVfR59punDg6nRKbj5hEYY1ck9KmpUxat/kt74YBOL6QnmecRCVlccXgjRwMNjgu5XGWu5FWkyNetU8zyhipDpfpOLZ01PVQ2lT6TiRn/JzUgn6VW7GlUjYKifuW5abXcCs0An5pQG0m8Mk1hFZUxAkssrCnx9VpQ5z/JT842iP4FK2krNTw0E1VIGCOoCpDyVHiAxoRaLBa2jW4wl12fc8F9QpH96612vH1MV7SUD645NG1K9Rw/8YSo8Z58dmkGovNR0lvMGPKYznSpxaGCKoOrFxHSMBanhVGSEAhUZv8831cHfXI8Khs2idHVFVkY9nYhhsL8PjQ2NJm+l17W57BToyXnkT/TBGeMT73JQv1kd6CLhOVMZpsDJGHqTqKhBYFJGX4VuY6VqqNf46Jh5maPyUKpYmY3uWdMahcoChsH0wke5NjXvxAkWzSMlJpKbnSVTai1gKYuuPmKqUEWs6kMlgJuZXwgCAUkMVZyYXyDWuvhZOTCdR70YPD4ntVgJNZUD1XzImuob0bN0mUkhiKUkzsWE0pE6trluPjzm3syCIbLUu8lSXzZfvgHsJQPrtltu9Rw4uO/C8OxUe5ZUro5mc7Eko6M56JX7M1zv33+C29RMwmiMbm6O4Emqi3EjK7e2nE93uRGh4hT1NIiwsGcpYJvr68lKdHUEVYMthfZ8At2ZKDoCDvg7m1DWtQQOuqo03U+WrDKntkbqMk0rJAbSSBvWnwGYIjG9OHtsdNTMKCxdpp4GVkTGcFxjBKm55EnkimqbquAJ+Hn91lhFMeEsI1QxVVzzH4j5uK8q1Hwgc4gZ5ynwrZEx1vuTDbAE7wUwabGagPIE1azJU3kp8NURoSTkN4NCaqpqKNQFqh7j8vTA6TwClHTVmSawmWugZyfYbrhl06ZHzBdvEHvJwPrGtrtT17/9bWVHT5241lvigpNPa5K143V6MXBskE8l3VUkabrnxlg5UzxTlmAq1JTDR7dSXlbJ6Icso0vIFjA2PIR8dA5LaytQzSizBVG05ZIoZ6VmhsZwbHASAx6Csq4VOeotUhSC/kpMT81R7GcRC8utzJExWVleglXg4rk13/nE2Cg0ab4YTDpOOR6BygCL0SfhZ/40LOAsoKya11BfQ0YkmKLWlJMaCa3OgtpR/9dHQce8oVSN6dR5GnEswV48tgHTguvTZzXTxBjZRsi0QQY4aguUHhOo6qrryFRNWLp0qRnapg6IAo6YWMBaANEZZiK/fi4X3bp508+cBO31sJcMLNkd79mU3Hfo4G8NTEzARkFpU09G+vy54XGUVpQx2koiSvYa5VOZbqiGs6oavlCp6ZxmpiWkiwvYM4iM9aPekcWaCj8aM3OoiM8iOzCG8d5h9PVP4fhMEhFGjg66hyRdg7QOnSHiJ/sQHRpHhi5JTBUha8xOz9B1ORFiZSjZqf7vba0tVsUS4MUKKrLIj1eYmECvVvMFGTS01RMoQdNRL5tU26FG+1g9K+SSBBi5RrlVbVOvTrmsHz+HFu2rV/Eqq271KSMjl4bMDMaK/JoamtDdbXUlLmooaTWlLWTFY+q8lq6ynyT7XnjLpo3PT933BrKXBazNt90yNTQ0+q79B3srJucopMtCcLIAsnNRPr0uTGdSmPTwyWtugJdgCpSVoD7oxoqgHed5HegkmDq4LPPzszqwHR/E6IF+9B+dwtBYBlN0o5MuNzLtDQjpZUfUOr58HDUFukgCcEWpCz0r2xDqWY0cozMlGNOMHOf1yo6pKWgKxAZqOAFKpsopVrTM0itWA61Ma0WlHkZcLk3zyGM289rbujoYuUVMNLrAFCZXpd8LWNZxNOtdiZkLVOcTkGRa6+85gkpddUrIVNpfLOSlG66trkFzQ7NJfioQ0He6TgUCeimlBTLr2ovXz23zdJPrCSpr6pk3oL0sYH3z7u8Urrv2mssGh4aXT0zNULzzqZV499K9sIISmqCirhpuaimBqjYUwHklDpwVdKKMzONR4pQ6yhZN4/iOYzi05xSmw0Ay40CcviZc4oOztQ51jZWo9NlQWUhhCfevo95KjE/gBIOC2YY2pNRHnUzhJ7Dj6SSSkRjC09NmUKcqWqmFgnngBSoLQBLk0lv6XASBQKMcl92mGWNcjMbIjprTKuBFW3cbvCUeutUJE3XqhdxyU3oJkhhIn2uqqxaOYxnPZlyyAK9Of1U1VUaPidU0NE2jmFuaWgiqLpOlt8BDoMvPcl0Er6yoqXiuIQJrIzXVLvPFG9ReFrBkt2645dF8Ifv2RDpWrSfSzidthCALVTI6opbK8MnX+1iUcypnpXU7MlyzlgsU0HySY1NJ7Hv6MAZOTJtuv3lWZI4sli9l6N5aju7GIDqCBFUuAQdd3UjfEA4fHcTxSBrunpXIBHkOCncBxE0gB3meOIOJ2Ylp80YHJVSlYTTwwvTx4n6qMJkApd+pwgSq4pofjB7TR/1PQl+9SavqqlDfUIchXkNZSDO7EAwEj0CqYWdm9M9pkEqHFYye0kghc27uLRftpxbTwNrWplZ0dXYa91c89wIjmd/LxaoBu/gdQfYUAXbLGx1UspcNrPvvfyh2ww1vK4lEwm/RRKkFuwsnT41hhvrKpRyU+lKxsPwBHzUVBf2xXgQLapG3YXaEkeOzR+hi6N5aq9G8pBqNbZVo7apB19I6LCVTecgIc6cmMNA7ipMnpzA8k8IMRXxg1SoU6mp4bLUBWgCRolZ4PzE9ZcYYpimUs4kUhX2Y1QJGYawkZavJBme6K1WcKlPtdWIILacBp4X7KZemjojqsjM+PIUSf7koiV9lT/cs4I94tOd1lV7yLaayO62eo+Wl1EwESnVlNXVfG5YsWXJaUxHKPJWVQtCxxFBi1OL3/HuYf19F93fKXPQb3F42sGR3vGtz4NRg/x05NTnbNbFYEhOTc3zK7WZkrqIpuYw4dVgJo6xTe44il7Lh2P4+ZFJJrDi3Gysv6EZtcwiVNSUo16SrZLf+YwM4dGAUw5MU7ylgloUeKQ3A3d2BAJlDGXXlvhTVqUJirPSxoRH0LOtBOd2OhK+iwlwqjTi/0wzIihrNhBgGBMKCBYJiRZqRKwumPJX+ViokEYny2KPQmNqx/lGU0PUJc2aO0wV3paBCwBJg1UdLi7rvSKuZPJTfY/qnd7S1o7Oj0/SU0G8tRrKsyFZiKjULWSCzz/I8F928caPm/XxT2POl+DKsrKz0QafTu1N15XTZ0U6xXV1XQXBNI52iy2NYrekbNa9BlmyTjBcwMhxBgpp65Tkr0Ly8ySQqtZ/c4/xsBDt2HsGBI6OMKu1Icvu0LY1oZRCu9mYE1VeKrkosorWSQeHpCcyMj2D50i546Wo1elrZdPW0TMTSgh6mqctOHTmKMIW9ZqzJK+2wADAX3ZqboFMDtypamkwwUT8wAfZ473GMj04iPBNGPBrnFwQj9zPdi7mfASj/qfeDsunzZCu5Zn2p7sga8l5XZYFqyZIOo6lMM40JHnQFYiU1KFttgRLuAhWXgwT8sps3bjqpvd4s9oow1l13/U9h/RXrrp6enV6Ro2tQuF5WSsEe1EBKCWFWFFkrxUpyZPJwhuOmx4HdQ4Ec8sIVpOB3y/3YMGXeq9yLofF5Asppmn/mCZRMdSVcdXWoqGsk61izzKl/k4fLeH8/IrNT6CSTKSuvrjq+Qg4VGnjARdouls1Ti6lbs9zZvIjFTO7q8Wrsnsu4R4140TSPYhyLDAuIzUVwZN8hDJwcJPtoQg43xocmjcYSo53Z9UUglPtTekK6S6LetBsSKJoorkPur6PDfFd0vRZbWoyrtaLFYjS4wFbvJaj2mJO8iewVAdY3t24pJYB+/9iJo3VqZ1OlqdDVJqen2BgLMM+CUg9QNwFU0Cx2Hh/mWDGT/SMUvi7M0d/t2X+CkWEGKYIqygqe8ziRZrRlq6pECUPz8spauhaX6afkJVMNnKA7TefQvqSLJ8nDhyzqCwl0OgtoLtD1ZRMoVJTB29KKAis4rZ6ZFPUJjQVMJE0XGXWUk9DWoFYxjKaoFsNFw3MYHxzDYbpuzaHl00zD/gAmRyZRGlRzi27L0ndKOyihKlAVm14UkQpUDfUNaG5qMrPo/aimshbLNJDCb/SavpfpuPzes+3uu7eZDW8ie0VcIaOXf0bednZXR7cR0DlSgwpas/aqkDTFtZ5+eieEqXMGWenJmhCiJX7MkDEiCSee+OFh9PaOIxzJE1QOJBgRaZrqTHUtMixsX2nINARrSiMf3ZaLoDpy4KCZJbm7rQmhfArd9izOLsSwJD4D5/AI9j13BPvGGep7Q2Zq7kqCs4uVqwpHNoe5qVn07j2Ak0d6TQSW58MQoyZT43aYke2oBqNOzhKEKWq0pHHrpjdoSdCwmnF/XNSfy/yG0Z+SmmIq6TsxYH1tHYV6q5nvU7kpmQFTEU80HUOaSikLHdcw5gKD0a6/Z9vWWrPjm8heFrDuvWtLz7ZvfPWxXC6z2c/KLmfoH/SFLHZiRWkKRrX867WwGbkMPvZpstWegUn0RjOYdbowlUsjx0KdTWTJNU4j9GOsqDCBkG6sAQ9q3kpVyYhTE2M4eAxbMoIDu7ejrsyPVY0V6EIEZ9syaJyLI9o7goP7+rF933EMFpwoNLcRMF7zkoEAAW1PR+CfTcCdzJP5vMhEUzjVexJH9uxHltrJR6aZmZjAmMnaJzA9Pcfrpz7U6GMNwScg9XoR6TPlpHR/yqYLVCH1hOB9Sk9J3NfT/cn1dXd2Grct1yfxpqLQnFhFxhLDWZ32rOrQtiLAuPi4vNt88Sayl+wKCapNvOGHubRnMuQD6hg1KGs+gXhCL7iOmoKXSzTdV6g/lCyUQB0dm8TkTMzMAqMr8NF7JsgMegPVeCqGab8L2dpqOMkO/vIKBEorUBsIocmWRXWOwJgawllVJTirIoCqRBj2iRlMHB3A8ePDGAsnMcPIc47uLcDIy665NhnxVVE0tbhy6PEX0NNciWBrI3yNDYgwgo1yEeOop4QaqCNzmkGQgAlHMXRq1CRN6+pr0NzSRH3kxOwkz8mHgI8PYtGoGZ0jF6bUg9hQQNEkHh3t7Sb5KcBI6Eusi51kRRBpXzFVscG5uF3rIvDoVr+6Zdtd+8wXbxL7hYGl9wJu2rBhPQvov/mnBjua7XJ/aT7RklRTUxOM+oZPt6FpMYTFfyowide5+RhmWSnVlZVkMXXH9ZixepGAD+naSoARXSm/qyRAmjw2LHMm0eOIodEeJ8jcqGL06SYg+vYdpSsbIxhSiGSyiNrJeJUheOge/SU+VNkz6HTl0enOwRcLY3RkHCNuHxIV9Ui4AiipqDDJULGTOtgJMLwQxCMJDA4MI5mk+6sIorVd029bjdGjp8agaa0l1tXzQe5Prl/j/gQUJUvVh15spRSG9JYpg9Ny0/qgfbUUwST/WPxOtgCqf75l0+ZPLmx609iLBta92+60bd54ax1v+y95y3/F5Qne91f41RgLY7XC8NnpWbJVHKOjwxSy89A005rLXH2WSFqGsQQvk6NhCD5PZvO4vOibmkSeglzvQM5Ql9CPmJcLVdRUopouttuRR6srDb9DfZp4yTk7UgSS+tYPnNBLtDNI8MjzbrrSKrrMmjK0Myrt9trR6sgiQPYcY1S348QQht1klrZuFOweBMgg7oAXlZVVZMuQeRASsRSGCKiJ8Sl6bjII96muLWPQUGo682lC/ZNHT0GvsZUL1IMkgClPpUlI9EqUtjZFf0vMMDaBo8g+RczocxFU+mxtU7rh+eiQ6zmC8uME1ke2vMx5FF4Pe/Eay2ar4///hhhJM/ytt9sK/5sldVsum71cYwHn5jRpRd5M/pWjG3FQS3jIABoMEZmhi+ETrc5z/A+pXAoB6qOuzna4SzxIk+32H5/AUVZUzKERz2Q6Cn6T56GmikxNI6MMqTQTD5CYjmLvUwcxNTBDtGbNTWRJNH66xqb6SqxtrsA5lW6UZZIYG5zC9gN92D00g3BZFTzUOzbNGaWOem6HCQKsrHgZEaKBojymqjHPgCNLAJDdquiSE3G6xwzvgQ9HnvemvviKKqWbgkE/gVdixiJ2ti8xs+iZyFKd9LiYpBgXgUaLGE4pBXXiUxVY6x9hqxwB9X4y1cdu2rBRT+Obzs6ITX66UU+FqKU+f/PG2zaYv7fdKUF5QS6Xu4v6okoTsWYpuE2uiu7w6PFDOHr0kBnTpx6dGuOnXlAen9fMk8l4ykROchOymekwATBtZlLpWdVlBpm69S49MpYrGUPDfBTlDONXdjYTFGkc29VPAEQQYmRZUV8BNxnQSX3m4bE1XWIyPIlTQ1MYns9iIkr9xPMVqiqAqmpUN7Xw2F7DCqpIvQwzHo3h2JFjmBybQDKSNAnV8PQ80nHNWepBR1eLeXtWIk13vHIpdj+1D/YMHxyCM0Cm0txWjY11C21/3afzVAKROYfoesEEKjG21ahs7XOm8e88QfUHt26+7Z8WNr0p7WcCi4CqZaH8NT/eziVNAfoP2s5tPVzeo1lQNGzq9GAGAkvDqY6fPIIjh/abUcPqzJbO0VUlrVlZNEhVM+7pBTSqAC3ykOEZuivqn7rmGtQ0UrjrypRBT0VxaUML4jsPIUiXUxJykcHm0NBej9UXrTCDTvN0WU6By+Mz847uPHQY8/E8opkCZpIpuBqbkCsrRRXXpQwG6FB1G8Zi87PoPdRLXRhGMpFGkpHl/GwUCUaLYi073bBNA2pDXjRR8FdUlTOKHECUrFlKkKhjn9xfXX0dhXoPXWWx0diAxACr+PeP96+ymOp5Ixjnuf3zBNXvLWx609qP3tkZRpFeRbTs5scrWDpuVoWPLucqAuqqRCJ5lkYNS7gW38QgEaUn0wxpIgDk+rSPVYBCsNwC/WgqyRA9Y3VP4VNt/A6/9/k91qgbinKXhyKam5V8VKTlpPbxRnOITmg+0RDKa4JYc+kqwGtN3KHRz9m8A+PTMex87oh5vUk458Aof1eorUKmNISSmmqUUqiLLY3W43VquNeRgwcxTVebTucJrAxiczHTa1SjrdVtxul1U+CXoKO7hddlM2kCdVl22ZxmEtmmpgbzfmj1p1IyVEJdYLLY2IhvUzwaWaPkqsyUxxlstQC+U9xXYwH12rY3vf0EsOjm3BtvvfkPeM+f4J9tvHWnKQBVNDWIae8jQ82RrTRZhyrILAumffWndIjWmnRWjbPmbV1CCw8lhstQcCs9Yf3I/NCMVNE002pO4RG48DeEjsbsldL1xGanzYianOkjpSmkfebcmux/ZHgCh4/0Y3KWgKcAD/Na7Qz589RHgXJGlxTouras8mYaHU33d/LYCfP2hwxBlSao4nSD0UhMJ+e16pIIEAYEje118AXdBF5SE+YgOjuPEoKoiQyo2V7US0EpA7k3gcR6YMxRzKIWCDFVsZgEKpn2XTBpqj8hqD6/8Peb3n4CWIz8ruQNf4kf9bJDC1Q0gcN6dYhF7QYcGm1MOw2mBdbKkimUHFVhKj8j5jL+TjBZKHS9EEkglStUDwLlDosFbgla67NGxNjJbpqq209BrHbHCM8Rp6uaG58xFTo0Nk1dN4jp+QSmowQWf+torUe2LGjeFKFZljVoQtcsNk3Gk+g/0Y+ZqRlkkhkz3CvJ4yl/pevXNTqp8XxBD5p5nBADDfW1ylB7TfOceg9NC/VUseuLNJNMvxO4xFJWBt5p+n6d6f60T5HVFkDYT3b73Q233f5Fs9MviZ0G1j3b7qzcdOvNf8Kb1VszS1UIKmTdvPouabSKmEp/a7u6wWh7sYBkxe9UgQm9pp8A0XxXagrRa2/16jUz5o+mtkEzkT9do3ELJtejph99SzAvAFrHS8RTGGPE6PZ44aA+S/udcGdd6D/Uj4Rm7QtHEGEkOs1oclJRYmMDMiUEA4W13KuagNTsIpCKKQf6Bqjp5ujS6Nbo2lLUVpFw1PwtF6a3OvgYrdY2VKNKI5AJ7CiZcGYsbNoImxtbGPl1oaOj3USXRXena9bvlUYQ2DQ6+8xmHC0ylZM+83fz3P/jZKqf+tLuN6udBtaGW276b4Lnw7z1Uj1VxUJQxapSDIhIK4qcFWJrtElxUjDLRDn8j9GeNIwy2RY+rJEmqlTNqXDauF09XqTR9CIAC1invzLJSCtKp/uiCD95ahzRJNmLgLF5WXkFB+ZHZ1BghJd3FjBJpprk/pm6SvXjgZvnFGuIMXQ9qkyBqv/EKejFT2m9NSLDh4DbxFQJRo+aK0HACFCoV9ZWmH5hroITo4NjJtteGgyZKRlXLV9JN9hsAFVkHz0UGlmjcyp9Yd2HejCYJ4WfF25uwfi7QZ7r+ls2bb53YdMvlZ0G1uaNG8ZJ90v4sVV/q7C0KKNcLBxuMWs9wXJhag8U4ET1cmeaJ0H7KJOdTGhIlN55Y7kIiVcWr9lfi3JeSpxK0KvINTeVNJx5Oyk30LmiQHLTGdUslIwlMcOILc1rUldnvY0+MzdHN8l9/S6MESD25kaky4PUX0GT8NR16bcS4XK7pwiqeYLKvL+G59IQ90QsgXkynnyxQKJr91JPKSnq4nUry55gMKC5qTrams1U13o5VHF4Fi/Hcm88h8pED5GsyEoCV/FBLS48zz4ul9y8cdMvPDH/m8VOA2vLtrsHqK+eY4GUsSC2sgAaCYBK6aUzC0Wmgip2cisWpnpqatKMYoGq055AKbO2qa8Rw23uqxHMmsTfbiiJApnHk1lJ1JxxIfwBz2H10XI4Wdl+r5lAQ1NfB8tDBuyBANlIU2RTe8XKSpAqJeB4fCsCs16OrWsUqE4eO2kAlKKmErA1z6jeZxOPW22aApquUxFpRVUZQnS5CWq2WUaiAlV7SwuBtQQtXOv6DHtzf1mxXGQ6p8rH+k7As/Y7o+x2kKk2EVRvmt6gL8WeL5Ez7O47v/ZJAur/ClRnmgroTGrXy7u1TU03GoouHWYVKEzCdGpSL/AuTtJhgUcRmd7YMDY6YNylvleKQq5SL1NST4gCK0Yz2GiksHGt/KmZl4p6RjPxlXC7Jp0t4dUvra00r0KZ1iRntTUG5K4FUMkNxhj9HT9yjEJdI5s1oQc1I69b2X0NNOXh+XSRbcliSjNontAl3W0o8LrGh8YR8lVSpDeirbkVrS3UVHTDZkIOAl5lYFiOa4FVLvCMXgpRp9Pdy/s715SVtewQU9H9nakhfintNGMV7Z4t5p2/n0ynMw6To3oB6KmQZAZoXDQTiwCn/JTRGnyipVWUh1J0KHbTvvqZ1qpwiVuJfKOloPOwkuhOWPyma4pp0FYF6Tj8TiJfPVODBJy6EU/NRDA0Ekay4IKHUVuWv1WXG1WsJ+iF3heToPvct/sAZiamzevodK26IfVgEKgFAKUT1AogEa4BH6WVpQSwByP9oyj3lxNMLaivqzNvkA8stO0V718rHUPgEqjEyLpvfpPnejPL4j/5x3u5j5u/eY7bLiOozhCav7z2E8DavGHDTSyIa8zLJvWP4FDBiQFUePosEGlRNRmGYgkr+lGBq6ArKytNRckEEoHL2r+oO5SzogA3meuQOYbRXax0M8kGj6HoUIMydA6dM0u3pUrTb8VoLoJXCc4wozkHXaHXHzDD5+MpzVPF2+LxDuw+iKH+YcQiBBHBI/ZT3yqxmK5H/c4FWmXWi3N9Krc2Pkym8obQ0dpuZmZuaWk1mk3XaTXFFB8sTehvTSprNdNYaRKW1Qc23Hb7N27btGmWv7mJ+0tT3UxQvah3/f0y2I8A696tWzXiUs03Z6tyNPOwCkuAUqVqm/5WAYttZCpkq0AtcGlffXbwtxqYIN2kCTXOBJUW5ZPUN8vDqC4YUM8CulQCScCSKS2hTxnqo3wmZ5KmYj5zLm6Xy1LjbzKdMF1wTAKUv80xYEjzN2r4Hh+ewDSjOYFHb3d3EjymvzuvRW/bElPphQUCeYguVHNyTQxMIugpwRKBqroGtXW15kEp3qMWmYCpB0APg9y5WHhhpM++Wzff9pvaR70Sbtu08bv8zX0E1RtyKPyrZVYpLRiBdStB87dcVPpeltPyYkHKVJhFUwrizIixaAKdFk2AIbbRnOwnj5/gOmGEeXH/fEH6jRVVEAPq2DlMTI5ibGKIDMcggARmep8KKAIWI1FviQ8un9s0+2hOKXUulHuLRuPQW94Fft2Rw2Mx2hDd2cDAKPx0n8FS/tZrMYrebKprNGBhYBDyk43iOYQnwvxcYkbSaECpXtdbW6+XUmrSM6sc9LviZx2Ld2ACBUtbOXVMaitn5U0bNlrZ419RMyqzaDdu3HgXC201C+dSrteyEP+Jyxe59HI5sbCbMbGTCrxoKnCBRoUuZnNR4CrqU6NsR0eHSRqeCULTAMvv+SsyjaSU3sdXZ962INCocVlDyazz6CXjGUT1loZEGupsJ/eqKJEcZV7/LxYyAxuos9QorWM75Y75Ua8HIQIZ6WmO9GrzdgelO9TEVEIgZZVcHZ9BaaDMZNOra6pRXlmBGo1u5j3qqrXoQSkyl1nzn8+nfJkGQFj5LN5jkOse7vorbdaj9zPs3m13VnM1zcJSu0RE21SARcbS5zPtzL/1WWCSUFYfcjVaa5v0lCrmeaAJCEqsaq7RJMbGhjE9PWX205yfcruKGE06w+SZNI0jXRvdrfSR3mFYrHD+zyRb5SZnKfAP7D9kRHlDk5pmSggQ9bmaNe1+pdR3aWXuCapygmpJcwfBRFAtdNjTg2M02AKQePGnC0x/azhYqKTMPEi6L+2neyJjXbbx9nc9vrDrr6T9CGPJ7tm2tXnhY9HCXFi+Nr1t86gKsAiqopkKXTAV+OlKXvhbrCPGklbRjHemwhYqwjJ+tlttdBq311DfQuZqYAVZQ6Xk1rxkNA3YUNwYC0fMm+2VPRfwpHG0GKDyuFrr+BppzJXRU6WhUgNUudk0tVyprwSFZAHTQ1OoCJSjvbnNTOpRSaYy8zAQLGeajldkLH22UgsaAvb8fWq7fndmefyq2k9EhRScp1+J8a2td/pZRBpYo9dlvJOe5h0stBtZzFUqxDPNuD8CRhVsKoGLKliLCtq8LpafFT0pGpTeKu4r4976H/8WO2i+zRITocktqremqkrHMVEj3ah5WXc+a1yV6vH541jXot/JHar7cDUBo/fxqFNfODwDPwMGr8OLiaEJlPrL0N7ShjpGf1XcT7pKD4KAYobb8+C6B/0tUwChh0RNN7rOYtunzs99lGb4fYr3LWbjr7D9BGMVjS7wUlaSclpOSpG/v3HD5ltYyjewsppcZBL1/VZhC0wCiypDQClasaK1jyraTbYpZ6WVibWUjuDvZAKLFqUXrN9YiwCkN9PX1jQiVFpu/tYLijzUS3qjqOZC19DmuJpokmIrIMtFM8/kKfgLWb3OZKHiGQio7/3kxKTp7ux2+zA1Pm1Ee3tbK91erZlTvqa2xtyHzDwUBL8OrEKyFrphj8/0d7e6HFvXrIX3qCkb38fo75/NAX7F7QWBRVDVsrA+kyvkz8uh4Lxh4+ZHtf2mTZs0eap6NyryMU+uQKWCVVufTCAprk8/5Vzrb0uIW25QT7yy5Iqm9AoPfRYAi1b8vY5fX99g5hfVUHhNJKuOfcqziQUlyjViWS94KlDUFzJqntEkJPxbQOV/fSeGTOOzR+6UoJoYnjTAaGmlyyVTaQ51uT+dr7gU70OfDQNy0YCJYmb9zH205rn07uRvmg2L9sLAIjuN37TxtpWskyj/FIWcNoLrsyzEW/lEf1ZPtSpPiwr6zL9V2EVwFK34vcClcXgClDSXQFb8LPAJtNqnCDSfP0Dx3ULGq2Gk5zEN1dJcboGLmkyDUePzMaSiDCYIrHzW0l2ZbIq/1WwvNjPMrMQTwOTQJHxOv2n309g/jY6W+9M5z7Ti9et6dR3SVAFex5mAKt6r7ovbericZb5YtNNBzi9s927dWs6QvZQF+3mbzX6lEqHWmxusRmsBQ5VQzHWpAmT6LMsp0uN3ylVpkKeSo/Nzc4wGp0+zn/aV4D7dvkegqLF7fHj4dOOxdJZ5UyrdnwZu2F1OlJSSVdwFBMusIVoawRzyl2JiZAJedwBtJvlZTTdbc/rdNMUHQElT5aeK16vITw9BEXgmcbsAuIVdzFrTFVFz1d+8cdOYtfVX214ysGQE1zoW8A/1WSAoinE94aaeeHRtF1BMuyC/e75SrFop/i1TY/bIyAhmCC7NzW51zrPEf/E3AoAmrB0ZGUIsFuE5GeUpEKBI00uZ1A4oDRgopRYqJ8O47eYVvmODowiSsTpa9fasOsNWYqozGcgy6/ol/N1kx1LqO/1t7aPvzgTWaWY+ROD9xc0bb7vLOsai/URU+GLtni1blAT8cy7LiiykSlIlWIXObawLfS4KddlCRSz8Zf2t3+kYcjdqGlEHQkWC2lZkkuLvtE1v6DJRmdNhRgJJZKs5p1jpYkO9rV5voveTcdRLIegjqNo6UF1VawY/KPVRBIxYsfhZr4LTOfWGL+mpM4e+W2sLVPq8cF1jBNUdBNX9ZsOiGXvJwNq8YcP/4sq0iS0UcphrvcpsIdzTdgtsWoruptjGKNPvipWk72TaT00kyqqrr3yRsYr76m/BWFM+ag5QtUcmolHjosR7Oqcy9bqiAsNEaa6yYKmZRLa60gJV0f3peLLiNVrXoHZEH4V6cTY9LRaodGx9f8bv9vE4HyKoHjMbFu20WbX5C9q9W7e8m77qFtMybBbsYCF/gEsTC35QFWBVkgUegUGA+nFQaZ8i4GSqMC3aVowgi98XK9P8jiDSL7QuDZXRtVlvUPUokep2mTnk9WbU0kAJKkLlaG5sQl11LRoaGo37K7KqjquleC1avF4/2TDECNTSVNY1CdzmT2PaxmPs5bX9GUH14MLmRTvDXhJjbd546wEWb5IFvJoF/h8s+HezjvvJJ3/Ez2+XW+L3psKKwJKIl8spbtMiK66L+qy4iLFU0WKv4ndFs3oVWGv14VI/KM3MJ72lr+TOlMGvqKhEdU0tyriuYkRZXq5eCgSV4bbnwS/TZ7liva5NmX4DXm7T5WnRtVsAM22heiPEdQTV9oWfL9qPmVWrL9Pu3XannQW/gZV/5xnAifBvB5cJAqOtKMRlRZCIjcRixr1xsSqSl8Tv1QNVI631O7lECftiEFA0Hcfsb/2FaGwOU1OTjE7j1EdkMuopdc7T28VKNTeDUpzSSPync+n3xWMoX2ZyVDZLaxW/l+lvfdY2Ln287nNv2rD5VX8T/JvZXrLGOtM2bbjlHwiMfyxWgIyfv85K+CA/dnO9TJUiE4AEKLq5NNcOsdGPA077io2sPk5Wx0F9Vr+uIutpu9ZyazqeNYbPb1jJdNojsMrKKshUtWabek9wb+s3ZDSti8cxTMVgwEqRWPtoKV6PTPtxKfBcHySoNEJ80X6GPe8LXoaxAlZzSRAgCVbINJdPcPnCjRs272VlfJhLQgBY6JJ8nMuv8WezYqEiA6niTleojsm1oj8/GUejgghJukUvAgE/QaM5ELzUYdabuxQNuj0uzd6MxoYGdC5ZalIKlWSq8jKr0VvndzgEJJ6DJxDPyd3phZZGU9H9aTYcgUmLrqN4TVzfT0D9OpcbCarFlMKLsFfKFfpYGddy+XVWwmYCShn703bP1m+8lauNXDR+/RBZ5zdisdg5RfaRnVmhxUV/yzTMLBLRiy8XokRty+Xp+qJmH/ViUCJTvxF76e1ZasDW36afOwFkjrPAjFoLNAJcaVkZ93v++Sruo+8Xlm9y2UA9ZX2xaC/KXhFgFY0AcxNUP7XnJL8/j0C4mstHqZccEvRFAIlRVJFFMMn0uQgkfjBZefVqUP91aaLid5rbQcJax9DEG76g5knXOEacdp9adLzioqjTaCq6UFlxe/Hzgov9GNd/QZZaBNUvaK8osF6MEVxdrLjPEBRXClhyhQKXKlqVqc8/3gyki6RTMtGhhLzeE63ZiyXm9fYHzR2qfRVBatyiBrQWQarfqtuzxjIWj6tzqZnGYjQLSPpcBLnWBNWjXN5x462bfoR9F+3F2WsKLLrECq5OsPI0KFaVmCewUlz75MJUoTJVfjE9UfxbYr7YQK1eDjKBQOCamZkxoBNLaRFAi8AsHpN7M7K0ospiTwqzD7nIzGbM3cw1UcvRRT5+6+Z3Xbbww0V7CfaKiPcXYwRVG1ebWXklqkAZK32CYPkoK3JQFV3crrVcnUAkcEgL+fz+WWXaJeiLro3AyPD3X2PU+Nfl5eWzAlWRdbQUTceTjtIxiy7ytGvkZwNAIYt/uF3uO3net5mdFu0l22sGLFb4Mi6HWImZhYp/mpsv4VqDOT+ysO1HAKEoUmkAj8ezh8D7K353SiCxgGL7Gpf337zp9js23fFrf0HAdHGfA2KhImiKxv3Mot8JrAKqYT4tlpYqaqqP2h2O99H9xRd+umgv0V4TYFFXOVjhAs/3uHyfm9TFeR2F/kmuP8O/T+fTFkBj1gufp1jhN/KrOD/fxOP8K48hH/npmzbeplmbjfHzNPe/nsvjxWNokQlsRRPgzvxbtnC+LxFcf3nThk1Wn51Fe1n2mgCLAMqx4i7nR/WyHGdFfl/bFr4bZ0XvZIWL0dRTNcr1LVxfx2UpAVOdLxQ2sPavIgddzfV9Nru9ndvFeD9i3DbA87yVAPkDLj8SnQpMZzKZ/taiIf52p+1xp9vxOzdt2rQY/b1C9pq5woU0xFsJmr8WqKi5SshkZqo7guAIXdoRVvoB/vlDrr/K5QtcBDCxyWf4Ow3oEAVN01UNavsL2S2b35XgsdQScHrksQHQGa7xTGNQ8DgZ8brF6O+VtWLI9JobgVVJsOQIsjA/f4Wfv83No1ymCIKzyT7fMDu+ROMxNeD2f8hStTy2cXdiLLlJmYDGz19jAfzxzZvf9Us9pdDrYa8nsP6WqxJW+FZW8le5vpJ/DywwmzHus5rb++kKlxAEh8gqv9BMLfx9NcH0cX58D3//HNeRbDYrdwq7zfYdMuW7CODTw90W7ZWz18wV/rgRMN/j6lwuQ/z8hwTU8TNBJeP2bq48XCv/9Xy3hhdpBM3krbfd8UGC6hoe43Kur3U4nVsIqH91ulw3L4Lql9SosX6NrPInC3/+hPH7Zi6LOaVFW7RFW7RFW7RFW7RFWzQA/z/lAHXVF/ascgAAAABJRU5ErkJggg==', +}; + +module.exports = device;