OPi: авто-захват кадра с RTSP + отправка на распознавание номера
This commit is contained in:
Binary file not shown.
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user