Explorar o código

feat: 添加设备安装地址上报与日志,重构存储配置

1. 新增install_address参数传递链路,在摄像头配置中添加安装地址
2. 重构存储配置为分层结构,新增日志轮转配置文件
3. 为第三方上报添加payload日志打印
wenhongquan hai 1 mes
pai
achega
a97b8f49e8

+ 28 - 0
AGENTS.md

@@ -73,3 +73,31 @@ This must happen **before** cv2 import to prevent FFmpeg `async_lock` assertion
 - `testrk3588/` is standalone test code with its own `requirements.txt` — not a dependency of the main service.
 - Config has hardcoded IPs and credentials in `config/camera.py` and `config/oss.py`. These match `devices.md` — update both when changing devices.
 - `service.sh` hardcodes paths to `/home/admin/dsh/dual_camera_system` — only works on the RK3588 device.
+
+## Deployment History
+
+### 2026-08-27 标段1 (10.126.126.1)
+- 配置:`config/camera.py` — `install_address='德胜河桥-西侧'`
+- 配置:`config/system.py` — `STORAGE_CONFIG` 改为 `retention_days: 1`
+- 日志:`/etc/logrotate.d/dsh-dual-camera` — 10MB/5份/7天过期
+- 代码:`third_party_pusher.py` — 添加第三方上报 payload 日志(第362行)
+- 代码:`app.py`、`core/capture_uploader.py` — `install_address` 传递链路
+- 服务管理:使用 `systemctl restart|stop|start|status dsh-dual-camera`
+- device_id:未改动
+
+### 2026-08-27 标段2 (192.168.8.3 测试环境)
+- 配置:`config/camera.py` — `install_address` 按测试环境配置
+- 代码:同上,install_address 传递链路已部署
+- 服务管理:同上,systemctl
+
+### 2026-08-27 标段3 (10.11.13.1)
+- 配置:`config/camera.py` — `install_address='保通桥-西侧'`
+- 代码:`third_party_pusher.py` — payload 日志已部署
+- 服务管理:`systemctl restart dsh-dual-camera`
+- device_id:`93cb0645-36dd-48f2-b75a-e1a016ee92c9`(未改动)
+
+### 2026-08-27 标段4 (10.11.12.1)
+- 配置:`config/camera.py` — `install_address='新芮庄桥-东侧'`
+- 代码:`third_party_pusher.py` — payload 日志已部署
+- 服务管理:`systemctl restart dsh-dual-camera`
+- device_id:`356c1808-d06b-443b-b84b-ea12c6443d20`(未改动)

BIN=BIN
dual_camera_system/__pycache__/third_party_pusher.cpython-310.pyc


+ 4 - 4
dual_camera_system/app.py

@@ -135,7 +135,7 @@ def create_app(test_mode: bool = False) -> FastAPI:
                 )
 
                 if SYSTEM_CONFIG.get("enable_detection", True) and detector_service is not None:
-                    def panorama_detect_loop(g=gid, uploader=uploader, detector=detector_service):
+                    def panorama_detect_loop(g=gid, uploader=uploader, detector=detector_service, install_address=group.get("panorama", {}).get("install_address", "")):
                         interval = 0.5
                         frame_count = 0
                         while not stop_event.is_set():
@@ -156,7 +156,7 @@ def create_app(test_mode: bool = False) -> FastAPI:
                                         } for d in dets]
                                         for dd in det_dicts:
                                             print(f"[detect] {g} bbox={dd['bbox']} conf={dd['confidence']:.3f}")
-                                        uploader.handle_detection("panorama", frame, det_dicts)
+                                        uploader.handle_detection("panorama", frame, det_dicts, install_address)
                                         for d in det_dicts:
                                             x1, y1, x2, y2 = d["bbox"]
                                             cv2.rectangle(marked, (x1, y1), (x2, y2), (0, 255, 0), 2)
@@ -174,7 +174,7 @@ def create_app(test_mode: bool = False) -> FastAPI:
                     threads.append(t_panorama)
 
                     if ptz_url:
-                        def ptz_detect_loop(g=gid, uploader=uploader, detector=detector_service):
+                        def ptz_detect_loop(g=gid, uploader=uploader, detector=detector_service, install_address=group.get("ptz", {}).get("install_address", "")):
                             interval = 0.5
                             while not stop_event.is_set():
                                 try:
@@ -189,7 +189,7 @@ def create_app(test_mode: bool = False) -> FastAPI:
                                             } for d in dets]
                                             for dd in det_dicts:
                                                 print(f"[detect] {g} PTZ bbox={dd['bbox']} conf={dd['confidence']:.3f}")
-                                            uploader.handle_detection("ptz", frame, det_dicts)
+                                            uploader.handle_detection("ptz", frame, det_dicts, install_address)
                                 except Exception as exc:
                                     print(f"[ptz_detect_loop {g}] error: {exc}")
                                 time.sleep(interval)

+ 4 - 0
dual_camera_system/config/camera.py

