OPi: авто-захват кадра с RTSP + отправка на распознавание номера

This commit is contained in:
claude
2026-06-26 15:09:33 +03:00
parent 69a773a2c9
commit 9d5d80ff7b
5 changed files with 90 additions and 0 deletions
Binary file not shown.
+54
View File
@@ -26,6 +26,9 @@ import datetime
import threading
import select
import os
import base64
import json
import urllib.request
# ── Серийный порт ────────────────────────────────────────────────────────────
SERIAL_PORT = '/dev/ttyUSB0'
@@ -43,6 +46,13 @@ MQTT2_PORT = 1884
MQTT2_USER = 'esp32'
MQTT2_PASS = 'Esp32Scales#2026'
# ── Камера + распознавание (наш VPS) ─────────────────────────────────────────
CAPTURE_ENABLED = True
RTSP_URL = 'rtsp://iivideo:Vk9mT2xLp7nQ4wBj@192.168.20.3:9784/cameras/124/streaming/main'
SERVER_CAPTURE_URL = 'https://scales.zeroday.su/api/capture'
CAPTURE_DEVICE_ID = 'scales_opi'
FRAME_PATH = '/tmp/scale_frame.jpg'
MQTT_WEIGHT_TOPIC = 'scale/weight'
MQTT_GO_TOPIC = 'scale/traffic/go'
@@ -171,6 +181,46 @@ def publish_weight(w):
print(f"[{name}] Ошибка публикации: {e}")
# ════════════════════════════════════════════════════════════════════════════════
# Захват кадра с камеры и отправка на сервер распознавания
# ════════════════════════════════════════════════════════════════════════════════
def grab_frame():
"""Снимает один кадр с RTSP-камеры через ffmpeg. Возвращает JPEG-байты или None."""
cmd = ['ffmpeg', '-y', '-loglevel', 'error', '-rtsp_transport', 'tcp',
'-i', RTSP_URL, '-frames:v', '1', '-update', '1',
'-vf', 'scale=1280:-2', '-q:v', '4', '-f', 'image2', FRAME_PATH]
try:
subprocess.run(cmd, timeout=15, check=True)
with open(FRAME_PATH, 'rb') as f:
return f.read()
except Exception as e:
print(f"[CAPTURE] Ошибка захвата кадра: {e}")
return None
def capture_and_send(weight, event_id):
"""Кадр + вес → наш VPS. Запускать в отдельном потоке (не блокирует светофор)."""
img = grab_frame()
if not img:
print(f"[CAPTURE] {event_id}: кадр не получен, пропуск")
return
try:
payload = json.dumps({
"event_id": event_id,
"device_id": CAPTURE_DEVICE_ID,
"weight": round(weight, 1),
"captured_at": datetime.datetime.now().isoformat(),
"media_type": "image/jpeg",
"photo_b64": base64.b64encode(img).decode(),
}).encode()
req = urllib.request.Request(SERVER_CAPTURE_URL, data=payload,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=20) as r:
print(f"[CAPTURE] {event_id}: отправлено ({len(img)} б), ответ {r.status}")
except Exception as e:
print(f"[CAPTURE] {event_id}: ошибка отправки: {e}")
# ════════════════════════════════════════════════════════════════════════════════
# Парсер протокола A9
# ════════════════════════════════════════════════════════════════════════════════
@@ -274,7 +324,11 @@ def main():
if max(vals) - min(vals) <= STABLE_DELTA:
avg = sum(vals) / len(vals)
print(f"[STATE] → WAIT_GO: вес стабилен {avg:.1f} кг, отправляем")
event_id = f"opi-{int(time.time()*1000)}"
publish_weight(avg)
if CAPTURE_ENABLED:
threading.Thread(target=capture_and_send,
args=(avg, event_id), daemon=True).start()
state = 'WAIT_GO'
go_event.clear()
go_sent_time = time.time()