This commit is contained in:
monster
2018-12-30 14:13:57 +03:00
parent e311c31b88
commit 2033fe79e4
225 changed files with 6370 additions and 0 deletions
+628
View File
@@ -0,0 +1,628 @@
# -*- coding: utf-8 -*-
from __future__ import print_function
import requests
from datetime import datetime, tzinfo
from copy import deepcopy
class AuthException(Exception):
pass
class EmpServerException(Exception):
pass
class Client(object):
"""
Клиента
"""
def __init__(self, **kwargs):
self.token = kwargs.get('token')
self.guid = kwargs.get('guid')
#requests
self.verify = kwargs.get('verify', True)
self.timeout = kwargs.get('timeout', 3.0)
self.session_id = None # emp mos ru
self.user_agent = kwargs.get('user_agent')
self.dev_app_version = kwargs.get('dev_app_version')
self.dev_user_agent = kwargs.get('dev_user_agent')
self.headers = {'Cache-Control': 'no-cache',
'Host': 'emp.mos.ru',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
'User-Agent': self.user_agent}
self.pheaders = deepcopy(self.headers)
self.pheaders.update({
'X-Cache-ov': '15552000',
'X-Cache-ov-mode': 'FORCE_NETWORK'
})
self.session = requests.Session()
def raise_for_status(self, answer):
"""
:param answer: JSON
{
u'errorCode': 0, # 0-OK,
401-Auth error
u'execTime': 0.138262,
u'errorMessage': u'', # error message
u'session_id': u'6c3333333c33333e44e21e7d43c46e03',
u'result': None or JSON
}
:return:
"""
# print('Exec time:', answer['execTime'])
if answer['errorCode'] == 401:
raise AuthException()
if answer['errorCode'] != 0:
if answer['errorMessage']:
raise EmpServerException(u'{0} (code:{1})'.format(answer['errorMessage'], answer['errorCode']))
raise EmpServerException(u'code:{0}'.format(answer['errorCode']))
def is_active(self):
"""
:return: True если уже залогинился
"""
return self.session_id is not None
def login(self, telephone, pwd):
"""
:param telephone: Телефон. Вид: 7xxxxxxxxxx
:param pwd: Пароль из приложения Госуслуги Москвы
:return: JSON
{
u'is_filled': True,
u'surname': u'x',
u'name': u'x',
u'session_id': u'6c3333333c33333e44e21e7d43c46e03'},
u'request_id': u'UN=PRO-12345678-1234-1234-1234-123456789123'
}
"""
login_data = {
'device_info': {
'guid': self.guid,
'user_agent': self.dev_user_agent,
'app_version': self.dev_app_version
},
'auth': {
'login': telephone,
'password': pwd,
'guid': self.guid
}
}
ret = self.session.post('https://emp.mos.ru/v1.0/auth/virtualLogin',
params={'token': self.token},
headers={'Content-Type': 'application/json; charset=UTF-8',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
'User-Agent': self.user_agent,
'cache-control': 'no-cache',
'Host': 'emp.mos.ru',
'Accept': '*/*'},
verify=self.verify,
timeout=self.timeout,
json=login_data)
response = ret.json()
self.raise_for_status(response)
self.session_id = response['session_id']
return response['result']
def get_profile(self):
"""
:return: JSON
{
u'drive_license': None,
u'firstname': u'x',
u'middlename': u'x',
u'lastname': u'x',
u'birthdate': u'dd.mm.YYYY',
u'msisdn': u'71234567890', # your telephone
u'email_confirmed': True,
u'email': u'x@x.ru'
}
"""
assert self.session_id
ret = self.session.get('https://emp.mos.ru/v1.0/profile/get',
params={'token': self.token,
'info[guid]': self.guid,
'auth[session_id]': self.session_id
},
headers=self.pheaders,
verify=self.verify,
timeout=self.timeout)
response = ret.json()
self.raise_for_status(response)
return response['result']['profile']
def get_flats(self):
"""
:return: JSON array
[{
u'name': u'x',
u'paycode': u'1234567890',
u'flat_id': u'1234567',
u'electro_account': None,
u'flat_number': u'0',
u'unad': u'0',
u'address': u'x',
u'electro_device': None,
u'unom': u'1234567' # Идентификатор дома
},{...}
]
"""
assert self.session_id
ret = self.session.get('https://emp.mos.ru/v1.0/flat/get',
params={'token': self.token,
'info[guid]': self.guid,
'auth[session_id]': self.session_id
},
headers=self.pheaders,
verify=self.verify,
timeout = self.timeout)
response = ret.json()
self.raise_for_status(response)
return response['result']
def get_watercounters(self, flat_id):
"""
:param flat_id: unicode string from flat_response
response[0]['flat_id']
:return:
{
u'stat_title': u'x',
u'archive': [
{
u'cold_indication': 1.55,
u'hot_indication': 1.2,
u'period': u'2017-08-31+03:00'
}, {
u'cold_indication': 1.3,
u'hot_indication': 0.39999999999998,
u'period': u'2017-09-30+03:00'
}, {
+10 эл-тов. Всего 12.
}],
u'counters': [
{
u'checkup': u'2021-08-08+03:00', # дата поверки
u'counterId': 123456, # id счетчика в emp.mos.ru
u'num': u'123456', # серийный номер счетчика
u'type': 1, # ХВС
u'indications': [
{
u'indication': u'200.24',
u'period': u'2018-07-31+03:00'
}, {
u'indication': u'200.6',
u'period': u'2018-06-30+03:00'
}, {
u'indication': u'200.5',
u'period': u'2018-05-31+03:00'
}, {
u'indication': u'200.5',
u'period': u'2018-04-30+03:00'
}]
},
{..}
]
}
"""
assert self.session_id
wheaders = deepcopy(self.headers)
wheaders.update({
'X-Clears-tags': 'WATER_COUNTERS',
'X-Cache-ov-mode': 'DEFAULT',
'Content-Type': 'application/json; charset=UTF-8' })
wcrequest = {
'flat_id': flat_id,
'is_widget': False,
'info': {
'guid': self.guid
},
'auth': {
'session_id': self.session_id
}
}
ret = self.session.post('https://emp.mos.ru/v1.0/watercounters/get',
params={'token': self.token},
headers=wheaders,
verify=self.verify,
timeout=self.timeout,
json=wcrequest)
response = ret.json()
self.raise_for_status(response)
return response['result']
def send_watercounters(self, flat_id, counters_data):
"""
:param flat_id: flat_response['flat_id']
:param counters_data: array of
[{
'counter_id': u'123456', # ['counters'][0]['counterId']
'period': datetime.now().strftime("%Y-%m-%d"),
'indication': 'xxx,xx'
}, {
...
}]
:return:
"""
assert self.session_id
wheaders = deepcopy(self.headers)
wheaders.update({
'X-Clears-tags': 'WATER_COUNTERS',
'Content-Type': 'application/json; charset=UTF-8'
})
wcrequest = {'flat_id': flat_id,
'counters_data': counters_data,
'info': {
'guid': self.guid
},
'auth': {
'session_id': self.session_id
}}
ret = self.session.post('https://emp.mos.ru/v1.0/watercounters/addValues',
params={'token': self.token},
headers=wheaders,
verify=self.verify,
timeout=self.timeout,
json=wcrequest)
response = ret.json()
self.raise_for_status(response)
return response['result']
def get_electrocounters(self, flat_id):
"""
:param flat_id: unicode string from flat_response
response[0]['flat_id']
:return:
{
"address": "город ...",
"electro_account": "851xxxx475",
"electro_device": "04xxx17",
"balance": 0,
"is_debt": false,
"description": "Внимание! Передача показаний возможна с 15 по 26 число месяца. При вводе текущих показаний обращайте внимание на значность счетчиков. В случае если показания введены с неверной значностью (большей, чем значность счетчика), они не будут загружены.",
"sh_znk": 5,
"zones": [{
"name": "T1",
"value": "6887"
}],
"intervals": [{
"name": "T1",
"value": "00:00-23:59"
}]
}
"""
assert self.session_id
wheaders = deepcopy(self.headers)
wheaders.update({
'X-Clears-tags': 'ELECTRO_COUNTERS',
'X-Cache-ov-mode': 'DEFAULT',
'Content-Type': 'application/json; charset=UTF-8' })
wcrequest = {
'flat_id': flat_id,
'is_widget': False,
'info': {
'guid': self.guid,
'user_agent': self.dev_user_agent,
'app_version': self.dev_app_version
},
'auth': {
'session_id': self.session_id
}
}
ret = self.session.post('https://emp.mos.ru/v1.0/electrocounters/get',
params={'token': self.token},
headers=wheaders,
verify=self.verify,
timeout=self.timeout,
json=wcrequest)
response = ret.json()
self.raise_for_status(response)
return response['result']
def send_electrocounters(self, flat_id, counters_data):
"""
:param flat_id: flat_response['flat_id']
:param counters_data: array of
[{
'counter_id': u'123456', # ['counters'][0]['counterId']
'period': datetime.now().strftime("%Y-%m-%d"),
'indication': 'xxx,xx'
}, {
...
}]
:return:
"""
assert self.session_id
wheaders = deepcopy(self.headers)
wheaders.update({
'X-Clears-tags': 'ELECTRO_COUNTERS',
'Content-Type': 'application/json; charset=UTF-8'
})
wcrequest = {'flat_id': flat_id,
'counters_data': counters_data,
'info': {
'guid': self.guid,
'user_agent': self.dev_user_agent,
'app_version': self.dev_app_version
},
'auth': {
'session_id': self.session_id
}}
ret = self.session.post('https://emp.mos.ru/v1.0/electrocounters/addValues',
params={'token': self.token},
headers=wheaders,
verify=self.verify,
timeout=self.timeout,
json=wcrequest)
response = ret.json()
self.raise_for_status(response)
return response['result']
def get_epd(self, flat_id, period, is_debt=True):
"""
:param flat_id: unicode string from flat_response
response[0]['flat_id']
:param period: unicode string represents date in 27.09.2018 format
:param is_debt: True/False
:return:
{
"is_debt": true,
"is_paid": true,
"amount": 2982.77,
"service_code": "emp.zkh",
"insurance": 61.93,
"ammount_insurance": 3044.7
}
"""
assert self.session_id
wheaders = deepcopy(self.headers)
wheaders.update({
'X-Clears-tags': 'EPD',
'X-Cache-ov-mode': 'DEFAULT',
'Content-Type': 'application/json; charset=UTF-8' })
wcrequest = {
'flat_id': flat_id,
'period': period,
'is_debt': is_debt,
'info': {
'guid': self.guid
},
'auth': {
'session_id': self.session_id
}
}
ret = self.session.post('https://emp.mos.ru/v1.1/epd/get',
params={'token': self.token},
headers=wheaders,
verify=self.verify,
timeout=self.timeout,
json=wcrequest)
response = ret.json()
self.raise_for_status(response)
return response['result']
def get_car_fines(self, sts_number):
"""
:param sts_number: unicode string contains car sts_numer
:return:
{
"paid": [{
"seriesAndNumber": "xxxxx",
"date": "2018-08-30+03:00",
"offence_place": "МОСКВА Г. МКАД,xxxx, ВНЕШНЯЯ СТОРОНА",
"offenceType": "12.9ч.2 - Превышение установленной скорости движения транспортного средства на величину от 20 до 40 километров в час ",
"cost": "500",
"is_discount": false,
"drive_license": null,
"sts_number": "xxxxxxx",
"executionState": "Исполнено",
"is_fssp": false
}],
"unpaid": []
}
"""
assert self.session_id
wheaders = deepcopy(self.headers)
wheaders.update({
'X-Clears-tags': 'FORCE_NETWORK',
'X-Cache-ov-mode': 'DEFAULT',
'Content-Type': 'application/json; charset=UTF-8' })
wcrequest = {
'sts_number': sts_number,
'info': {
'guid': self.guid
},
'auth': {
'session_id': self.session_id
}
}
ret = self.session.post('https://emp.mos.ru/v1.0/offence/getOffence',
params={'token': self.token},
headers=wheaders,
verify=self.verify,
timeout=self.timeout,
json=wcrequest)
response = ret.json()
self.raise_for_status(response)
return response['result']
def logout(self, timeout=None):
"""
Почему то очень долго выполняется (5 сек)
"""
if self.session_id:
logout_data = {
'info': {
'guid': self.guid
},
'auth': {
'session_id': self.session_id
}
}
ret = self.session.post('https://emp.mos.ru/v1.0/auth/logout',
params={'token': self.token},
headers=self.headers,
timeout=timeout or self.timeout,
json=logout_data)
response = ret.json()
self.raise_for_status(response)
self.session_id = None
return response['result']
class MosAPI(object):
"""
kwargs:
token: уникальный ключ приложения
guid: некий уникальный ключ
https_verify: ключ verify в GET, POST запросах, по умолчанию 'False'
timeout: ключ timeout в GET, POST запросах
user_agent: версия веб клиента
dev_user_agent: 'Android' для ОС Android
dev_app_version: версия ОС
"""
def __init__(self, **kwargs):
self.kwargs = kwargs
self._clients = {'default': Client(**kwargs)}
def client(self, client_id='default', **kwargs):
if client_id and \
client_id not in self._clients:
k = self.kwargs
k.update(kwargs)
self._clients[client_id] = Client(**k)
return self._clients[client_id]
# if only one client
def is_active(self):
return self.client().is_active()
def login(self, *args):
return self.client().login(*args)
def logout(self, *args):
return self.client().logout(*args)
def get_profile(self):
return self.client().get_profile()
def get_flats(self):
return self.client().get_flats()
def get_watercounters(self, *args):
return self.client().get_watercounters(*args)
def send_watercounters(self, *args):
return self.client().send_watercounters(*args)
def get_electrocounters(self, *args):
return self.client().get_electrocounters(*args)
def send_electrocounters(self, *args):
return self.client().send_electrocounters(*args)
def get_epd(self, *args):
return self.client().get_epd(*args)
def get_car_fines(self, *args):
return self.client().get_car_fines(*args)
class Water():
COLD = 1
HOT = 2
@staticmethod
def water_abbr(water):
if water == Water.COLD: return u'ХВС'
elif water == Water.HOT: return u'ГВС'
@staticmethod
def name(water):
if water == Water.COLD: return u'холодная вода'
elif water == Water.HOT: return u'горячая вода'
class Watercounter():
"""
[{ 'counterId': 1437373, # внутреннее id счетчика
'type': 1, # тип воды
'num': '417944', # серийный номер счетчика
'checkup': '2023-09-25+03:00', # дата следующей поверки
'indications':
[{'period': '2018-08-31+03:00', 'indication': '21.38'},
{'period': '2018-07-31+03:00', 'indication': '20.7'},
{'period': '2018-06-30+03:00', 'indication': '19'}]
},
{...}
]
list(filter(lambda x: x['num'] == num, response['counters']))
list(filter(lambda x: x['counterId'] == id, response['counters']))
list(filter(lambda x: x['type'] == water_type_id, response['counters']))
"""
@staticmethod
def last_value(counter):
"""
alphabetical sort data =)
:param counter: counter JSON
:return: float or None
"""
indications = counter['indications']
indications.sort(key=lambda x: x['period'])
if indications:
return float(indications[-1]['indication'])
@staticmethod
def water_title(counter):
return Watercounter.humanreadable_name(counter['type'])
@staticmethod
def checkup(counter):
"""
https://docs.python.org/3/library/datetime.html#datetime.timezone
Если нужно UTC d.replace(tzinfo=None)
:param counter: counter JSON
:return: datetime с временной зоной
"""
checkup = counter['checkup']
d = datetime.strptime(checkup, '%Y-%m-%d%z') #'checkup': '2023-09-25+03:00'
return d
@staticmethod
def serialize_for_send(counter, value):
"""
:param counterId: id счетчика. не номер из приложения!
:param value: значение float
:return: dict
"""
return {
'counter_id': int(counter['counterId']),
'period': datetime.now().strftime("%Y-%m-%d"),
'indication': '{:.2f}'.format(value).replace('.', ',')
}
@@ -0,0 +1,152 @@
import requests
import hashlib
import globals
import datetime as dt
from xml.etree import ElementTree
from automation import Automation, Base # type: ignore
class MosEnergoSbytSender(Automation):
def initialize(self):
super().initialize()
self.run_daily(self.send_mosenergosbyt_counters, dt.time(12, 0, 0))
# self.send_mosenergosbyt_counters({})
def send_mosenergosbyt_counters(self, kwargs):
if not 'home_power_total' in self.entity_ids:
self.error('Please define home_power_total in entity_ids.')
return
current_counter_value = int(float(self.get_state(self.entity_ids['home_power_total'])))
self.log('Sending current counter value: {}'.format(current_counter_value))
self.app.send(current_counter_value)
class MosEnergoSbytUpdater(Automation):
def initialize(self):
super().initialize()
self.timer = self.run_every(self.update_sensors, self.datetime(), 1*60*60)
# self.send_mosenergosbyt_counters({})
def update_sensors(self, args) -> None:
self.app.update()
counter_value = self.app.value
counter_date = self.app.date
balance = self.app.balance
self.log('Current counter value is: {} (@{}), balance is: {}'.format(counter_value, counter_date, balance))
if 'mosenergosbyt_total' in self.entity_ids:
entity_id = self.entity_ids['mosenergosbyt_total']
attributes = {
'unit_of_measurement': 'kWh',
'date': counter_date,
# 'checkup': checkup,
'icon': 'mdi:av-timer',
'friendly_name': 'Счетчик (мосэнергосбыт)',
'description': 'Created and updated from appdaemon ({})'.format(__name__)
}
self.set_state(entity_id, state = counter_value, attributes = attributes)
if 'mosenergosbyt_balance' in self.entity_ids:
entity_id = self.entity_ids['mosenergosbyt_balance']
attributes = {
'unit_of_measurement': '₽',
'date': counter_date,
'icon': 'mdi:bank-transfer',
'friendly_name': 'Баланс (мосэнергосбыт)',
'description': 'Created and updated from appdaemon ({})'.format(__name__)
}
self.set_state(entity_id, state = balance, attributes = attributes)
class MosEnergoSbytCounter(Base):
_version = 11
_device = "ios/12.1.2/2.14.14(221)"
_session = None
_profile = None
# def initialize(self):
# super().initialize()
@property
def value(self) -> float:
"""Return the current state of counter."""
if (self._profile == None):
self.update()
return float(self._profile['SH_POK_STR'])
@property
def balance(self) -> float:
"""Return the current state of counter."""
if (self._profile == None):
self.update()
balance = -1 * float(self._profile['SM_DEBT'])
if balance == -0:
balance = 0
return balance
@property
def date(self) -> str:
"""Return the current state of counter."""
if (self._profile == None):
self.update()
return self._profile['DT_POK']
def is_active(self):
"""
:return: True если уже залогинился
"""
return self._session is not None
def update(self) -> None:
if not self.is_active():
self._session = self._get_session()
url = 'https://lkkbyt.mosenergosbyt.ru/gate/do?api_version={}&id_session={}&process=refresh&type=1'.format(self._version, self._session)
response = requests.get(url).text
values_response = self._parse_response(response)
self._profile = values_response
# self.log(values_response)
def send(self, value) -> bool:
if not self.is_active():
self._session = self._get_session()
url = 'https://lkkbyt.mosenergosbyt.ru/gate/do?id_session={}&pr_check_ras=0&pr_flat_pu=0&process=dotranspok&type=3&vl_pok_t1={}&vl_pok_t2=0&vl_pok_t3=0'.format(self._session, value)
response = requests.get(url).text
send_response = self._parse_response(response)
result = send_response['KD_RESULT']
if result == 0:
return True
return False
def _get_session(self) -> str:
login = str(self.properties['login'])
pass_md5 = hashlib.md5(self.properties['pwd'].encode('utf-8')).hexdigest()
ls = ''
phone = ''
if len(login) == 10:
ls = login
elif len(login) == 11:
phone = login
else:
self.error('unknown login type: {}'.format(login))
return
url = "https://lkkbyt.mosenergosbyt.ru/gate/do?api_version={}&id_device={}&phone={}&ls={}&process=login&pw_abn={}&type=1".format(self._version, self._device, phone, ls, pass_md5)
response = requests.get(url).text
session_response = self._parse_response(response)
session = session_response['ID_SESSION']
return session
def _parse_response(self, response):
et = ElementTree.fromstring(response)
headers = []
values = []
for node in et.findall('./h/p'):
headers.append(node.text)
for node in et.findall('./b/p'):
values.append(node.text)
result = {}
for i in range(0, len(headers)):
header = headers[i]
value = values[i]
result[header] = value
return result
@@ -0,0 +1,47 @@
mosenergo_counter:
module: mosenergosbyt
class: MosEnergoSbytCounter
properties:
login: !secret mosenergo_main_login
pwd: !secret mosenergo_main_pwd
mosenergo_counter_updater:
module: mosenergosbyt
class: MosEnergoSbytUpdater
entity_ids:
mosenergosbyt_total: sensor.power_mosenergosbyt_total
mosenergosbyt_balance: sensor.power_mosenergosbyt_balance
app: mosenergo_counter
dependencies:
- mosenergo_counter
global_dependencies:
- globals
mosenergo_counter_sender:
module: mosenergosbyt
class: MosEnergoSbytSender
entity_ids:
home_power_total: sensor.power_main_total
app: mosenergo_counter
dependencies:
- mosenergo_counter
global_dependencies:
- globals
mosenergo_alt:
module: mosenergosbyt
class: MosEnergoSbytCounter
properties:
login: !secret mosenergo_alt_login
pwd: !secret mosenergo_alt_pwd
mosenergo_alt_updater:
module: mosenergosbyt
class: MosEnergoSbytUpdater
app: mosenergo_alt
dependencies:
- automation
- mosenergo_alt
global_dependencies:
- globals
+281
View File
@@ -0,0 +1,281 @@
import globals
import datetime as dt
from automation import Automation, Base
from datetime import datetime, tzinfo, timedelta
from mos_lib import MosAPI, Watercounter, Water, EmpServerException
class WaterCounterSender(Automation):
timezone = '+03:00'
def initialize(self):
super().initialize()
self.run_daily(self.send_mosru_counters, dt.time(12, 00, 0))
def send_mosru_counters(self, kwargs):
self.log('Starting sending counters value to mos.ru')
new_values = []
self.app.update_watercounters()
water_hot = self.app.water_hot
water_cold = self.app.water_cold
counters = [water_hot, water_cold]
for counter in counters:
counter_type = counter['type']
checkup = counter['checkup'].replace(self.timezone,'')
checkup_date = datetime.strptime(checkup, '%Y-%m-%d')
# подходит дата поверки
if checkup_date < datetime.now():
self.error('Counter {} checkup date is expired at {}!'.format(counter['counterId'], checkup))
continue
our_sensor = ""
if counter_type == 1:
our_sensor = self.entity_ids['cold']
elif counter_type == 2:
our_sensor = self.entity_ids['hot']
real_value = int(float(self.get_state(our_sensor)))
self.log("Updating counter {} ({}) to real value: {}".format(counter['counterId'], our_sensor, real_value))
new_values.append(Watercounter.serialize_for_send(counter, real_value))
if new_values:
try:
self.app.send_watercounters(new_values)
self.log('Counters values was sent to mos.ru')
except EmpServerException as err:
self.log('error sending: {}'.format(err))
class WaterCounterSensor(Automation):
timezone = '+03:00'
def initialize(self):
super().initialize()
self.timer = self.run_every(self.update_sensors, self.datetime(), 1*60*60)
def update_sensors(self, args) -> None:
self.log('Updating water counters')
self.app.update_watercounters()
if 'hot' in self.entity_ids:
water_hot = self.app.water_hot
self.create_water_entity(self.entity_ids['hot'], water_hot)
if 'cold' in self.entity_ids:
water_cold = self.app.water_cold
self.create_water_entity(self.entity_ids['cold'], water_cold)
def create_water_entity(self, entity_id, counter):
indication_max = 0
indication_date = ""
for indication in counter['indications']:
current = float(indication['indication'])
if (current > indication_max):
indication_max = current
indication_date = indication['period']
indication_date = indication_date.replace(self.timezone,'')
counter_type = counter['type']
checkup = counter['checkup']
type_desc = ""
type_desc_ru = ""
if counter_type == 1:
type_desc = "cold"
type_desc_ru = "Холодная"
elif counter_type == 2:
type_desc = "hot"
type_desc_ru = "Горячая"
icon = "mdi:av-timer"
checkup = checkup.replace(self.timezone,'')
checkup_date = datetime.strptime(checkup, '%Y-%m-%d')
# подходит дата поверки
if (checkup_date - timedelta(days=31)) < datetime.now():
icon = "mdi:alert-octagon"
attributes = {}
attributes['unit_of_measurement'] = 'm3'
attributes['date'] = indication_date
attributes['checkup'] = checkup
attributes['icon'] = icon
attributes['friendly_name'] = "{}(mos.ru)".format(type_desc_ru)
attributes['description'] = 'Created and updated from appdaemon ({})'.format(__name__)
self.set_state(entity_id, state = indication_max, attributes = attributes)
class EpdBalanceSensor(Automation):
def initialize(self):
super().initialize()
self.timer = self.run_every(self.update_sensors, self.datetime(), 1*60*60)
def update_sensors(self, args) -> None:
if 'epd_balance' in self.entity_ids:
self.app.update_epd()
epd_balance = self.app.epd_balance
epd_description = self.app.epd_description
self.log('Current epd balance: {}'.format(epd_balance))
entity_id = self.entity_ids['epd_balance']
attributes = {
'unit_of_measurement': '₽',
'icon': 'mdi:bank-transfer',
'date': datetime.now().strftime('%d.%m.%Y'),
'unpaid': epd_description,
'friendly_name': 'Баланс (ЕПД)',
'description': 'Created and updated from appdaemon ({})'.format(__name__)
}
self.set_state(entity_id, state = epd_balance, attributes = attributes)
class MosruBase(Base):
_api = None
_flat_id = None
_guid='4421a3fb0567714b27dec84d3c3862dc'
_app_version = '3.5.0.195 (102)'
_user_agent = 'okhttp/3.8.1'
_dev_user_agent = 'Android'
_timezone = '+03:00'
def initialize(self):
super().initialize()
if not globals.check_properties(self,['login', 'pwd', 'token', 'paycode']):
return
self._api = MosAPI(token=self.properties['token'],
user_agent=self._user_agent,
guid=self._guid,
dev_user_agent=self._dev_user_agent,
dev_app_version=self._app_version,
timeout=6)
self._set_flat_id()
def _set_flat_id(self):
''' Set flat id by properties or pay code '''
if 'flat_id' in self.properties:
self._flat_id = self.properties['flat_id']
return
if (self._flat_id != None):
return
self._check_login()
flats = self._api.get_flats()
for flat in flats:
if (int(flat['paycode']) != self.properties['paycode']):
continue
self._flat_id = flat['flat_id']
def _check_login(self):
if not self._api.is_active():
self._api.login(self.properties['login'], self.properties['pwd'])
class MosruPower(MosruBase):
_power_counters = None
@property
def power_total(self):
if (self._power_counters == None):
self.update_power()
return self._power_counters['zones'][0]['value']
@property
def power_balance(self):
if (self._power_counters == None):
self.update_power()
return float(self._power_counters['balance'])
def update_power(self) -> None:
self._check_login()
self._power_counters = self._api.get_electrocounters(self._flat_id)
class MosruWater(MosruBase):
_water_counters = None
@property
def water_hot(self):
"""Return the current state of counter."""
if (self._water_counters == None):
self.update_watercounters()
hots_json = list(filter(lambda x: x['type'] == Water.HOT, self._water_counters))
return hots_json[0]
@property
def water_cold(self):
"""Return the current state of counter."""
if (self._water_counters == None):
self.update_watercounters()
colds_json = list(filter(lambda x: x['type'] == Water.COLD, self._water_counters))
return colds_json[0]
@property
def water_hot_value(self):
hot_value = Watercounter.last_value(self.water_hot)
return hot_value
@property
def water_cold_value(self):
cold_value = Watercounter.last_value(self.water_cold)
return cold_value
def send_watercounters(self, new_values) -> None:
self._check_login()
self._api.send_watercounters(self._flat_id, new_values)
def update_watercounters(self) -> None:
self._check_login()
self._water_counters = self._api.get_watercounters(self._flat_id)['counters']
class MosruEpd(MosruBase):
_epd_data = None
@property
def epd_description(self) -> str:
if (self._epd_data == None):
self.update_epd()
unpaid = []
for date in self._epd_data:
epd = self._epd_data[date]
if epd['is_paid']:
continue
unpaid.append('{}: {}'.format(date, epd['amount']))
return ", ".join(unpaid)
@property
def epd_balance(self) -> float:
"""Return the current state of counter."""
if (self._epd_data == None):
self.update_epd()
balance = 0
for date in self._epd_data:
epd = self._epd_data[date]
epd_is_paid = epd['is_paid']
if epd_is_paid:
continue
balance = balance - epd['amount']
return balance
def update_epd(self) -> None:
self._check_login()
data = {}
date = datetime.now().date()
for i in range (0,6):
process_date = date - timedelta(i*365/12)
date_str = process_date.strftime('%d.%m.%Y')
epd = self._api.get_epd(self._flat_id, date_str, False)
for i in range(0, 10): #retries
if epd != []:
break
epd = self._api.get_epd(self._flat_id, date_str, False)
epd_first = epd[0]
data[date_str] = epd_first
# epd_total = epd_first['amount']
# epd_is_paid = epd_first['is_paid']
# self.log(" - Дата: {}, сумма: {}, оплачен: {}.".format(date_str, epd_total, epd_is_paid))
self._epd_data = data
class MosruClient(MosruEpd, MosruWater, MosruPower):
def initialize(self):
super().initialize()
# self.log('Flat id is: {}'.format(self._flat_id))
# self.log('epd balance: {}'.format(self.epd_balance))
# self.log('cold: {}'.format(self.water_cold))
# self.log('hot: {}'.format(self.water_hot))
# self.log('power_counter: {}, balance: {}'.format(self.power_total, self.power_balance))
@@ -0,0 +1,50 @@
#################################
# MOS.RU DATA RETRIEVER #
#################################
mosru_client:
class: MosruClient
module: mosru
properties:
login: !secret mosru_login
pwd: !secret mosru_pwd
token: !secret mosru_token
paycode: !secret mosru_paycode
flat_id: !secret mosru_flat_id
global_dependencies:
- globals
epd_sensor:
class: EpdBalanceSensor
module: mosru
entity_ids:
epd_balance: sensor.epd_balance
app: mosru_client
dependencies:
- mosru_client
global_dependencies:
- globals
water_sensor:
class: WaterCounterSensor
module: mosru
entity_ids:
hot: sensor.counter_mosru_hot
cold: sensor.counter_mosru_cold
app: mosru_client
dependencies:
- mosru_client
global_dependencies:
- globals
water_sender:
class: WaterCounterSender
module: mosru
entity_ids:
hot: sensor.counter_hot
cold: sensor.counter_cold
app: mosru_client
dependencies:
- mosru_client
global_dependencies:
- globals
@@ -0,0 +1,53 @@
import appdaemon.plugins.hass.hassapi as hass
#
# Water leak detector
# close water valve and send a report
#
# Args:
#
# sensor = water leak sensor group
# valve = water valve - app will close it, when leak is detected
# notify = notify name to send notification
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class WaterLeak(hass.Hass):
ringtone = 2
gw_mac = ''
def initialize(self):
if 'sensor' not in self.args or 'valve' not in self.args or 'notify' not in self.args:
self.error("Please provide sensor, valve and notify in config!")
return
self.listen_state(self.water_leak, self.args['sensor'])
def water_leak(self, entity, attribute, old, new, kwargs):
if new == "on":
self.send_alarm()
def send_alarm(self):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
#close water valve
self.turn_off(self.args['valve'])
## to ensure that water is closed. It is important!!!
self.call_service("mqtt/publish", topic = 'home/bath/bath/valve/set', payload = 'close')
self.call_service("xiaomi_aqara/play_ringtone", gw_mac=self.gw_mac, ringtone_id=self.ringtone, ringtone_vol=100)
message = "ВНИМАНИЕ! В ваше отстутствие сработал датчик протечки воды!!! Выключаю подачу воды."
self.notify(message, name = self.args['notify'])
message = "Состояние датчиков протечки:\n"
attributes = self.get_state(self.args['sensor'], attribute = "all")
if 'attributes' in attributes:
attributes = attributes['attributes']
if 'entity_id' in attributes:
for entity_id in attributes["entity_id"]:
message += '{} ({}) = {}\n'.format(self.friendly_name(entity_id), entity_id, self.get_state(entity_id))
self.notify(message, name = self.args['notify'])
@@ -0,0 +1,13 @@
#################################
# WATER LEAK #
#################################
water_leak:
module: water_leak
class: WaterLeak
constraint: input_boolean.water_leak_control
sensor: group.water_leak
valve: switch.water_valve
notify: telegram
gw_mac: !secret aqara1_mac
global_dependencies:
- globals