@@ -34,6 +34,7 @@ CAMERA_GROUPS = [
             'use_sdk': False,
             'resolution': (2560, 1440),
             'rtsp_url': 'rtsp://admin:QAZwsx12@192.168.8.2:554/Streaming/Channels/101',
+            'install_address': '新芮庄桥-东侧',
         },
         'ptz': {
             'ip': '192.168.8.5',
@@ -48,6 +49,7 @@ CAMERA_GROUPS = [
             'rtsp_url': 'rtsp://admin:Aa1234567@192.168.8.5:554/cam/realmonitor?channel=1&subtype=1',
             'pan_flip': True,
             'ceiling_mount': True,
+            'install_address': '新芮庄桥-东侧',
         },
     },
     {
@@ -65,6 +67,7 @@ CAMERA_GROUPS = [
             'use_sdk': False,
             'resolution': (3840, 1080),
             'rtsp_url': 'rtsp://admin:Aa1234567@192.168.1.8:554/cam/realmonitor?channel=1&subtype=0',
+            'install_address': '新芮庄桥-东侧',
         },
         'ptz': {
             'ip': '192.168.1.9',
@@ -79,6 +82,7 @@ CAMERA_GROUPS = [
             'rtsp_url': 'rtsp://admin:Aa1234567@192.168.1.9:554/cam/realmonitor?channel=1&subtype=0',
             'pan_flip': False,
             'ceiling_mount': True,
+            'install_address': '新芮庄桥-东侧',
         },
     },
 ]

+ 11 - 0
dual_camera_system/config/logrotate_dsh

@@ -0,0 +1,11 @@
+/home/admin/dsh/dual_camera_system/logs/dsh-dual-camera.log {
+    size 10M
+    rotate 5
+    copytruncate
+    compress
+    delaycompress
+    missingok
+    notifempty
+    dateext
+    maxage 7
+}

+ 14 - 4
dual_camera_system/config/system.py

@@ -5,9 +5,19 @@ SYSTEM_CONFIG = {
     'enable_detection': True,
 }
 
-# 存储配置 (从 device.py 迁移)
+# 存储配置
 STORAGE_CONFIG = {
-    'base_dir': '/home/admin/dsh',
-    'detection_dir': '/home/admin/dsh/detection_images',
-    'paired_dir': '/home/admin/dsh/paired_images',
+    'captures': {
+        'base_dir': 'data/captures',
+        'keep_local_copy': False,
+        'retention_days': 1,
+        'max_files': 2000,
+        'cleanup_interval_seconds': 3600,
+    },
+    'previews': {
+        'base_dir': 'data/previews',
+        'retention_days': 1,
+        'max_files': 1000,
+        'cleanup_interval_seconds': 3600,
+    },
 }

+ 2 - 0
dual_camera_system/core/capture_uploader.py

@@ -79,6 +79,7 @@ class CaptureUploader:
         camera_type: str,
         frame: np.ndarray,
         detections: List[Dict],
+        install_address: str = "",
     ) -> List[Dict]:
         self._validate_inputs(frame, detections)
 
@@ -158,6 +159,7 @@ class CaptureUploader:
                 "project_id": DEVICE_CONFIG.get("project_id", ""),
                 "timestamp": ts,
                 "camera_type": camera_type,
+                "install_address": install_address,
                 "image_paths": [original_path, marked_path],
                 "image_urls": image_urls,
                 "detections": [

+ 11 - 3
dual_camera_system/third_party_pusher.py

@@ -126,6 +126,7 @@ def _convert_to_legacy_batch_info(new_info: Dict[str, Any]) -> Dict[str, Any]:
         "project_id": new_info.get("project_id", ""),
         "timestamp": normalized_ts,
         "datetime": datetime.fromtimestamp(normalized_ts).isoformat(),
+        "install_address": new_info.get("install_address", ""),
         "total_persons": len(persons),
         "ptz_images_count": len(persons),
         "panorama": {
@@ -353,12 +354,17 @@ class ThirdPartyPusher:
             return False
         
         url = f"{self.base_url}{self.batch_report_url}"
-        
+
         # 构建请求数据
         payload = self._build_payload(report)
-        
+
+        try:
+            logger.info("[第三方平台] 上报 payload: %s", json.dumps(payload, ensure_ascii=False))
+        except Exception:
+            logger.info("[第三方平台] 上报 payload: %s", payload)
+
         headers = self._get_auth_headers()
-        
+
         for attempt in range(self.retry_count):
             try:
                 if self.data_format == 'json':
@@ -438,11 +444,13 @@ class ThirdPartyPusher:
             payload = {
                 'createTime': datetime.fromtimestamp(normalized_ts).strftime("%Y-%m-%d %H:%M:%S"),
                 'addr': f"设备{report.device_id}批次上报",
+                'installAddress': batch_info.get('install_address', ''),
                 'ext1': json.dumps([image_url]),
                 'ext2': json.dumps({
                     'batchId': report.batch_id,
                     'deviceId': report.device_id,
                     'projectId': report.project_id,
+                    'installAddress': batch_info.get('install_address', ''),
                     'totalPersons': len(batch_info.get('detections', [])),
                     'ptzImagesCount': 1 if batch_info.get('camera_type') == 'ptz' else 0,
                     'persons': batch_info.get('detections', []),