В далёком апреле 2020 года я купил себе комплект из видеорегистратора и нескольких камер для наблюдения за авто во дворе.

Тогда руки так и не дошли использовать комплект по назначению, и всё это добро пролежало 5 лет нетронутым. Такой регистратор по-прежнему можно купить в известном красном маркетплейсе, а всё остальное уже снято с производства. Однако регистратор оказался с заделом на будущее: он умеет общаться с любыми ONVIF-камерами, причём с поддержкой PoE, и рассчитан аж на 8 каналов. Поэтому в 2025 году «бомж-комплект» ценой в 13К обрёл вторую жизнь. 

Видеонаблюдение за загородным домом

После постройки загородного дома встал вопрос организации видеонаблюдения. Провода были заблаговременно проброшены в предполагаемые места размещения, регистратор настроен и размещён в шкафу. В регистраторе, естественно, установлен диск для хранения записей, а камеры изначально были выбраны с функцией автодетекта людей. При просмотре истории видеозаписей на таймлайне появляются отметки, когда срабатывал автодетект, и можно удобно просмотреть все вхождения за день: 

В поездках хочется быть осведомлённым обо всех событиях, особенно когда никого нет дома. Но не заниматься же ручным перебором событий, тем более что интернет должного качества не всегда под рукой.

Настройка отправки алертов с камер в телегу

Почти любой регистратор умеет в отправку событий на почту. Для этого всего лишь нужно указать необходимые данные в его настройках:

Однако, во-первых, с доставкой почты может быть всё самое непредсказуемое. Во-вторых, в 2025 году лично я проверяю её от силы раз в несколько дней.

Перебирая другие настройки регистратора, я обнаружил пункт Alarm Center:

Беглый гуглёж подсказал, что многие производители делают собственные решения для сбора сообщений от своих устройств. Существуют и какие-то универсальные вещи типа IP Camera Alarm Server (с первой страницы выдачи). 

Если честно, руки зачесались глянуть, что же там такого изысканного в отправляемых данных. И почему бы это не собирать и обрабатывать простым скриптом где-то на роутере, в который воткнут регистратор.

Настраиваем отправку на адрес роутера, расчехляем tcpdump и генерируем первый аларм, пройдясь перед камерой:

tcpdump -i any tcp port 15002 -X
00:56:14.925559 br0   In  IP 192.168.1.49.42934 > 192.168.1.1.15002: Flags [.], ack 1, win 913, options [nop,nop,TS val 27149187 ecr 67800391], length 0
	0x0000:  4500 0034 aff5 4000 4006 074c c0a8 0131  E..4..@.@..L...1
	0x0010:  c0a8 0101 a7b6 3a9a f43b ec78 7a96 c1c8  ......:..;.xz...
	0x0020:  8010 0391 19d2 0000 0101 080a 019e 4383  ..............C.
	0x0030:  040a 8d47                                ...G
