В сети сотни статей про использование ESP32 для съема показаний приборов учета, но практически все из них про интеграцию с «умным домом». Мой вариант реализации решает практическую задачу — просто сам передает показания, снимая с меня эту задачу.
Основное отличие моего решения от тех, что я видел, заключается в максимальной автономности и легкости контроля и управления. ESP32 самостоятельно общается с сайтом, который принимает показания приборов учета посредством встроенного http клиента. Процесс контролируется и управляется встроенным в ESP32 telegram ботом.
API сайта принимающего показания
Решать эту задачу я начал с изучения API тех сайтов, которые я использовал для передачи показаний. В моем случае это были сайты https://www.mosenergosbyt.ru/ и https://мособлеирц.рф/. Исследовал API банально, используя F12 в своем браузере. Запускаем режим отладки страницы, включаем запись, аутентифицируемся, передаем показания и исследуем методы POST, которые обычно передают и принимают в качестве ответов объекты json.
Сразу скажу, что ГИС ЖКХ отбросил сразу из-за сложности аутентификации, привязки к госуслугам и 2FA. Однако, если там есть система авторизации по токенам и кто-то знает как ей пользоваться, то было бы интересно перенять такой опыт.
API сайта мосэнергосбыта мне не понравился: там используются плагины, и для разных городов и улиц АPI запросы могут существенно различаться, делая публикацию результатов работы практически бесполезной и не интересной. А вот сайт мособлеирц имеет достаточно понятный и унифицированный API.
Для исследования API сайта я написал python скрипт. Он лежит в проекте. С его помощь Вы можете исследовать свой личный кабинет в мособлеирц или использовать как отправную точку для исследования API своего поставщика.
Для аутентификации на сайте нужно методом POST отправить JSON объект со своими учетными данными
http.begin(client, "https://lkk.mosobleirc.ru/api/tenants-registration/v2/login"); http.addHeader("Content-Type", "application/json"); StaticJsonDocument<512> req; req["phone"] = this->user; req["password"] = this->pass; req["loginMethod"] = "PERSONAL_OFFICE"; serializeJson(req, json); log_i("req memory usage %d", req.memoryUsage() ); // getting the access token statusCode = http.POST( json ); http.end(); if ( this->checkForErrors(statusCode) ) { client.stop(); return 0; };
Если аутентификация прошла успешно, то нам в ответе пришлют токен, который далее нужно подставлять в атрибут X-Auth-Tenant-Token в заголовки всех последующих запросов.
Здесь я использую StaticJsonDocument фиксированного размера, который подобрал экспериментальным путем. Замечу, что этот объект создается в куче. Также в проекте я использую DynamicJsonDocument, которые размещаются в стеке. Если размер выбрать меньше необходимого, то возникнет исключительная ситуация, и ESP32 будет перезагружаться.
Далее методом GET нам нужно запросить список своих «домов». Это объекты, в которых указаны почтовые адреса и какие счетчики по этим адресам установлены. У меня в личном кабинете зарегистрировано несколько адресов, поэтому мне нужно искать объект с нужными мне счетчиками.
DynamicJsonDocument eircItems(512); String _url = "https://lkk.mosobleirc.ru/api/api/clients/configuration-items"; http.begin(client, _url ); http.addHeader("X-Auth-Tenant-Token", this->token); http.addHeader("Content-Type", "application/json"); log_i( "GET url : %s ", _url.c_str() ); statusCode = http.GET();
Этот запрос возвращает такие объекты:
Hidden text
{ "id": 2000000, "name": "Квартира", "isBasic": false, "isOwner": false, "isRenter": true, "address": { "id": 2000000, "city": "годод", "street": "улица", "house": "д.1", "number": "кв.1", "room": "кв.1", "location": " город, улица, д.1, кв.1", "shortLocation": "улица, д.1" }, "personalAccount": { "id": 1300000, "systemId": null, "number": "28984398", "utilitiesBalance": 0.00, "repairsBalance": 0, "paymentInsurance": 0, "receiveReceiptByMail": true, "receiptsEmail": null, "newNotConfirmedReceiptsEmail": null } }
Здесь нам интересны id и name. На Id будут ссылаться счетчики, а name будет использовать telegram бот. Из любопытного здесь еще есть баланс в поле utilitiesBalance, но нам он в этой задаче не интересен.
Теперь можно запросить список счетчиков для id того дома, который мы выбрали:
unsigned int iid = v["id"].as<unsigned int>(); String itemName = v["name"].as<const char*>(); DynamicJsonDocument eircmeasures(2048); String _url = "https://lkk.mosobleirc.ru/api/api/clients/meters/for-item/"; _url += String(iid); http.begin(client, _url); http.addHeader("X-Auth-Tenant-Token", this->token); http.addHeader("Content-Type", "application/json"); log_i( "GET url : %s ", _url.c_str() ); statusCode = http.GET();
Этот запрос вернет нам такие объекты:
Hidden text
{ "meter": { "id": 6180000, "name": "2938042", "type": "HotWater", "number": "2938042", "configurationItemId": 2000000, "capacity": "99999.999", "unit": "METERS", "attorneyDeadline": "2026-01-01", "tariffCount": 1, "isAttorney": true, "isSystem": true, "lastValue": { "id": 420200980, "total": { "value": 142, "displayValue": "142", "consumptionValue": 3, "displayConsumptionValue": "+3.0", "minValue": 142.0 }, "tariff1": { "value": 142, "displayValue": "142", "consumptionValue": 3, "displayConsumptionValue": "+3.0", "minValue": 142.0 }, "tariff2": null, "tariff3": null, "sender": { "userId": 4, "firstName": "клиентом ", "lastName": "Передано", "middleName": null, "isTenant": false }, "receivedDate": "2023-08-10T09:00:00Z", "status": "PROCESSED", "settlementPeriod": { "year": 2023, "month": 8 }, "isAverageValue": false } }, "valueSendInfo": { "meterIndicationDate": { "from": 14, "to": 19, "fromDate": "14-09-2023", "toDate": "19-09-2023" }, "willBeCountForPeriod": { "year": 2023, "month": 8 }, "notes": [ { "type": "Field", "message": "Достаточно ввести цифры до запятой. Цифры после запятой вспомогательные и не требуют внесения." } ], "isAbleToSendMeterValues": false, "isInAllowedPeriod": false, "isValueSentForCurrentPeriod": true, "isAutomaticModeOnly": false, "isFractionalPartRequired": false } }
Здесь и текущие показания по тарифам (в случае нашего аналогового счетчика, это единый тариф), и дата поверки attorneyDeadline, и период, когда данные счетчиков принимаются valueSendInfo — из этих данных ESP32 узнает, когда нужно передавать показания за текущий месяц.
Когда наступит время передавать показания, ESP отправит такой запрос:
String _url = "https://lkk.mosobleirc.ru/api/api/clients/meters/"; _url += String(measureId); _url += String("/values?withOptionalCheck=true"); http.begin(client, _url); http.addHeader("X-Auth-Tenant-Token", this->token); http.addHeader("Content-Type", "application/json"); DynamicJsonDocument payload(128); payload["value1"] = newVal; json = ""; serializeJson(payload, json); statusCode = http.POST( json );
Кстати о времени: время берется по протоколу NTP, хотя также можно брать и из telegram бота, так как там в каждом запросе передается время UTC.
Бот
Контролировать процесс было решено с помощью telegram бота, встроенного в сам ESP32. Бот сделан на базе библиотеки https://github.com/GyverLibs/FastBot. Бот позволяет задать начальные значения отсчета, корректировать их по необходимости, посмотреть текущее состояние, контролировать процесс передачи показаний и заодно напомнит о дате очередной поверки счетчиков.
Команд у бота не много:
-
help — выдать список команд
-
get — сходить на сайт мособлеирц, забрать оттуда текущие «тамошние» данные и показать все вместе с текущими значениями счетчиков
-
val — то же, что и get, но не ходить на сайт, а показать последние считанные оттуда данные
-
push — принудительная отправка текущих показаний
-
set — первичная установка и/или корректировка текущих значений счетчиков
Формат команды такой:
set <hot> <col> [<data>]
Здесь hot и col — это целые числа в десятках литров.
Например, если прибор сейчас показывает значение 00012,345 (345 красные цифры единиц литров), нам нужно отбросить последнюю красную цифру и запятую. Таким образом, нужно передать боту только 1234. При этом, в мособлеирц бот отправит только количество целых кубометров, т.е. 12.
Пример.
Мои текущие показания:
Горячая вода — 00145,765
Холодная вода — 00305,236
отбрасываем запятые и по одной последней цифре!!! Получается, что боту нужно отправить такую команду
/set 14576 30523
Устройство счетчиков
Физические счетчики воды (приборы учета), конечно, должны быть оснащены герконами, импульсы которых считывает контроллер.
Программные счетчики импульсов пробовал делать и на основе прерываний, и с аппаратной, и с программной защитой от дребезга. Остановился на программной защите, основанной на таймерах. Оказалось, это самый практичный и надежный вариант. Тестовая эксплуатация на протяжении двух месяцев показала практически 100% точность подсчета импульсов. Такой вариант не требует никакой обвязки, провода от счетчиков можно подключить непосредственно к пинам контроллера. Отсутствие обвязки значительно упрощает проект и делает его более доступным для повторения, причем незначительно влияет на качество работы.
Подсчитанные значения сохраняются в энергонезависимой памяти контроллера, чтобы можно было пережить пропадания питания. Я использовал библиотеку preferences, но позже внимательно на нее посмотрев, решил, что в перспективе переделаю эту часть для повышения срока службы flash памяти.
Дело в том, что в ESP32 eeprom эмулируется в одной из страниц flash памяти. Подробнее об этом можно прочитать здесь https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_flash.html
При этом, если я правильно понял, сами значения библиотекой preferences пишутся оптимально, когда каждое обновление счетчиков занимает новый 32 байтный блок, но вот в начале страницы заголовок обновляется с каждой записью. Это значит, что ресурс этого сегмента памяти иссякнет быстрее всего. Тут не стоит сильно переживать. Объявленный ресурс 100 000 циклов, и этого должно хватить на 1000 кубометров, но все равно небольшая неудовлетворенность присутствует :).
В ближайшее время попробую доработать проект, чтобы увеличить ресурс flash. Планирую воспользоваться советами, которые мне уже дали в комментариях, и сделать запись в циклический буфер.
ссылка на оригинал статьи https://habr.com/ru/articles/773318/
Добавить комментарий