Каждый месяц у меня была одна и та же задача:
снять и передать показания воды;
снять и передать показания электроэнергии;
проверить начисления;
найти ссылки на оплату;
оплатить счета.
Сам процесс занимал немного времени, но требовал внимания. Стоило забыть про очередное 21 число, как начинались перерасчёты, начисления по среднему и прочие коммунальные приключения.
Поэтому я решил автоматизировать всё полностью. В результате сейчас:
Home Assistant собирает показания всех счётчиков;
вода распознаётся AI-моделью на ESP32-CAM;
электричество учитывается по импульсам светодиода счётчика;
показания автоматически отправляются поставщикам услуг;
начисления автоматически проверяются;
ссылки на оплату автоматически собираются;
Telegram присылает готовый отчёт.
Моё участие в процессе сведено практически к нулю.
Электросчётчик
С электросчётчиком всё оказалось достаточно просто. На корпусе есть импульсный светодиод с маркировкой: 3200 имп/кВт·ч. Каждая вспышка соответствует определённому количеству потреблённой энергии.
Подключаться к внутренним интерфейсам счётчика и тем более лезть под пломбы мне не хотелось, поэтому был выбран полностью бесконтактный вариант.
Используемые компоненты:
Wemos D1 Mini;
датчик освещённости TEMT6000;
ESPHome.
Датчик установлен напротив светодиода счётчика и фиксирует каждую вспышку. После получения импульса Home Assistant увеличивает значение энергии на: 1 / 3200 кВт·ч. Выглядит это как то так, плату Wemos d1 mini, нужно установить снаружи эл. щитка особенно если он металлический.

Отдельно ведётся учёт дневного и ночного тарифа. В результате Home Assistant знает:
День;
Ночь;
Общий расход;
Историю потребления.
Так же на лету можно корректировать показания день\ночь если произошел рассинхрон
Так это выглядит в HA

Настройка интеграции