00:56:14.926004 eth2  In  IP 192.168.1.49.42934 > 192.168.1.1.15002: Flags [P.], seq 1:213, ack 1, win 913, options [nop,nop,TS val 27149187 ecr 67800391], length 212
	0x0000:  4500 0108 aff6 4000 4006 0677 c0a8 0131  E.....@.@..w...1
	0x0010:  c0a8 0101 a7b6 3a9a f43b ec78 7a96 c1c8  ......:..;.xz...
	0x0020:  8018 0391 8a0e 0000 0101 080a 019e 4383  ..............C.
	0x0030:  040a 8d47 ff01 0000 0000 0000 0000 0000  ...G............
	0x0040:  0000 e405 c000 0000 7b20 2241 6464 7265  ........{."Addre
	0x0050:  7373 2220 3a20 2230 7833 3130 3141 3843  ss".:."0x3101A8C
	0x0060:  3022 2c20 2243 6861 6e6e 656c 2220 3a20  0",."Channel".:.
	0x0070:  342c 2022 4465 7363 7269 7022 203a 2022  4,."Descrip".:."
	0x0080:  222c 2022 4576 656e 7422 203a 2022 4875  ",."Event".:."Hu
	0x0090:  6d61 6e44 6574 6563 7422 2c20 2253 6572  manDetect",."Ser
	0x00a0:  6961 6c49 4422 203a 2022 3965 3832 3032  ialID".:."9e8202
	0x00b0:  3338 3161 3063 6563 6430 222c 2022 5374  381a0cecd0",."St
	0x00c0:  6172 7454 696d 6522 203a 2022 3230 3235  artTime".:."2025
	0x00d0:  2d30 382d 3132 2032 303a 3536 3a31 3322  -08-12.20:56:13"
	0x00e0:  2c20 2253 7461 7475 7322 203a 2022 5374  ,."Status".:."St
	0x00f0:  6f70 222c 2022 5479 7065 2220 3a20 2241  op",."Type".:."A
	0x0100:  6c61 726d 2220 7d0a                      larm".}.
00:56:14.926010 eth2.1 In  IP 192.168.1.49.42934 > 192.168.1.1.15002: Flags [P.], seq 1:213, ack 1, win 913, options [nop,nop,TS val 27149187 ecr 67800391], length 212
	0x0000:  4500 0108 aff6 4000 4006 0677 c0a8 0131  E.....@.@..w...1
	0x0010:  c0a8 0101 a7b6 3a9a f43b ec78 7a96 c1c8  ......:..;.xz...
	0x0020:  8018 0391 8a0e 0000 0101 080a 019e 4383  ..............C.
	0x0030:  040a 8d47 ff01 0000 0000 0000 0000 0000  ...G............
	0x0040:  0000 e405 c000 0000 7b20 2241 6464 7265  ........{."Addre
	0x0050:  7373 2220 3a20 2230 7833 3130 3141 3843  ss".:."0x3101A8C
	0x0060:  3022 2c20 2243 6861 6e6e 656c 2220 3a20  0",."Channel".:.
	0x0070:  342c 2022 4465 7363 7269 7022 203a 2022  4,."Descrip".:."
	0x0080:  222c 2022 4576 656e 7422 203a 2022 4875  ",."Event".:."Hu
	0x0090:  6d61 6e44 6574 6563 7422 2c20 2253 6572  manDetect",."Ser
	0x00a0:  6961 6c49 4422 203a 2022 3965 3832 3032  ialID".:."9e8202
	0x00b0:  3338 3161 3063 6563 6430 222c 2022 5374  381a0cecd0",."St
	0x00c0:  6172 7454 696d 6522 203a 2022 3230 3235  artTime".:."2025
	0x00d0:  2d30 382d 3132 2032 303a 3536 3a31 3322  -08-12.20:56:13"
	0x00e0:  2c20 2253 7461 7475 7322 203a 2022 5374  ,."Status".:."St
	0x00f0:  6f70 222c 2022 5479 7065 2220 3a20 2241  op",."Type".:."A
	0x0100:  6c61 726d 2220 7d0a                      larm".}.
00:56:14.926017 br0   In  IP 192.168.1.49.42934 > 192.168.1.1.15002: Flags [P.], seq 1:213, ack 1, win 913, options [nop,nop,TS val 27149187 ecr 67800391], length 212
	0x0000:  4500 0108 aff6 4000 4006 0677 c0a8 0131  E.....@.@..w...1
	0x0010:  c0a8 0101 a7b6 3a9a f43b ec78 7a96 c1c8  ......:..;.xz...
	0x0020:  8018 0391 8a0e 0000 0101 080a 019e 4383  ..............C.
	0x0030:  040a 8d47 ff01 0000 0000 0000 0000 0000  ...G............
	0x0040:  0000 e405 c000 0000 7b20 2241 6464 7265  ........{."Addre
	0x0050:  7373 2220 3a20 2230 7833 3130 3141 3843  ss".:."0x3101A8C
	0x0060:  3022 2c20 2243 6861 6e6e 656c 2220 3a20  0",."Channel".:.
	0x0070:  342c 2022 4465 7363 7269 7022 203a 2022  4,."Descrip".:."
	0x0080:  222c 2022 4576 656e 7422 203a 2022 4875  ",."Event".:."Hu
	0x0090:  6d61 6e44 6574 6563 7422 2c20 2253 6572  manDetect",."Ser
	0x00a0:  6961 6c49 4422 203a 2022 3965 3832 3032  ialID".:."9e8202
	0x00b0:  3338 3161 3063 6563 6430 222c 2022 5374  381a0cecd0",."St
	0x00c0:  6172 7454 696d 6522 203a 2022 3230 3235  artTime".:."2025
	0x00d0:  2d30 382d 3132 2032 303a 3536 3a31 3322  -08-12.20:56:13"
	0x00e0:  2c20 2253 7461 7475 7322 203a 2022 5374  ,."Status".:."St
	0x00f0:  6f70 222c 2022 5479 7065 2220 3a20 2241  op",."Type".:."A
	0x0100:  6c61 726d 2220 7d0a                      larm".}.
00:56:14.926131 br0   Out IP 192.168.1.1.15002 > 192.168.1.49.42934: Flags [.], ack 213, win 470, options [nop,nop,TS val 67800391 ecr 27149187], length 0
	0x0000:  4500 0034 2464 4000 4006 92dd c0a8 0101  E..4$d@.@.......
	0x0010:  c0a8 0131 3a9a a7b6 7a96 c1c8 f43b ed4c  ...1:...z....;.L
	0x0020:  8010 01d6 83a9 0000 0101 080a 040a 8d47  ...............G
	0x0030:  019e 4383                                ..C.

О как! Внутри обычный JSON, который вполне себе можно парсить тупейшим сервером-приёмщиком на любом языке программирования. А потом слать себе оповещения в телегу. Тем более что пуши от неё приходят, даже когда интернета практически совсем нет (не MAX, конечно, тот, поговаривают, и без инета работает, но тоже неплохо).

Итак, мучаем СhatGPT на предмет скрипта парсинга и отправки в телегу и получаем первые сообщения в боте:

Отрезаем лишние вещи, украшаем под себя и добавляем небольшой маппинг, чтобы понимать, откуда летит алерт:

Почти идеально. Осталось как-то избавиться от false-positive, а для этого неплохо бы получать картинку в момент детекта. Но такого функционала «из коробки» у алерт-центра нет. Штош, подключаем фантазию и просто курляем камеру по доступным ручкам, чтобы получить желаемое. У большинства подобных камер такой функционал есть и спрятан где-то тут:

http://${ip}/webcapture.jpg?command=snap&channel=${channel}&user=${user}&password=${pass}"

Целиком плод нашего совместного с GPT-5 творчества выглядит следующим образом:

Посмотреть Shell-скрипт
cat /opt/bin/alarm_handler.sh
#!/bin/sh

# robust PATH + ASCII locale (we print UTF-8 via printf bytes)
PATH=/opt/sbin:/opt/bin:/opt/usr/sbin:/opt/usr/bin:/usr/sbin:/usr/bin:/sbin:/bin
export LANG=C
export LC_ALL=C

# === Telegram ===
BOT="BOTID:"
CHAT="-11111111"

# === NVR settings for snapshot ===
NVR_IP="192.168.1.49"
NVR_USER="admin"
NVR_PASS="pass"

SPAM_WINDOW=5                             # seconds to suppress repeats

STATE_FILE="/opt/tmp/alarm_state.txt"
TMP="/opt/tmp/alarm.$$"
LOG_FILE="/opt/var/log/alarm-center.log"
SNAP_DIR="/opt/var/alarm_snaps"

log() {
  TS="$(date '+%Y-%m-%d %H:%M:%S')"
  printf '[%s] %s\n' "$TS" "$*" >> "$LOG_FILE"
}

mkdir -p /opt/tmp /opt/var/log "$SNAP_DIR"
cat > "$TMP"

TS=$(date '+%Y-%m-%d %H:%M:%S')

# Save raw packet for debug
echo "[$TS]" >> "$LOG_FILE"
cat "$TMP" >> "$LOG_FILE"
echo >> "$LOG_FILE"

# --- Extract JSON ---
JSON=$(grep -ao '{.*}' "$TMP" | head -n1)

EVENT=$(echo "$JSON" | jq -r '.Event')
STATUS=$(echo "$JSON" | jq -r '.Status')
CHAN=$(echo "$JSON" | jq -r '.Channel')
TIME=$(echo "$JSON" | jq -r '.StartTime')
TYPE=$(echo "$JSON" | jq -r '.Type')

# --- Channel mapping (0-based) ---
case "$CHAN" in
  0) CHAN_NAME="banya2dom" ;;
  1) CHAN_NAME="dom2zad" ;;
  2) CHAN_NAME="dom2banya" ;;
  3) CHAN_NAME="dom2parking" ;;
  4) CHAN_NAME="dom2les" ;;
  *) CHAN_NAME="Channel-${CHAN}" ;;
esac

# --- UTF-8 emojis via bytes (safe for BusyBox) ---
EMOJI_HUMAN="$(printf '\xF0\x9F\x91\xA4')"   # 👤
EMOJI_CAR="$(printf '\xF0\x9F\x9A\x97')"     # 🚗
EMOJI_FACE="$(printf '\xF0\x9F\x99\x82')"    # 🙂
EMOJI_MASK="$(printf '\xF0\x9F\x8E\xAD')"    # 🎭
EMOJI_LOSS="$(printf '\xF0\x9F\x93\xB4')"    # 📴
EMOJI_CAM="$(printf '\xF0\x9F\x93\xB7')"     # 📷

# --- Policy per event ---
TAKE_SNAPSHOT=0   # pull snapshot?
REPORT_STOP=0     # send Stop too?
case "$EVENT" in
  HumanDetect)                   EMOJI="$EMOJI_HUMAN"; TAKE_SNAPSHOT=1; REPORT_STOP=0 ;;
  CarDetect|VehicleDetect)       EMOJI="$EMOJI_CAR";   TAKE_SNAPSHOT=1; REPORT_STOP=0 ;;
  FaceDetect)                    EMOJI="$EMOJI_FACE";  TAKE_SNAPSHOT=1; REPORT_STOP=0 ;;
  BlindDetect|CameraMask|Masking) EMOJI="$EMOJI_MASK"; TAKE_SNAPSHOT=0; REPORT_STOP=1 ;;
  LossDetect|VideoLoss|SignalLoss) EMOJI="$EMOJI_LOSS"; TAKE_SNAPSHOT=0; REPORT_STOP=1 ;;
  *)                              EMOJI="$EMOJI_CAM";  TAKE_SNAPSHOT=0; REPORT_STOP=1 ;;
esac

# --- Filter Stop when not needed ---
if [ "$REPORT_STOP" -eq 0 ] && [ "$STATUS" = "Stop" ]; then
  rm -f "$TMP"
  exit 0
fi

# --- Anti-spam (distinct key for Start/Stop when REPORT_STOP=1) ---
KEY="${EVENT}_${CHAN}"
[ "$REPORT_STOP" -eq 1 ] && KEY="${EVENT}_${STATUS}_${CHAN}"

NOW=$(date +%s)
LAST_TS=0
[ -f "$STATE_FILE" ] && LAST_TS=$(grep "^$KEY " "$STATE_FILE" | awk '{print $2}')
grep -v "^$KEY " "$STATE_FILE" 2>/dev/null > "$STATE_FILE.tmp"
echo "$KEY $NOW" >> "$STATE_FILE.tmp"
mv "$STATE_FILE.tmp" "$STATE_FILE"

if [ -n "$LAST_TS" ] && [ $((NOW - LAST_TS)) -lt $SPAM_WINDOW ]; then
  rm -f "$TMP"
  exit 0
fi

# --- Optional snapshot (1-based channel index) ---
JPEG_FILE=""
if [ "$TAKE_SNAPSHOT" -eq 1 ]; then
  JPEG_FILE="${SNAP_DIR}/${KEY}_${NOW}.jpg"
  SNAP_CHAN=$((CHAN + 1))
  SNAP_URL="http://${NVR_IP}/webcapture.jpg?command=snap&channel=${SNAP_CHAN}&user=${NVR_USER}&password=${NVR_PASS}"
  curl -m 5 -s "$SNAP_URL" -o "$JPEG_FILE"
  [ ! -s "$JPEG_FILE" ] && JPEG_FILE=""
