airfresher
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
ARG VERSION
|
||||
FROM acockburn/appdaemon:${VERSION}
|
||||
|
||||
#log encoding temporary fix
|
||||
RUN sed -i -e 's/safe_enc = lambda s: str(s).encode("utf-8", "replace").decode("ascii", "replace")/safe_enc = lambda s: str(s)/g' /usr/src/app/appdaemon/utils.py
|
||||
|
||||
RUN pip3 install \
|
||||
emp_mos_api==0.9 \
|
||||
emp_mos_api \
|
||||
sqlalchemy \
|
||||
mysqlclient
|
||||
@@ -4,7 +4,7 @@ import globals
|
||||
class KitchenLedController(hass.Hass):
|
||||
listen_event_handle_list = []
|
||||
def initialize(self):
|
||||
if not globals.check_args(self,['watch_entity']):
|
||||
if not globals.check_args(self,['watch_entity', 'topic']):
|
||||
return
|
||||
|
||||
self.listen_event_handle_list.append(self.listen_state(self.entity_changed, self.args['watch_entity']))
|
||||
@@ -15,4 +15,4 @@ class KitchenLedController(hass.Hass):
|
||||
stete = "off"
|
||||
if new == "on":
|
||||
stete = "on"
|
||||
self.call_service("mqtt/publish", topic = "home/kitchen-led/kitchen-led/state/set", payload = stete)
|
||||
self.call_service("mqtt/publish", topic = self.args['topic'], payload = stete)
|
||||
|
||||
@@ -5,5 +5,17 @@ kitchen_led_controller:
|
||||
module: kitchen_led_controller
|
||||
class: KitchenLedController
|
||||
watch_entity: light.kitchen
|
||||
topic: home/kitchen-led/kitchen-led/state/set
|
||||
global_dependencies:
|
||||
- globals
|
||||
|
||||
#################################
|
||||
# LIGHT BUTTONS #
|
||||
#################################
|
||||
kitchen_mini_light_controller:
|
||||
module: kitchen_led_controller
|
||||
class: KitchenLedController
|
||||
watch_entity: light.kitchen
|
||||
topic: home/plug/kitchen-mini-light/status/set
|
||||
global_dependencies:
|
||||
- globals
|
||||
|
||||
@@ -48,4 +48,4 @@ class ReportByPresence(hass.Hass):
|
||||
message = "В доме остались работать следующие устройства:\n" + message
|
||||
self.notify(message, name = self.args['notify'])
|
||||
else:
|
||||
self.notify("Все устройства в доме выключены.", name = self.args['notify'])
|
||||
self.notify("Все устройства в доме выключены.", name = self.args['notify'])
|
||||
|
||||
@@ -17,12 +17,16 @@ import appdaemon.plugins.hass.hassapi as hass
|
||||
class WaterValveControl(hass.Hass):
|
||||
standby_power_limit = 15
|
||||
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(self.away_mode, "away_mode")
|
||||
self.listen_event(self.return_home_mode, "return_home_mode")
|
||||
|
||||
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):
|
||||
@@ -47,7 +51,7 @@ class WaterValveControl(hass.Hass):
|
||||
device_names += self.friendly_name(device)
|
||||
self.notify('В доме остались требующие воды устройства ({}), подача воды не будет отключена.'.format(device_names), name = self.args['notify'])
|
||||
self.cancel_current_timer()
|
||||
self.timer = self.run_every(self.wait_when_device_is_done, self.datetime(), 5*60)
|
||||
self.timers.append(self.run_every(self.wait_when_device_is_done, self.datetime(), 5*60))
|
||||
else:
|
||||
self.turn_off(self.args['water_valve'])
|
||||
for device in self.args['water_devices']:
|
||||
@@ -83,7 +87,7 @@ class WaterValveControl(hass.Hass):
|
||||
return powered_on_devices
|
||||
|
||||
def cancel_current_timer(self):
|
||||
if self.timer != None:
|
||||
for timer in self.timers:
|
||||
self.log('canceling timer')
|
||||
self.cancel_timer(self.timer)
|
||||
self.timer = None
|
||||
self.cancel_timer(timer)
|
||||
self.timers = []
|
||||
|
||||
@@ -6,6 +6,7 @@ lock:
|
||||
user_65537: Данил (сред)
|
||||
user_65538: Света (указ)
|
||||
user_65539: Мама (указ)
|
||||
user_65540: Саня
|
||||
notify: telegram
|
||||
global_dependencies:
|
||||
- globals
|
||||
@@ -1,628 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
import requests
|
||||
from datetime import datetime, tzinfo
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class AuthException(Exception):
|
||||
pass
|
||||
|
||||
class EmpServerException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""
|
||||
Клиента
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
self.token = kwargs.get('token')
|
||||
self.guid = kwargs.get('guid')
|
||||
|
||||
#requests
|
||||
self.verify = kwargs.get('verify', True)
|
||||
self.timeout = kwargs.get('timeout', 3.0)
|
||||
|
||||
self.session_id = None # emp mos ru
|
||||
self.user_agent = kwargs.get('user_agent')
|
||||
self.dev_app_version = kwargs.get('dev_app_version')
|
||||
self.dev_user_agent = kwargs.get('dev_user_agent')
|
||||
self.headers = {'Cache-Control': 'no-cache',
|
||||
'Host': 'emp.mos.ru',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'User-Agent': self.user_agent}
|
||||
self.pheaders = deepcopy(self.headers)
|
||||
self.pheaders.update({
|
||||
'X-Cache-ov': '15552000',
|
||||
'X-Cache-ov-mode': 'FORCE_NETWORK'
|
||||
})
|
||||
self.session = requests.Session()
|
||||
|
||||
def raise_for_status(self, answer):
|
||||
"""
|
||||
:param answer: JSON
|
||||
{
|
||||
u'errorCode': 0, # 0-OK,
|
||||
401-Auth error
|
||||
u'execTime': 0.138262,
|
||||
u'errorMessage': u'', # error message
|
||||
u'session_id': u'6c3333333c33333e44e21e7d43c46e03',
|
||||
u'result': None or JSON
|
||||
}
|
||||
:return:
|
||||
"""
|
||||
# print('Exec time:', answer['execTime'])
|
||||
if answer['errorCode'] == 401:
|
||||
raise AuthException()
|
||||
|
||||
if answer['errorCode'] != 0:
|
||||
if answer['errorMessage']:
|
||||
raise EmpServerException(u'{0} (code:{1})'.format(answer['errorMessage'], answer['errorCode']))
|
||||
raise EmpServerException(u'code:{0}'.format(answer['errorCode']))
|
||||
|
||||
def is_active(self):
|
||||
"""
|
||||
:return: True если уже залогинился
|
||||
"""
|
||||
return self.session_id is not None
|
||||
|
||||
def login(self, telephone, pwd):
|
||||
"""
|
||||
:param telephone: Телефон. Вид: 7xxxxxxxxxx
|
||||
:param pwd: Пароль из приложения Госуслуги Москвы
|
||||
:return: JSON
|
||||
{
|
||||
u'is_filled': True,
|
||||
u'surname': u'x',
|
||||
u'name': u'x',
|
||||
u'session_id': u'6c3333333c33333e44e21e7d43c46e03'},
|
||||
u'request_id': u'UN=PRO-12345678-1234-1234-1234-123456789123'
|
||||
}
|
||||
"""
|
||||
login_data = {
|
||||
'device_info': {
|
||||
'guid': self.guid,
|
||||
'user_agent': self.dev_user_agent,
|
||||
'app_version': self.dev_app_version
|
||||
},
|
||||
'auth': {
|
||||
'login': telephone,
|
||||
'password': pwd,
|
||||
'guid': self.guid
|
||||
}
|
||||
}
|
||||
|
||||
ret = self.session.post('https://emp.mos.ru/v1.0/auth/virtualLogin',
|
||||
params={'token': self.token},
|
||||
headers={'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'User-Agent': self.user_agent,
|
||||
'cache-control': 'no-cache',
|
||||
'Host': 'emp.mos.ru',
|
||||
'Accept': '*/*'},
|
||||
verify=self.verify,
|
||||
timeout=self.timeout,
|
||||
json=login_data)
|
||||
|
||||
response = ret.json()
|
||||
self.raise_for_status(response)
|
||||
self.session_id = response['session_id']
|
||||
return response['result']
|
||||
|
||||
def get_profile(self):
|
||||
"""
|
||||
:return: JSON
|
||||
{
|
||||
u'drive_license': None,
|
||||
u'firstname': u'x',
|
||||
u'middlename': u'x',
|
||||
u'lastname': u'x',
|
||||
u'birthdate': u'dd.mm.YYYY',
|
||||
u'msisdn': u'71234567890', # your telephone
|
||||
u'email_confirmed': True,
|
||||
u'email': u'x@x.ru'
|
||||
}
|
||||
"""
|
||||
assert self.session_id
|
||||
|
||||
ret = self.session.get('https://emp.mos.ru/v1.0/profile/get',
|
||||
params={'token': self.token,
|
||||
'info[guid]': self.guid,
|
||||
'auth[session_id]': self.session_id
|
||||
},
|
||||
headers=self.pheaders,
|
||||
verify=self.verify,
|
||||
timeout=self.timeout)
|
||||
|
||||
response = ret.json()
|
||||
self.raise_for_status(response)
|
||||
return response['result']['profile']
|
||||
|
||||
def get_flats(self):
|
||||
"""
|
||||
:return: JSON array
|
||||
[{
|
||||
u'name': u'x',
|
||||
u'paycode': u'1234567890',
|
||||
u'flat_id': u'1234567',
|
||||
u'electro_account': None,
|
||||
u'flat_number': u'0',
|
||||
u'unad': u'0',
|
||||
u'address': u'x',
|
||||
u'electro_device': None,
|
||||
u'unom': u'1234567' # Идентификатор дома
|
||||
},{...}
|
||||
]
|
||||
"""
|
||||
assert self.session_id
|
||||
|
||||
ret = self.session.get('https://emp.mos.ru/v1.0/flat/get',
|
||||
params={'token': self.token,
|
||||
'info[guid]': self.guid,
|
||||
'auth[session_id]': self.session_id
|
||||
},
|
||||
headers=self.pheaders,
|
||||
verify=self.verify,
|
||||
timeout = self.timeout)
|
||||
|
||||
response = ret.json()
|
||||
self.raise_for_status(response)
|
||||
return response['result']
|
||||
|
||||
def get_watercounters(self, flat_id):
|
||||
"""
|
||||
:param flat_id: unicode string from flat_response
|
||||
response[0]['flat_id']
|
||||
:return:
|
||||
{
|
||||
u'stat_title': u'x',
|
||||
u'archive': [
|
||||
{
|
||||
u'cold_indication': 1.55,
|
||||
u'hot_indication': 1.2,
|
||||
u'period': u'2017-08-31+03:00'
|
||||
}, {
|
||||
u'cold_indication': 1.3,
|
||||
u'hot_indication': 0.39999999999998,
|
||||
u'period': u'2017-09-30+03:00'
|
||||
}, {
|
||||
+10 эл-тов. Всего 12.
|
||||
}],
|
||||
u'counters': [
|
||||
{
|
||||
u'checkup': u'2021-08-08+03:00', # дата поверки
|
||||
u'counterId': 123456, # id счетчика в emp.mos.ru
|
||||
u'num': u'123456', # серийный номер счетчика
|
||||
u'type': 1, # ХВС
|
||||
u'indications': [
|
||||
{
|
||||
u'indication': u'200.24',
|
||||
u'period': u'2018-07-31+03:00'
|
||||
}, {
|
||||
u'indication': u'200.6',
|
||||
u'period': u'2018-06-30+03:00'
|
||||
}, {
|
||||
u'indication': u'200.5',
|
||||
u'period': u'2018-05-31+03:00'
|
||||
}, {
|
||||
u'indication': u'200.5',
|
||||
u'period': u'2018-04-30+03:00'
|
||||
}]
|
||||
},
|
||||
{..}
|
||||
]
|
||||
}
|
||||
"""
|
||||
assert self.session_id
|
||||
wheaders = deepcopy(self.headers)
|
||||
wheaders.update({
|
||||
'X-Clears-tags': 'WATER_COUNTERS',
|
||||
'X-Cache-ov-mode': 'DEFAULT',
|
||||
'Content-Type': 'application/json; charset=UTF-8' })
|
||||
|
||||
wcrequest = {
|
||||
'flat_id': flat_id,
|
||||
'is_widget': False,
|
||||
'info': {
|
||||
'guid': self.guid
|
||||
},
|
||||
'auth': {
|
||||
'session_id': self.session_id
|
||||
}
|
||||
}
|
||||
|
||||
ret = self.session.post('https://emp.mos.ru/v1.0/watercounters/get',
|
||||
params={'token': self.token},
|
||||
headers=wheaders,
|
||||
verify=self.verify,
|
||||
timeout=self.timeout,
|
||||
json=wcrequest)
|
||||
|
||||
response = ret.json()
|
||||
self.raise_for_status(response)
|
||||
return response['result']
|
||||
|
||||
def send_watercounters(self, flat_id, counters_data):
|
||||
"""
|
||||
:param flat_id: flat_response['flat_id']
|
||||
:param counters_data: array of
|
||||
[{
|
||||
'counter_id': u'123456', # ['counters'][0]['counterId']
|
||||
'period': datetime.now().strftime("%Y-%m-%d"),
|
||||
'indication': 'xxx,xx'
|
||||
}, {
|
||||
...
|
||||
}]
|
||||
:return:
|
||||
"""
|
||||
assert self.session_id
|
||||
|
||||
wheaders = deepcopy(self.headers)
|
||||
wheaders.update({
|
||||
'X-Clears-tags': 'WATER_COUNTERS',
|
||||
'Content-Type': 'application/json; charset=UTF-8'
|
||||
})
|
||||
|
||||
wcrequest = {'flat_id': flat_id,
|
||||
'counters_data': counters_data,
|
||||
'info': {
|
||||
'guid': self.guid
|
||||
},
|
||||
'auth': {
|
||||
'session_id': self.session_id
|
||||
}}
|
||||
|
||||
ret = self.session.post('https://emp.mos.ru/v1.0/watercounters/addValues',
|
||||
params={'token': self.token},
|
||||
headers=wheaders,
|
||||
verify=self.verify,
|
||||
timeout=self.timeout,
|
||||
json=wcrequest)
|
||||
|
||||
response = ret.json()
|
||||
self.raise_for_status(response)
|
||||
return response['result']
|
||||
|
||||
def get_electrocounters(self, flat_id):
|
||||
"""
|
||||
:param flat_id: unicode string from flat_response
|
||||
response[0]['flat_id']
|
||||
:return:
|
||||
{
|
||||
"address": "город ...",
|
||||
"electro_account": "851xxxx475",
|
||||
"electro_device": "04xxx17",
|
||||
"balance": 0,
|
||||
"is_debt": false,
|
||||
"description": "Внимание! Передача показаний возможна с 15 по 26 число месяца. При вводе текущих показаний обращайте внимание на значность счетчиков. В случае если показания введены с неверной значностью (большей, чем значность счетчика), они не будут загружены.",
|
||||
"sh_znk": 5,
|
||||
"zones": [{
|
||||
"name": "T1",
|
||||
"value": "6887"
|
||||
}],
|
||||
"intervals": [{
|
||||
"name": "T1",
|
||||
"value": "00:00-23:59"
|
||||
}]
|
||||
}
|
||||
"""
|
||||
assert self.session_id
|
||||
wheaders = deepcopy(self.headers)
|
||||
wheaders.update({
|
||||
'X-Clears-tags': 'ELECTRO_COUNTERS',
|
||||
'X-Cache-ov-mode': 'DEFAULT',
|
||||
'Content-Type': 'application/json; charset=UTF-8' })
|
||||
|
||||
wcrequest = {
|
||||
'flat_id': flat_id,
|
||||
'is_widget': False,
|
||||
'info': {
|
||||
'guid': self.guid,
|
||||
'user_agent': self.dev_user_agent,
|
||||
'app_version': self.dev_app_version
|
||||
},
|
||||
'auth': {
|
||||
'session_id': self.session_id
|
||||
}
|
||||
}
|
||||
|
||||
ret = self.session.post('https://emp.mos.ru/v1.0/electrocounters/get',
|
||||
params={'token': self.token},
|
||||
headers=wheaders,
|
||||
verify=self.verify,
|
||||
timeout=self.timeout,
|
||||
json=wcrequest)
|
||||
|
||||
response = ret.json()
|
||||
self.raise_for_status(response)
|
||||
return response['result']
|
||||
|
||||
def send_electrocounters(self, flat_id, counters_data):
|
||||
"""
|
||||
:param flat_id: flat_response['flat_id']
|
||||
:param counters_data: array of
|
||||
[{
|
||||
'counter_id': u'123456', # ['counters'][0]['counterId']
|
||||
'period': datetime.now().strftime("%Y-%m-%d"),
|
||||
'indication': 'xxx,xx'
|
||||
}, {
|
||||
...
|
||||
}]
|
||||
:return:
|
||||
"""
|
||||
assert self.session_id
|
||||
|
||||
wheaders = deepcopy(self.headers)
|
||||
wheaders.update({
|
||||
'X-Clears-tags': 'ELECTRO_COUNTERS',
|
||||
'Content-Type': 'application/json; charset=UTF-8'
|
||||
})
|
||||
|
||||
wcrequest = {'flat_id': flat_id,
|
||||
'counters_data': counters_data,
|
||||
'info': {
|
||||
'guid': self.guid,
|
||||
'user_agent': self.dev_user_agent,
|
||||
'app_version': self.dev_app_version
|
||||
},
|
||||
'auth': {
|
||||
'session_id': self.session_id
|
||||
}}
|
||||
ret = self.session.post('https://emp.mos.ru/v1.0/electrocounters/addValues',
|
||||
params={'token': self.token},
|
||||
headers=wheaders,
|
||||
verify=self.verify,
|
||||
timeout=self.timeout,
|
||||
json=wcrequest)
|
||||
|
||||
response = ret.json()
|
||||
self.raise_for_status(response)
|
||||
return response['result']
|
||||
|
||||
def get_epd(self, flat_id, period, is_debt=True):
|
||||
"""
|
||||
:param flat_id: unicode string from flat_response
|
||||
response[0]['flat_id']
|
||||
:param period: unicode string represents date in 27.09.2018 format
|
||||
:param is_debt: True/False
|
||||
:return:
|
||||
{
|
||||
"is_debt": true,
|
||||
"is_paid": true,
|
||||
"amount": 2982.77,
|
||||
"service_code": "emp.zkh",
|
||||
"insurance": 61.93,
|
||||
"ammount_insurance": 3044.7
|
||||
}
|
||||
"""
|
||||
assert self.session_id
|
||||
wheaders = deepcopy(self.headers)
|
||||
wheaders.update({
|
||||
'X-Clears-tags': 'EPD',
|
||||
'X-Cache-ov-mode': 'DEFAULT',
|
||||
'Content-Type': 'application/json; charset=UTF-8' })
|
||||
|
||||
wcrequest = {
|
||||
'flat_id': flat_id,
|
||||
'period': period,
|
||||
'is_debt': is_debt,
|
||||
'info': {
|
||||
'guid': self.guid
|
||||
},
|
||||
'auth': {
|
||||
'session_id': self.session_id
|
||||
}
|
||||
}
|
||||
|
||||
ret = self.session.post('https://emp.mos.ru/v1.1/epd/get',
|
||||
params={'token': self.token},
|
||||
headers=wheaders,
|
||||
verify=self.verify,
|
||||
timeout=self.timeout,
|
||||
json=wcrequest)
|
||||
|
||||
response = ret.json()
|
||||
self.raise_for_status(response)
|
||||
return response['result']
|
||||
|
||||
def get_car_fines(self, sts_number):
|
||||
"""
|
||||
:param sts_number: unicode string contains car sts_numer
|
||||
:return:
|
||||
{
|
||||
"paid": [{
|
||||
"seriesAndNumber": "xxxxx",
|
||||
"date": "2018-08-30+03:00",
|
||||
"offence_place": "МОСКВА Г. МКАД,xxxx, ВНЕШНЯЯ СТОРОНА",
|
||||
"offenceType": "12.9ч.2 - Превышение установленной скорости движения транспортного средства на величину от 20 до 40 километров в час ",
|
||||
"cost": "500",
|
||||
"is_discount": false,
|
||||
"drive_license": null,
|
||||
"sts_number": "xxxxxxx",
|
||||
"executionState": "Исполнено",
|
||||
"is_fssp": false
|
||||
}],
|
||||
"unpaid": []
|
||||
}
|
||||
"""
|
||||
assert self.session_id
|
||||
wheaders = deepcopy(self.headers)
|
||||
wheaders.update({
|
||||
'X-Clears-tags': 'FORCE_NETWORK',
|
||||
'X-Cache-ov-mode': 'DEFAULT',
|
||||
'Content-Type': 'application/json; charset=UTF-8' })
|
||||
|
||||
wcrequest = {
|
||||
'sts_number': sts_number,
|
||||
'info': {
|
||||
'guid': self.guid
|
||||
},
|
||||
'auth': {
|
||||
'session_id': self.session_id
|
||||
}
|
||||
}
|
||||
|
||||
ret = self.session.post('https://emp.mos.ru/v1.0/offence/getOffence',
|
||||
params={'token': self.token},
|
||||
headers=wheaders,
|
||||
verify=self.verify,
|
||||
timeout=self.timeout,
|
||||
json=wcrequest)
|
||||
|
||||
response = ret.json()
|
||||
self.raise_for_status(response)
|
||||
return response['result']
|
||||
|
||||
def logout(self, timeout=None):
|
||||
"""
|
||||
Почему то очень долго выполняется (5 сек)
|
||||
"""
|
||||
if self.session_id:
|
||||
logout_data = {
|
||||
'info': {
|
||||
'guid': self.guid
|
||||
},
|
||||
'auth': {
|
||||
'session_id': self.session_id
|
||||
}
|
||||
}
|
||||
ret = self.session.post('https://emp.mos.ru/v1.0/auth/logout',
|
||||
params={'token': self.token},
|
||||
headers=self.headers,
|
||||
timeout=timeout or self.timeout,
|
||||
json=logout_data)
|
||||
|
||||
response = ret.json()
|
||||
self.raise_for_status(response)
|
||||
self.session_id = None
|
||||
return response['result']
|
||||
|
||||
|
||||
class MosAPI(object):
|
||||
"""
|
||||
kwargs:
|
||||
|
||||
token: уникальный ключ приложения
|
||||
guid: некий уникальный ключ
|
||||
https_verify: ключ verify в GET, POST запросах, по умолчанию 'False'
|
||||
timeout: ключ timeout в GET, POST запросах
|
||||
user_agent: версия веб клиента
|
||||
dev_user_agent: 'Android' для ОС Android
|
||||
dev_app_version: версия ОС
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
self._clients = {'default': Client(**kwargs)}
|
||||
|
||||
def client(self, client_id='default', **kwargs):
|
||||
if client_id and \
|
||||
client_id not in self._clients:
|
||||
k = self.kwargs
|
||||
k.update(kwargs)
|
||||
self._clients[client_id] = Client(**k)
|
||||
return self._clients[client_id]
|
||||
|
||||
# if only one client
|
||||
def is_active(self):
|
||||
return self.client().is_active()
|
||||
def login(self, *args):
|
||||
return self.client().login(*args)
|
||||
def logout(self, *args):
|
||||
return self.client().logout(*args)
|
||||
|
||||
def get_profile(self):
|
||||
return self.client().get_profile()
|
||||
def get_flats(self):
|
||||
return self.client().get_flats()
|
||||
def get_watercounters(self, *args):
|
||||
return self.client().get_watercounters(*args)
|
||||
def send_watercounters(self, *args):
|
||||
return self.client().send_watercounters(*args)
|
||||
def get_electrocounters(self, *args):
|
||||
return self.client().get_electrocounters(*args)
|
||||
def send_electrocounters(self, *args):
|
||||
return self.client().send_electrocounters(*args)
|
||||
def get_epd(self, *args):
|
||||
return self.client().get_epd(*args)
|
||||
def get_car_fines(self, *args):
|
||||
return self.client().get_car_fines(*args)
|
||||
|
||||
class Water():
|
||||
COLD = 1
|
||||
HOT = 2
|
||||
|
||||
@staticmethod
|
||||
def water_abbr(water):
|
||||
if water == Water.COLD: return u'ХВС'
|
||||
elif water == Water.HOT: return u'ГВС'
|
||||
|
||||
@staticmethod
|
||||
def name(water):
|
||||
if water == Water.COLD: return u'холодная вода'
|
||||
elif water == Water.HOT: return u'горячая вода'
|
||||
|
||||
|
||||
class Watercounter():
|
||||
"""
|
||||
[{ 'counterId': 1437373, # внутреннее id счетчика
|
||||
'type': 1, # тип воды
|
||||
'num': '417944', # серийный номер счетчика
|
||||
'checkup': '2023-09-25+03:00', # дата следующей поверки
|
||||
'indications':
|
||||
[{'period': '2018-08-31+03:00', 'indication': '21.38'},
|
||||
{'period': '2018-07-31+03:00', 'indication': '20.7'},
|
||||
{'period': '2018-06-30+03:00', 'indication': '19'}]
|
||||
},
|
||||
{...}
|
||||
]
|
||||
|
||||
list(filter(lambda x: x['num'] == num, response['counters']))
|
||||
list(filter(lambda x: x['counterId'] == id, response['counters']))
|
||||
list(filter(lambda x: x['type'] == water_type_id, response['counters']))
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def last_value(counter):
|
||||
"""
|
||||
alphabetical sort data =)
|
||||
:param counter: counter JSON
|
||||
:return: float or None
|
||||
"""
|
||||
indications = counter['indications']
|
||||
indications.sort(key=lambda x: x['period'])
|
||||
if indications:
|
||||
return float(indications[-1]['indication'])
|
||||
|
||||
@staticmethod
|
||||
def water_title(counter):
|
||||
return Watercounter.humanreadable_name(counter['type'])
|
||||
|
||||
@staticmethod
|
||||
def checkup(counter):
|
||||
"""
|
||||
https://docs.python.org/3/library/datetime.html#datetime.timezone
|
||||
Если нужно UTC d.replace(tzinfo=None)
|
||||
|
||||
:param counter: counter JSON
|
||||
:return: datetime с временной зоной
|
||||
"""
|
||||
checkup = counter['checkup']
|
||||
d = datetime.strptime(checkup, '%Y-%m-%d%z') #'checkup': '2023-09-25+03:00'
|
||||
return d
|
||||
|
||||
@staticmethod
|
||||
def serialize_for_send(counter, value):
|
||||
"""
|
||||
:param counterId: id счетчика. не номер из приложения!
|
||||
:param value: значение float
|
||||
:return: dict
|
||||
"""
|
||||
return {
|
||||
'counter_id': int(counter['counterId']),
|
||||
'period': datetime.now().strftime("%Y-%m-%d"),
|
||||
'indication': '{:.2f}'.format(value).replace('.', ',')
|
||||
}
|
||||
@@ -2,7 +2,8 @@ import globals
|
||||
import datetime as dt
|
||||
from automation import Automation, Base
|
||||
from datetime import datetime, tzinfo, timedelta
|
||||
from mos_lib import MosAPI, Watercounter, Water, EmpServerException
|
||||
from emp_mos_api.mos import MosAPI, Watercounter, Water, EmpServerException
|
||||
#from mos_lib import MosAPI, Watercounter, Water, EmpServerException
|
||||
|
||||
class WaterCounterSender(Automation):
|
||||
timezone = '+03:00'
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
db:
|
||||
power_history:
|
||||
module: db
|
||||
class: Db
|
||||
connectionstring: !secret mysql_url
|
||||
entity_id: sensor.power_total
|
||||
|
||||
debug: true
|
||||
global_dependencies:
|
||||
- automation
|
||||
|
||||
|
||||
|
||||
counter_hot_history:
|
||||
module: db
|
||||
class: Db
|
||||
connectionstring: !secret mysql_url
|
||||
entity_id: sensor.counter_hot
|
||||
|
||||
debug: true
|
||||
global_dependencies:
|
||||
- automation
|
||||
|
||||
counter_cold_history:
|
||||
module: db
|
||||
class: Db
|
||||
connectionstring: !secret mysql_url
|
||||
entity_id: sensor.counter_cold
|
||||
|
||||
debug: true
|
||||
global_dependencies:
|
||||
- automation
|
||||
@@ -0,0 +1,50 @@
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
import globals
|
||||
import datetime
|
||||
#
|
||||
# Entity timer
|
||||
# Turn on and off this entities
|
||||
#
|
||||
# Args:
|
||||
#
|
||||
# None
|
||||
#
|
||||
# Release Notes
|
||||
#
|
||||
# Version 1.0:
|
||||
# Initial Version
|
||||
|
||||
class AirFresher(hass.Hass):
|
||||
timers = []
|
||||
fresh_times = 0
|
||||
|
||||
def initialize(self):
|
||||
self.listen_state(self.light_change, 'group.bath_light')
|
||||
|
||||
def light_change(self, entity, attribute, old, new, kwargs):
|
||||
if (old == "on" and new == "off"):
|
||||
self.log('Stop freshing timer.')
|
||||
self.cancel_timers()
|
||||
|
||||
if (old == "off" and new == "on"):
|
||||
self.log('Start freshing timer.')
|
||||
self.fresh_times = 0
|
||||
newtime = self.datetime()+datetime.timedelta(minutes=3)
|
||||
self.timers.append(self.run_every(self.timer_tick, newtime, 5*60))
|
||||
|
||||
def timer_tick(self, args) -> None:
|
||||
self.fresh()
|
||||
|
||||
def cancel_timers(self) -> None:
|
||||
for timer in self.timers:
|
||||
self.cancel_timer(timer)
|
||||
timers = []
|
||||
|
||||
def fresh(self):
|
||||
self.fresh_times = self.fresh_times + 1
|
||||
if (self.fresh_times > 5):
|
||||
self.log('Fresh max times is reached. Canceling timer.')
|
||||
self.cancel_timers()
|
||||
return
|
||||
self.log('freshing in {}'.format(self.fresh_times))
|
||||
self.call_service("mqtt/publish", topic = "home/airfresher/airfresher/fresh/set", payload = "1")
|
||||
@@ -0,0 +1,8 @@
|
||||
airfresher:
|
||||
module: airfresher
|
||||
class: AirFresher
|
||||
# on_time: "09:00:00"
|
||||
# off_time: "22:00:00"
|
||||
# entity: switch.ledstrip
|
||||
global_dependencies:
|
||||
- globals
|
||||
+2
-19
@@ -33,7 +33,6 @@ services:
|
||||
depends_on:
|
||||
- mysql
|
||||
- mosquitto
|
||||
- influxdb
|
||||
- broadlink
|
||||
network_mode: "host"
|
||||
restart: always
|
||||
@@ -45,7 +44,7 @@ services:
|
||||
appdaemon:
|
||||
image: monster1025/appdaemon
|
||||
build:
|
||||
context: ./appdaemon
|
||||
context: ./appdaemon/docker
|
||||
args:
|
||||
VERSION: 3.0.2
|
||||
container_name: appdaemon
|
||||
@@ -135,7 +134,7 @@ services:
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=grafanapass
|
||||
links:
|
||||
- "influxdb:influxdb"
|
||||
- "mysql:mysql"
|
||||
ports:
|
||||
- "3000:3000"
|
||||
restart: always
|
||||
@@ -143,19 +142,3 @@ services:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: 50m
|
||||
|
||||
influxdb:
|
||||
image: influxdb
|
||||
container_name: influxdb
|
||||
volumes:
|
||||
- "/etc/localtime:/etc/localtime:ro"
|
||||
- "/srv/influxdb/db:/var/lib/influxdb"
|
||||
- "/srv/influxdb/log:/var/log/influxdb"
|
||||
ports:
|
||||
- "8083:8083"
|
||||
- "8086:8086"
|
||||
restart: always
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: 50m
|
||||
|
||||
@@ -50,7 +50,6 @@ switch: !include_dir_merge_list configuration/switch
|
||||
tts: !include_dir_merge_list configuration/tts
|
||||
|
||||
#one item
|
||||
influxdb: !include configuration/influxdb.yaml
|
||||
mqtt: !include configuration/mqtt/mqtt.yaml
|
||||
telegram_bot: !include configuration/bot/telegram_bot.yaml
|
||||
vacuum: !include configuration/xiaomi_aqara/vacuum.yaml
|
||||
|
||||
@@ -3,4 +3,4 @@ kitchen_light:
|
||||
control: hidden
|
||||
entities:
|
||||
- light.kitchen
|
||||
- switch.kitchen_mini_light
|
||||
#- switch.kitchen_mini_light
|
||||
@@ -4,4 +4,5 @@
|
||||
entities:
|
||||
- sensor.temperature_158d00012d9ad7
|
||||
- sensor.humidity_158d00012d9ad7
|
||||
- light.bath
|
||||
- light.bath
|
||||
- sensor.airfresher_remain
|
||||
@@ -1,12 +0,0 @@
|
||||
host: !secret base_localname
|
||||
port: 8086
|
||||
database: hass
|
||||
max_retries: 3
|
||||
default_measurement: state
|
||||
|
||||
include:
|
||||
domains:
|
||||
- sensor
|
||||
- binary_sensor
|
||||
- switch
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#################################################
|
||||
# AirFresher #
|
||||
#################################################
|
||||
- platform: mqtt
|
||||
state_topic: 'home/airfresher/airfresher/remain'
|
||||
name: 'airfresher_remain'
|
||||
# value_template: '{{ (value | float ) / 1000 }}'
|
||||
unit_of_measurement: 'times'
|
||||
|
||||
- platform: template
|
||||
sensors:
|
||||
airfresher_remain_percent:
|
||||
value_template: "{{ ((100 / 1400) * (states('sensor.airfresher_remain') | int )) | round(2) }}"
|
||||
@@ -1,4 +0,0 @@
|
||||
# - platform: template
|
||||
# sensors:
|
||||
# lux_outside:
|
||||
# value_template: '{{0}}'
|
||||
@@ -2,7 +2,7 @@
|
||||
sensors:
|
||||
power_main_total:
|
||||
unit_of_measurement: 'kWh'
|
||||
value_template: "{{ states('sensor.power_total') | float + 4733.6 | round(2) }}"
|
||||
value_template: "{{ (states('sensor.power_total') | float + 4733.6) | round(2) }}"
|
||||
#4733.6 - разница показаний.
|
||||
|
||||
- platform: modbus
|
||||
|
||||
Reference in New Issue
Block a user