Скетч ESPHome
esphome: name: electricmeter friendly_name: ElectricMeter on_boot: priority: -10 then: - lambda: |- id(flash_counter_day_sensor).publish_state((float) id(flash_day)); id(flash_counter_night_sensor).publish_state((float) id(flash_night)); id(energy_day_sensor).publish_state(id(energy_day_total)); id(energy_night_sensor).publish_state(id(energy_night_total)); id(energy_sensor).publish_state(id(energy_day_total) + id(energy_night_total)); esp8266: board: d1_mini restore_from_flash: true preferences: flash_write_interval: 1min #logger: api: encryption: key: "" ota: - platform: esphome password: "" wifi: ssid: !secret wifi_ssid password: !secret wifi_password ap: ssid: "" password: "" captive_portal: globals: - id: flash_day type: int restore_value: yes initial_value: '0' - id: flash_night type: int restore_value: yes initial_value: '0' - id: energy_day_total type: float restore_value: yes initial_value: '2893.30000' # 9258560 / 3200 - id: energy_night_total type: float restore_value: yes initial_value: '1094.30000' # 3501760 / 3200 time: - platform: homeassistant id: esptime timezone: Europe/Amsterdam on_time: - seconds: 0 minutes: 0 hours: 0 then: - lambda: |- id(flash_day) = 0; id(flash_night) = 0; id(flash_counter_day_sensor).publish_state(0); id(flash_counter_night_sensor).publish_state(0); sensor: - platform: adc pin: A0 name: "TEMT6000 raw" id: light_raw update_interval: 10ms - platform: template name: "Освещенность в люксах" id: light_lux unit_of_measurement: "lx" accuracy_decimals: 1 lambda: |- return id(light_raw).state * 1000.0f; update_interval: 10ms - platform: template name: "Импульсы день" id: flash_counter_day_sensor unit_of_measurement: "imp" accuracy_decimals: 0 lambda: |- return (float) id(flash_day); - platform: template name: "Импульсы ночь1" id: flash_counter_night_sensor unit_of_measurement: "imp" accuracy_decimals: 0 lambda: |- return (float) id(flash_night); - platform: template name: "Энергия день" id: energy_day_sensor unit_of_measurement: "kWh" device_class: energy state_class: total_increasing accuracy_decimals: 5 lambda: |- return id(energy_day_total); - platform: template name: "Энергия ночь1" id: energy_night_sensor unit_of_measurement: "kWh" device_class: energy state_class: total_increasing accuracy_decimals: 5 lambda: |- return id(energy_night_total); - platform: template name: "Энергия всего1 " id: energy_sensor unit_of_measurement: "kWh" device_class: energy state_class: total_increasing accuracy_decimals: 5 lambda: |- return id(energy_day_total) + id(energy_night_total); number: - platform: template name: "Правка энергия день" id: edit_energy_day min_value: 0 max_value: 100000 step: 0.00001 mode: box optimistic: false lambda: |- return id(energy_day_total); set_action: - lambda: |- id(energy_day_total) = x; id(energy_day_sensor).publish_state(id(energy_day_total)); id(energy_sensor).publish_state(id(energy_day_total) + id(energy_night_total)); - platform: template name: "Правка энергия ночь1" id: edit_energy_night min_value: 0 max_value: 100000 step: 0.00001 mode: box optimistic: false lambda: |- return id(energy_night_total); set_action: - lambda: |- id(energy_night_total) = x; id(energy_night_sensor).publish_state(id(energy_night_total)); id(energy_sensor).publish_state(id(energy_day_total) + id(energy_night_total)); interval: - interval: 10ms then: - lambda: |- static float last_lux = 0.0f; static uint32_t last_flash_time = 0; float current_lux = id(light_lux).state; uint32_t now_ms = millis(); if ((current_lux - last_lux) > 150.0f && (now_ms - last_flash_time > 150)) { last_flash_time = now_ms; auto now_time = id(esptime).now(); bool is_day = true; if (now_time.is_valid()) { is_day = (now_time.hour >= 7 && now_time.hour < 23); } if (is_day) { id(flash_day)++; id(energy_day_total) += 1.0f / 3200.0f; id(flash_counter_day_sensor).publish_state(id(flash_day)); id(energy_day_sensor).publish_state(id(energy_day_total)); } else { id(flash_night)++; id(energy_night_total) += 1.0f / 3200.0f; id(flash_counter_night_sensor).publish_state(id(flash_night)); id(energy_night_sensor).publish_state(id(energy_night_total)); } id(energy_sensor).publish_state(id(energy_day_total) + id(energy_night_total)); } last_lux = current_lux;
BASH скрипт отправки показаний эл. энергии
#!/usr/bin/env bash set -euo pipefail ################################################## # --- НАСТРОЙКИ --- ################################################## RKS_EMAIL="Логин" RKS_PASSWORD="Пароль" ACCOUNT_ID="айди аккаута" DEVICE_ID="айди прибора учета" HA_URL="http://127.0.0.1:8123" HA_TOKEN="" # токен HA TG_BOT="токен бота ТГ" TG_CHATS=("айди пользователей" "айди пользователей") TARIFF_DAY="ab89b37f-94e4-11ea-960f-00155d016301" TARIFF_NIGHT="ab89b380-94e4-11ea-960f-00155d016301" ################################################## # --- ФУНКЦИИ --- ################################################## send_telegram() { local TEXT="$1" for CHAT in "${TG_CHATS[@]}"; do curl -s -X POST "https://api.telegram.org/bot${TG_BOT}/sendMessage" \ -d chat_id="$CHAT" \ -d text="$TEXT" >/dev/null done } fail() { send_telegram "❌ Ошибка РКС:\n$1" exit 1 } ################################################## # --- 1. Получаем токен --- ################################################## TOKEN=$(curl -k -s -X POST "https://lk.rks-energo.ru/api/signin" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "User-Agent: Mozilla/5.0" \ -H "Origin: https://lk.rks-energo.ru" \ -H "Referer: https://lk.rks-energo.ru/" \ -d "{\"email\":\"$RKS_EMAIL\",\"password\":\"$RKS_PASSWORD\"}" \ | jq -r '.token') [[ -z "$TOKEN" || "$TOKEN" == "null" ]] && fail "Не удалось получить токен" ################################################## # --- 2. Получаем баланс --- ################################################## RESPONSE=$(curl -k -s -X GET "https://lk.rks-energo.ru/api/personalAccount" \ -H "Authorization: Bearer $TOKEN" \ -H "Accept: application/json") TOTAL=$(echo "$RESPONSE" | jq -r '.data[0].balance') VALUE_DAY=$(echo "$RESPONSE" | jq -r '.data[0].balance_details["1"].Value // 0' | tr ',' '.') VALUE_NIGHT=$(echo "$RESPONSE" | jq -r '.data[0].balance_details["2"].Value // 0' | tr ',' '.') ################################################## # --- 3. Получаем ссылку на оплату --- ################################################## AMOUNT=$(printf "%.2f" "$TOTAL") ORDER_RESPONSE=$(curl -k -s -X GET \ "https://lk.rks-energo.ru/api/acquiring/registerOrder?personal_account_id=$ACCOUNT_ID&amount=$AMOUNT" \ -H "Authorization: Bearer $TOKEN" \ -H "Accept: application/json") PAY_URL=$(echo "$ORDER_RESPONSE" | jq -r '.data.link // empty') ################################################## # --- 4. Обновляем сенсоры HA --- ################################################## ha_post() { local ENTITY="$1" local STATE="$2" local NAME="$3" curl -s -X POST "$HA_URL/api/states/$ENTITY" \ -H "Authorization: Bearer $HA_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"state\":\"$STATE\",\"attributes\":{\"unit_of_measurement\":\"₽\",\"friendly_name\":\"$NAME\"}}" >/dev/null } ha_post "sensor.rks_penia" "$VALUE_NIGHT" "РКС Баланс Пеня" ha_post "sensor.rks_current" "$VALUE_DAY" "РКС Баланс Текущий" ha_post "sensor.rks_total" "$TOTAL" "РКС Баланс Общий" if [ -n "$PAY_URL" ]; then ha_post "sensor.rks_payment_url" "$PAY_URL" "РКС Ссылка на оплату" fi ################################################## # --- 5. Берём показания с HA --- ################################################## VALUE_DAY=$(curl -s -X GET "$HA_URL/api/states/sensor.electricmeter_energiia_den" \ -H "Authorization: Bearer $HA_TOKEN" | jq -r '.state') VALUE_DAY=$(printf "%.2f" "$VALUE_DAY") VALUE_NIGHT=$(curl -s -X GET "$HA_URL/api/states/sensor.electricmeter_energiia_noch1" \ -H "Authorization: Bearer $HA_TOKEN" | jq -r '.state') VALUE_NIGHT=$(printf "%.2f" "$VALUE_NIGHT") [[ ! "$VALUE_DAY" =~ ^[0-9]+([.][0-9]+)?$ ]] && fail "Дневное значение не число: $VALUE_DAY" [[ ! "$VALUE_NIGHT" =~ ^[0-9]+([.][0-9]+)?$ ]] && fail "Ночное значение не число: $VALUE_NIGHT" ################################################## # --- 6. Формируем values для POST --- ################################################## VALUES="[{\"value\":$VALUE_DAY,\"tariff_zone_id\":\"$TARIFF_DAY\"},{\"value\":$VALUE_NIGHT,\"tariff_zone_id\":\"$TARIFF_NIGHT\"}]" ################################################## # --- 7. Отправка показаний с проверкой JSON --- ################################################## SEND_RESPONSE=$(curl -s -k -X POST \ "https://lk.rks-energo.ru/api/personalAccount/$ACCOUNT_ID/devices/$DEVICE_ID/send" \ -H "Authorization: Bearer $TOKEN" \ -F "values=$VALUES") # Проверяем, что сервер вернул корректный JSON if ! echo "$SEND_RESPONSE" | jq . >/dev/null 2>&1; then fail "Сервер вернул не JSON:\n$SEND_RESPONSE" fi STATUS=$(echo "$SEND_RESPONSE" | jq -r '.status // empty') ################################################## # --- 8. Telegram уведомления --- ################################################## if [[ "$STATUS" == "1" ]]; then TEXT=$'✅ Показания электроэнергии успешно отправлены!\n\n☀️ День: '"$VALUE_DAY"$'\n? Ночь: '"$VALUE_NIGHT" send_telegram "$TEXT" else ERROR_TEXT=$(echo "$SEND_RESPONSE" | jq -r '.data.message // "Неизвестная ошибка"') TEXT=$'❌ Ошибка отправки показаний РКС!\n\nОтвет сервера:\n'"$ERROR_TEXT"$'\n\n☀️ День: '"$VALUE_DAY"$'\n? Ночь: '"$VALUE_NIGHT" send_telegram "$TEXT" fi exit 0
Фактически получился независимый контроль показаний электросчётчика.
Вода и компьютерное зрение
С водой ситуация оказалась проще. Мои счётчики не имеют импульсных выходов, поэтому классическое подключение было невозможно. Менять исправные приборы учёта ради автоматизации не хотелось. Поэтому я пошёл другим путём.
Для каждого водосчётчика установлена ESP32-CAM.
На ней работает система распознавания цифр по изображению, которая периодически фотографирует счётчик и определяет текущие показания.
Сразу отмечу, что саму систему компьютерного зрения я не разрабатывал с нуля.
За основу был взят проект AI-on-the-edge-device, который позволяет распознавать показания механических счётчиков прямо на ESP32-CAM. О самом проекте и его настройке подробно рассказывал Павел на своём сайте «У Павла!». Именно по этой статье я и запускал систему распознавания:
В моей системе этот проект был интегрирован в Home Assistant и стал частью общей автоматизации ЖКХ.
После распознавания данные отправляются в Home Assistant как обычные сенсоры.
По сути система делает то же самое, что раньше делал я сам: смотрит на цифры счётчика и записывает результат. Разница только в том, что теперь это происходит автоматически.
В HA выглядит вот так:

