Files
weighing-controller/RECOVERY.md
T
ZeroDay aafbbc64d3 Runbook восстановления OPi на новой SD-карте + systemd-юнит
Инцидент 22.07: ФС ушла в read-only — захват кадра и SSH отвалились,
heartbeat и MQTT-вес продолжали работать. В репозитории не хватало
юнита и инструкции подъёма с нуля.

- orange_pi/scales.service (восстановлен по systemctl status)
- RECOVERY.md: установка, проверки, продление жизни карты,
  диагностика через MQTT без SSH
2026-07-23 09:09:21 +03:00

5.6 KiB

Восстановление Orange Pi на новой SD-карте

Runbook на случай, когда карта деградировала и устройство надо поднять с нуля. Составлен во время инцидента 22.07.2026: ФС ушла в read-only — захват кадра и SSH отвалились, при этом heartbeat и публикация веса в MQTT продолжали работать.

Что взять на пост

  • новая SD-карта, high-endurance (Samsung PRO Endurance / SanDisk Max Endurance); 32 ГБ хватает с запасом — занято ~12%
  • образ Armbian для Orange Pi PC, записанный заранее
  • orange_pi/scales.py, orange_pi/outbox.py, orange_pi/scales.service из этого репозитория
  • старую карту НЕ выбрасывать: с неё может понадобиться вытащить остатки очереди outbox

1. Система

apt update
apt install -y python3 python3-pip ffmpeg
pip3 install paho-mqtt        # на свежих Debian/Armbian добавить --break-system-packages

Больше внешних зависимостей нет — всё остальное в скрипте из stdlib.

2. Файлы

Рабочий файл на устройстве — именно /root/scales.py; репозиторий на OPi не клонируется, файл доставляется вручную. outbox.py кладётся рядом, в /root.

cp scales.py outbox.py /root/

Актуальную версию можно скачать прямо с сервера:

curl -s -o /root/scales.py https://scales.zeroday.su/testphotos/scales.py

3. Сервис

cp scales.service /etc/systemd/system/scales.service
systemctl daemon-reload
systemctl enable --now scales.service
systemctl status scales.service --no-pager

4. Проверки после старта

# в логе должны появиться MQTT1/MQTT2, GPIO, OUTBOX, HEARTBEAT
journalctl -u scales.service -n 30 --no-pager

# весовой терминал
ls -l /dev/ttyUSB0

# камера — тот же вызов, что внутри grab_frame()
ffmpeg -y -loglevel error -rtsp_transport tcp -i "<RTSP_URL из scales.py>" \
  -frames:v 1 -update 1 -q:v 2 -f image2 /tmp/scale_frame.jpg && ls -l /tmp/scale_frame.jpg

# офлайн-распознавание на боксе
curl -s http://192.168.20.4:5010/health

# сервер принимает (ожидаемый ответ: {"error":"event_id required"})
curl -s -X POST -H "Content-Type: application/json" -d '{}' https://scales.zeroday.su/api/local-plate

Финальная проверка — кнопка «Тест-снимок» в панели: фото должно появиться в заездах.

5. Чтобы карта прожила дольше

Главный убийца SD-карт — постоянная запись логов.

mkdir -p /etc/systemd/journald.conf.d
printf '[Journal]\nStorage=volatile\nRuntimeMaxUse=32M\n' > /etc/systemd/journald.conf.d/volatile.conf
systemctl restart systemd-journald

Убедиться, что /tmp — tmpfs (туда пишется кадр перед отправкой):

findmnt /tmp

Если нет — добавить в /etc/fstab:

tmpfs /tmp tmpfs defaults,nosuid,nodev,size=128M 0 0

На перспективу: перенос системы на USB-SSD или eMMC снимает проблему целиком.

Диагностика без SSH

Устройство управляется через MQTT-брокер даже когда SSH недоступен (на сервере: localhost:1883, пользователь scales-server).

# живой heartbeat раз в 30 с — значит процесс жив
mosquitto_sub -h 127.0.0.1 -p 1883 -u scales-server -P '<пароль>' -t 'scale/device/status' -v

# команды: ping (мгновенный статус), test-shot (проверка камеры), reboot
mosquitto_pub -h 127.0.0.1 -p 1883 -u scales-server -P '<пароль>' -t 'scale/device/cmd' -m '{"cmd":"ping"}'

Как читать симптомы:

признак что значит
heartbeat идёт, на ping отвечает мгновенно процесс жив, ресурсы в порядке
outbox_pending: 0, но заездов на сервере нет capture_and_send вышел до постановки в очередь — не получен кадр
test-shot не даёт фото, а RTSP из VLC играет причина локальная на OPi: запись в /tmp или ffmpeg
SSH рвёт соединение на key exchange sshd не может создать сессию — характерно для read-only ФС

Совпадение последних трёх — почти наверняка карта.

Известная недоработка

capture_and_send() при неудачном захвате кадра выходит по return до постановки события в очередь. Значит отказ камеры теряет не фото, а всю запись о взвешивании — именно так были потеряны заезды 20.07.2026. Вес должен доезжать до сервера и без фото; поправить при ближайшем доступе к устройству.