fi

# --- Build message ---
TITLE="${EMOJI} Alarm: ${EVENT}"
[ "$REPORT_STOP" -eq 1 ] && TITLE="${TITLE} (${STATUS})"

TEXT="${TITLE}
Time: ${TIME}
Channel: ${CHAN_NAME}"

TG_API="https://api.telegram.org/bot${BOT}"

log "send: bot_len=${#BOT} chat='${CHAT}' jpeg='${JPEG_FILE:-none}'"

if [ -n "$BOT" ] && [ -n "$CHAT" ]; then
  if [ -n "$JPEG_FILE" ] && [ -s "$JPEG_FILE" ]; then
    CODE=$(curl -m 10 -s -w '%{http_code}' -o /dev/null \
           -F chat_id="${CHAT}" \
           -F caption="${TEXT}" \
           -F photo="@$JPEG_FILE" \
           "$TG_API/sendPhoto")
    log "sendPhoto code=$CODE file=$(basename "$JPEG_FILE")"
  else
    CODE=$(curl -m 8 -s -w '%{http_code}' -o /dev/null \
           -d "chat_id=${CHAT}" \
           -d "text=${TEXT}" \
           -d "disable_web_page_preview=true" \
           "$TG_API/sendMessage")
    log "sendMessage code=$CODE"
  fi
else
  log "send: SKIP (empty BOT/CHAT)"
fi

# --- Cleanup ---
rm -f "$TMP"
find /opt/tmp -type f -name "alarm.*" -mmin +5 -delete 2>/dev/null
find "$SNAP_DIR" -type f -name "*.jpg" -mmin +5 -delete 2>/dev/null

# --- Log rotate (>1MB, keep last 5) ---
MAX_SIZE=$((1024 * 1024))
if [ -f "$LOG_FILE" ] && [ "$(stat -c%s "$LOG_FILE")" -ge "$MAX_SIZE" ]; then
  RTS=$(date '+%Y%m%d-%H%M%S')
  mv "$LOG_FILE" "${LOG_FILE}.${RTS}"
  gzip "${LOG_FILE}.${RTS}"
  : > "$LOG_FILE"
  ls -1t ${LOG_FILE}.*.gz 2>/dev/null | tail -n +6 | xargs -r rm -f
fi

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

Запускается всё это по cron’у через socat.

~ # crontab -l
*/1 * * * * /opt/bin/alarm_watchdog.sh

~ # cat /opt/bin/alarm_watchdog.sh
#!/bin/sh

PATH=/opt/sbin:/opt/bin:/opt/usr/sbin:/opt/usr/bin:/usr/sbin:/usr/bin:/sbin:/bin

LOG="/opt/var/log/alarm-center.log"
HANDLER="/opt/bin/alarm_handler.sh"
PORT=15002

if ! netstat -lnp 2>/dev/null | grep -q ":$PORT .*LISTEN"; then
    socat -u TCP-LISTEN:$PORT,fork EXEC:$HANDLER >/dev/null 2>&1 &
    echo "$(date '+%Y-%m-%d %H:%M:%S') restarting socat" >> "$LOG"
fi

Итоги

Мягко говоря, решение не самое элегантное, но вот так на коленке был собран небольшой сетап. Теперь можно следить за своим домом в долгих поездках. 

По желанию можно развить мысль дальше. Например, включить проверку на наличие каких-то конкретных активных клиентов Wi-Fi, чтобы не шуметь зря, когда жильцы дома. Или дополнительно делать проверку каких-то датчиков, если таковые имеются. Тут всё зависит от фантазии.

P. S.

Читайте также в нашем блоге: