ESP32 снимает показания водяных счетчиков и сам передает их «куда надо»

от автора

В сети сотни статей про использование 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *