updating configuration

This commit is contained in:
Your Name
2026-03-10 07:08:59 +00:00
commit 9498a076ee
238 changed files with 18119 additions and 0 deletions
+57
View File
@@ -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
+21
View File
@@ -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.
+4
View File
@@ -0,0 +1,4 @@
include *.md
include LICENSE
recursive-include emp_mos_api/*.py
recursive-include emp_mos_api/examples/*.py
+110
View File
@@ -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
@@ -0,0 +1,3 @@
from emp_mos_api.mos import MosAPI, Water, Watercounter, \
AuthException, EmpServerException
@@ -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))
@@ -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))
@@ -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))
@@ -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))
@@ -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() #долго
@@ -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('.', ',')
}
@@ -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() #долго
@@ -0,0 +1,2 @@
distro>=1.3.0
requests>=2.19.1
+50
View File
@@ -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'
],
)