BASH скрипт отправки показаний воды
#!/usr/bin/env bash set -euo pipefail ############################## # --- НАСТРОЙКИ --- ############################## COOKIE_FILE="/config/scripts/water_cookies.cookie" # Netscape cookie file TOKENS_FILE="/config/scripts/water_tokens.json" # валидный JSON с токенами HOME_URL="" LOGIN_URL="" # Защита файлов mkdir -p "$(dirname "$COOKIE_FILE")" touch "$COOKIE_FILE" chmod 600 "$COOKIE_FILE" touch "$TOKENS_FILE" chmod 600 "$TOKENS_FILE" #echo "==> 1) Получаем стартовые cookies (GET $HOME_URL)" curl -s -c "$COOKIE_FILE" -A "Mozilla/5.0" \ -H "Accept: */*" \ -H "Accept-Language: ru,en;q=0.9" \ "$HOME_URL" > /dev/null || true #echo "==> 2) Логинимся и сохраняем куки в $COOKIE_FILE" # Выполняем POST на логин; тело - JSON {username, password}'' LOGIN_RESPONSE=$(curl -s -c "$COOKIE_FILE" -b "$COOKIE_FILE" -X POST "$LOGIN_URL" \ -H "Content-Type: application/json;charset=utf-8" \ -H "Accept: application/json, text/plain, */*" \ -H "Origin: https://xn----7sbdqbfldlsq5dd8p.xn--p1ai" \ -H "Referer: https://xn----7sbdqbfldlsq5dd8p.xn--p1ai/" \ -H "User-Agent: Mozilla/5.0" \ -d '{"username":"Логин","password":"Паоль"}') # Тело ответа (для отладки) #echo "Login response body:" #if echo "$LOGIN_RESPONSE" | jq . >/dev/null 2>&1; then # echo "$LOGIN_RESPONSE" | jq . #else # echo "$LOGIN_RESPONSE" #fi #echo ###################################################### # --- 3) Извлекаем токены из cookie-файла --- ###################################################### # Формат Netscape cookie: domain [tab] flag [tab] path [tab] secure [tab] expiry [tab] name [tab] value # Имя куки — в 6-м поле, значение — в 7-м поле (awk считает пробелы/табы) COOKIE_FILE="/config/scripts/water_cookies.cookie" TOKENS_FILE="/config/scripts/water_tokens.json" # гарантируем unix-формат (убираем возможные CR) # (необязательно — полезно, если куки приходят с CRLF) sed -i 's/\r$//' "$COOKIE_FILE" || true get_cookie_value() { local name="$1" awk -v name="$name" -F $'\t' '$6==name{print $7}' "$COOKIE_FILE" 2>/dev/null | tail -n1 || true } CSRFTOKEN=$(get_cookie_value "csrftoken") ACCESS_TOKEN=$(get_cookie_value "access_token") REFRESH_TOKEN=$(get_cookie_value "refresh_token") #echo "Parsed cookie values:" #echo " CSRFTOKEN: ${CSRFTOKEN:-<none>}" #echo " ACCESS_TOKEN: ${ACCESS_TOKEN:-<none>}" #echo " REFRESH_TOKEN: ${REFRESH_TOKEN:-<none>}" jq -n \ --arg at "$ACCESS_TOKEN" \ --arg rt "$REFRESH_TOKEN" \ --arg ct "$CSRFTOKEN" \ '{ access_token: (if $at == "" then null else $at end), refresh_token: (if $rt == "" then null else $rt end), csrftoken: (if $ct == "" then null else $ct end) }' > "$TOKENS_FILE" chmod 600 "$TOKENS_FILE" #echo "Wrote tokens to $TOKENS_FILE" cat "$TOKENS_FILE" VALUE_COLD=$(curl -s -X GET "http://127.0.0.1:8123/api/states/sensor.khololdnaia_voda" \ -H "Authorization: Bearer токен HA" \ -H "Content-Type: application/json" | jq -r '.state'| awk '{printf("%d\n",$1)}') VALUE_HOT=$(curl -s -X GET "http://127.0.0.1:8123/api/states/sensor.goriachaia_voda" \ -H "Authorization: Bearer токен HA" \ -H "Content-Type: application/json" | jq -r '.state'| awk '{printf("%d\n",$1)}') ###################################################### # --- 4) Отправляем показания --- ###################################################### SEND_URL="" # формируем JSON тело PAYLOAD=$(jq -n \ --argjson cold "$VALUE_COLD" \ --argjson hot "$VALUE_HOT" \ '{ meters: [ {meter_id: "", values: [$cold]}, {meter_id: "", values: [$hot]} ] }') #echo "==> Отправляем показания: cold=$VALUE_COLD, hot=$VALUE_HOT" RESPONSE=$(curl -s -X POST "$SEND_URL" \ -H "Content-Type: application/json;charset=utf-8" \ -H "X-CSRFToken: $CSRFTOKEN" \ -b "$COOKIE_FILE" \ -d "$PAYLOAD") #echo "Ответ сервера:" #echo "$RESPONSE" | jq . || echo "$RESPONSE" ###################################################### # --- 6. Проверка и Telegram уведомление --- ###################################################### STATUS=$(echo "$RESPONSE" | jq -r '.status_code') if echo "$RESPONSE" | jq -e '.errors? | length > 0' >/dev/null; then PARSED_ERRORS=$(echo "$RESPONSE" | jq -r '.errors[]' \ | sed 's/Счётчик №01896 68/Холодная вода/g' \ | sed 's/Счётчик №01896 74/Горячая вода/g') TEXT=$'❌ Ошибка отправки показаний!\nОтвет сервера:\n'"$PARSED_ERRORS" curl -s -X POST "https://api.telegram.org/токен/sendMessage" \ -d chat_id= \ -d text="$TEXT" curl -s -X POST "https://api.telegram.org/токен/sendMessage" \ -d chat_id= \ -d text="$TEXT" else curl -s -X POST "https://api.telegram.org/токен/sendMessage" \ -d chat_id= \ -d text="✅ Показания водички успешно отправлены: ❄️ Холодня - $VALUE_COLD m³ ? Горячая - $VALUE_HOT m³" curl -s -X POST "https://api.telegram.org/токен/sendMessage" \ -d chat_id= \ -d text="✅ Показания водички успешно отправлены: ❄️ Холодня - $VALUE_COLD m³ ? Горячая - $VALUE_HOT m³" fi
Автоматическая передача показаний
Каждое 21 число Home Assistant запускает набор сценариев.
Для воды выполняется:
Авторизация на сайте.
Получение cookies и CSRF-токенов.
Чтение показаний из Home Assistant.
Формирование JSON-запроса.
Отправка показаний.
Анализ ответа сервера.
Если поставщик услуг отклонил показания, в Telegram приходит причина ошибки. Если всё прошло успешно — приходит подтверждение.

