TL;DR: у нас в доме пошли «моргашки» света из-за ремонта. Я подключил свой APC Smart-UPS SC620 к apcupsd, накатал первого телеграм-бота на Python и теперь получаю в чат компактные уведомления о смене статуса (ONBATT/ONLINE), просадки сети и низком заряде. Пороговые значения и «антиспам» настраиваются в .env. Называется это чудо — @Podezd24Bot.
Почему вообще полез в эту историю
У соседей — «моргнуло», у меня — мигнул монитор, и где-то далеко тихо плачет файловая система. Ремонт в доме — дело полезное, но скачки по сети меня не радуют. Старенький APC Smart-UPS SC620 ещё бодр, грех не использовать. Решение простое: поднять мониторинг и слать алерты в Телеграм. Ну и повод отличный наконец-то сделать первого бота на Python.
Из чего это сделано
- apcupsd — демон, который общается с UPS (в моём случае по /dev/ttyS1, COM2).
- apcaccess — утилита, через которую бот читает статус.
- Python 3 + python-telegram-bot 21.x — мой код бота.
- systemd — чтобы всё жило само и перезапускалось.
- Немного udev и serial-getty магии, чтобы никто не трогал COM-порт.
Что бот умеет
/ups— красиво показывает текущие параметры (сеть, выход, частоту, нагрузку, батарею, остаток времени)./events— последние строки из/var/log/apcupsd.events./subscribe//unsubscribe— подписка на алерты.- Нотификации о смене статуса (ONLINE ↔ ONBATT), низком заряде, высокой нагрузке.
- Фильтр шумных статусов: если батарея «неоригинал» — я просто подавляю REPLACEBATT.
- Антиспам для просадок сети: учитываю «скачок» и требую, чтобы отклонение держалось N секунд, прежде чем тревожить людей.
- Сообщения аккуратно размечены MarkdownV2 и упакованы в компактные «карточки» с кнопками (статус/события/подписка).
Как я это готовил (пошагово)
1) Дровим apcupsd и убеждаемся, что порт живой
UPS у меня сидит на /dev/ttyS1 (визуально «COM2»). Проверка:
sudo apctest /dev/ttyS1 # 1) Query the UPS... — должно показать SM/параметры, серийник и т.д.
Если apctest разговаривает — отлично, значит порт тот.
2) Минимальный конфиг apcupsd.conf
sudo tee /etc/apcupsd/apcupsd.conf >/dev/null <<'EOF' ## apcupsd.conf v1.1 ## UPSCABLE smart UPSTYPE apcsmart DEVICE /dev/ttyS1 LOCKFILE /var/lock POLLTIME 5 NETSERVER on NISIP 0.0.0.0 NISPORT 3551 EOF
Перезапуск:
sudo systemctl restart apcupsd apcaccess status | grep -E 'STATUS|LINEV|BATTV|LOADPCT|TIMELEFT|MODEL'
3) Чтобы никто не мешал COM-порту
echo 'KERNEL=="ttyS1", ENV{ID_MM_DEVICE_IGNORE}="1"' | \
sudo tee /etc/udev/rules.d/99-apcupsd-ttyS1.rules
sudo udevadm control --reload
sudo udevadm trigger -v -c add /dev/ttyS1
# На всякий случай отключил getty:
sudo systemctl disable --now serial-getty@ttyS1.service
4) Бот (структура)
/opt/apc-telegram-bot/ ├── bot.py ├── .env └── .venv/ # виртуалка Python
В .env — все настройки:
TELEGRAM_BOT_TOKEN=тут_токен_от_BotFather ADMINS=123456789 APC_HOST=127.0.0.1 APC_PORT=3551 POLL_SECONDS=10 ALERT_ON_BATTERY_SECONDS=5 ALERT_LOW_BATTERY_PCT=30 ALERT_LOAD_HIGH_PCT=80 # Подавить шумные статусы: SUPPRESS_STATUSES=REPLACEBATT # Порог сети: ALERT_VOLT_LOW=200 # ниже — «просадка» ALERT_VOLT_HIGH=250 # выше — «перенапряжение» # Если разница между опросами >= ALERT_VOLT_JUMP — считаем скачком ALERT_VOLT_JUMP=12 # Сколько должно держаться вне диапазона, чтобы слать алерт (анти-дерготня) VOLT_PERSIST_SECONDS=3 # Кулдаун для напряжения — не торопимся писать каждые 2 сек VOLT_COOLDOWN_SECONDS=60
5) Установка зависимостей
sudo apt install -y python3-venv python3-pip cd /opt/apc-telegram-bot python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install python-telegram-bot==21.* python-dotenv
6) systemd-юнит
# /etc/systemd/system/apc-telegram-bot.service [Unit] Description=APC UPS Telegram bot (apcupsd) After=network-online.target [Service] Type=simple User=apcbot WorkingDirectory=/opt/apc-telegram-bot Environment="PYTHONUNBUFFERED=1" ExecStart=/opt/apc-telegram-bot/.venv/bin/python /opt/apc-telegram-bot/bot.py Restart=always RestartSec=5 [Install] WantedBy=multi-user.target
Права и запуск:
sudo useradd -r -s /usr/sbin/nologin apcbot || true sudo chown -R apcbot:apcbot /opt/apc-telegram-bot sudo setfacl -m u:apcbot:r /var/log/apcupsd.events sudo systemctl daemon-reload sudo systemctl enable --now apc-telegram-bot sudo systemctl status apc-telegram-bot --no-pager
Интерфейс и уведомления
- В группах бот публикует аккуратные карточки: заголовок, таблица параметров, кнопки.
- Разметка — MarkdownV2, все спецсимволы экранирую, чтобы не ломалось.
- Кнопки:
- «📊 Статус» — текущие значения
- «🧾 События» — хвост лога
- «🔔 Подписка / 🔕 Отписка» — мгновенно.
Почему бот мог «молчать» на скачках
Я специально добавил анти-флаппер для напряжения:
- если просадка «нырнула» и тут же вернулась — алерта нет;
- нужно, чтобы отклонение продержалось
VOLT_PERSIST_SECONDS; - и чтобы новый алерт не валился каждую минуту — есть
VOLT_COOLDOWN_SECONDS.
Таким образом, «микроморгашки» из Netdata, которые не влияют на работу, не будят чат в 3 ночи. Но если реально «поплыло» — уведомление придёт.
Немного кода (фрагмент форматирования карточки)
def fmt_status_md2(kv: dict) -> str:
def esc(s): return re.sub(r'[_*\[\]()~`>#+\-=|{}.!]', r'\\\g<0>', str(s or ""))
def pick(k, d="—"): return kv.get(k, d)
panel = "\n".join([
f"IN : {pick('LINEV'):>6} FQ: {pick('LINEFREQ'):>5}",
f"OUT: {pick('OUTPUTV'):>6} LD: {pick('LOADPCT'):>6}",
f"BAT: {pick('BATTV'):>6} CHG: {pick('BCHARGE', pick('BATTCHG', '')):>6}",
f"LEFT: {pick('TIMELEFT'):>7}",
])
return "\n".join([
f"*{esc('UPS')}*: *{esc(pick('MODEL'))}*",
f"Статус: *{esc(pick('STATUS'))}*",
"```\\n" + panel.replace("```", "`\u200b``") + "\\n```",
f"_{esc(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))}_",
])
Типичные грабли и решения
Bogus configuration value (*invalid-ups-mode*)— у тебя первая строка не## apcupsd.conf v1.1 ##. Приведи конфиг к новому формату.PANIC! Cannot communicate with UPS via serial port— проверьDEVICE, отключиserial-getty@ttyS*, запрети ModemManager через udev-правило.- Бесконечные REPLACEBATT — добавь
SUPPRESS_STATUSES=REPLACEBATTв.env. - Бот не видит лог событий — дай чтение:
setfacl -m u:apcbot:r /var/log/apcupsd.events.
Дорожная карта
- Отправка графиков (мини-PNG) по запросу.
- Агрегация: несколько UPS в одном боте.
- Автоматический экспорт в Prometheus/Netdata.
- /silent режим на ночь — «не буди меня по мелочи».
Итог
Проект решил мою практическую боль: свет моргнул — я в курсе. А ещё это отличный, приземлённый старт для Python: реальное железо, реальный профит и код, который хочется улучшать. Если у тебя тоже «моргает» — попробуй.