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





