Аналогично работает отправка показаний электроэнергии.

Автоматическая проверка начислений
После передачи показаний работа системы не заканчивается.
Скрипты дополнительно заходят в личные кабинеты поставщиков услуг и собирают:
основной долг;
пени;
итоговую сумму;
ссылки на оплату.
Отдельно обрабатываются:
вода;
электроэнергия;
отопление;
капитальный ремонт.
Для отопления показания передавать не требуется, поэтому система просто получает начисления и формирует ссылку на оплату.
Telegram вместо четырёх личных кабинетов
В результате каждый месяц я получаю единый отчёт.
текущие показания;
задолженность;
ссылка на оплату.
Уведомление в телеграмм

Больше не нужно вспоминать адреса сайтов, логины и пароли или искать нужный раздел в личном кабинете. Всё уже собрано в одном сообщении. Ссылка на оплату открывает сразу форму оплаты где можно выбрать способ оплаты.
Итоги
Проект начинался как попытка не забывать передавать показания воды.
В итоге Home Assistant превратился в полноценную систему управления коммунальными услугами.
Сейчас она:
самостоятельно снимает показания;
самостоятельно передаёт показания;
контролирует начисления;
собирает ссылки на оплату;
сообщает об ошибках;
уведомляет о задолженностях.
А мне остаётся только открыть Telegram и нажать кнопку «Оплатить». Так же было реализовано получение квитанции сразу телеграмм бот, но практика показала что это лишняя информация именно в боте и IMAP интеграция HA оказалась высоконагруженной
PS: Такое можно реализовать и с ботами ВКонтакте.
nikerossxp
Считаем электричество умным автоматом в собственном щитке. WiFi\Zigbee\ESPHome.
Импульсный счетчик стоит 1.5-2к, контроллер на пару счётчиков - 5к (SAURES водомерка, например)
Всё добавляется в HA.
4yGON Автор
Ктож спорит)) но сейчас ещё проще ук сама собирает показания, так как на счетчике электричества есть rs-232.
По воде вопрос тока в цене и во времени которое хочется потратить