Pull to refresh

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

Level of difficultyEasy
Reading time7 min
Views35K

В сети сотни статей про использование 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. Планирую воспользоваться советами, которые мне уже дали в комментариях, и сделать запись в циклический буфер.

Tags:
Hubs:
Total votes 22: ↑20 and ↓2+24
Comments86

Articles