Каждый месяц у меня была одна и та же задача:

  • снять и передать показания воды;

  • снять и передать показания электроэнергии;

  • проверить начисления;

  • найти ссылки на оплату;

  • оплатить счета.

Сам процесс занимал немного времени, но требовал внимания. Стоило забыть про очередное 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 запускает набор сценариев.

Для воды выполняется:

  1. Авторизация на сайте.

  2. Получение cookies и CSRF-токенов.

  3. Чтение показаний из Home Assistant.

  4. Формирование JSON-запроса.

  5. Отправка показаний.

  6. Анализ ответа сервера.

Если поставщик услуг отклонил показания, в Telegram приходит причина ошибки. Если всё прошло успешно — приходит подтверждение.

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

Автоматическая проверка начислений

После передачи показаний работа системы не заканчивается.

Скрипты дополнительно заходят в личные кабинеты поставщиков услуг и собирают:

  • основной долг;

  • пени;

  • итоговую сумму;

  • ссылки на оплату.

Отдельно обрабатываются:

  • вода;

  • электроэнергия;

  • отопление;

  • капитальный ремонт.

Для отопления показания передавать не требуется, поэтому система просто получает начисления и формирует ссылку на оплату.

Telegram вместо четырёх личных кабинетов

В результате каждый месяц я получаю единый отчёт.

  • текущие показания;

  • задолженность;

  • ссылка на оплату.

Уведомление в телеграмм

Больше не нужно вспоминать адреса сайтов, логины и пароли или искать нужный раздел в личном кабинете. Всё уже собрано в одном сообщении. Ссылка на оплату открывает сразу форму оплаты где можно выбрать способ оплаты.

Итоги

Проект начинался как попытка не забывать передавать показания воды.

В итоге Home Assistant превратился в полноценную систему управления коммунальными услугами.

Сейчас она:

  • самостоятельно снимает показания;

  • самостоятельно передаёт показания;

  • контролирует начисления;

  • собирает ссылки на оплату;

  • сообщает об ошибках;

  • уведомляет о задолженностях.

А мне остаётся только открыть Telegram и нажать кнопку «Оплатить». Так же было реализовано получение квитанции сразу телеграмм бот, но практика показала что это лишняя информация именно в боте и IMAP интеграция HA оказалась высоконагруженной

PS: Такое можно реализовать и с ботами ВКонтакте.

Комментарии (2)


  1. nikerossxp
    31.05.2026 01:26

    1. Считаем электричество умным автоматом в собственном щитке. WiFi\Zigbee\ESPHome.

    2. Импульсный счетчик стоит 1.5-2к, контроллер на пару счётчиков - 5к (SAURES водомерка, например)

    Всё добавляется в HA.


    1. 4yGON Автор
      31.05.2026 01:26

      Ктож спорит)) но сейчас ещё проще ук сама собирает показания, так как на счетчике электричества есть rs-232.

      По воде вопрос тока в цене и во времени которое хочется потратить