From 2f8beec37f5be59787260a35abb650f59e1608f3 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 1 Jun 2026 10:13:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(cd):=20get=5Ftyy=5Fdata=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=B9=B6=E8=A1=8C=E8=8E=B7=E5=8F=96=E7=BB=B4=E8=A7=86?= =?UTF-8?q?=E6=99=BA=E9=80=A0SVS=E8=AF=86=E5=88=AB=E5=AD=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 get_svs_char/_parse_vtfp_line,通过 VTFP 文本协议触发拍照并解析 识别字符。get_tyy_data 增加可选 svs_host/svs_port/svs_flow,采集器与 SVS 并行触发后合并为一个 dict 返回,任一失败整体抛错。 Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/em/cd.py | 89 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/apps/em/cd.py b/apps/em/cd.py index 6ce0387a..17ab61fb 100644 --- a/apps/em/cd.py +++ b/apps/em/cd.py @@ -1,6 +1,7 @@ import socket from rest_framework.exceptions import ParseError import json +import re import time from django.core.cache import cache from apps.utils.thread import MyThread @@ -8,6 +9,7 @@ import uuid import logging import threading import requests +from concurrent.futures import ThreadPoolExecutor myLogger = logging.getLogger('log') @@ -243,10 +245,85 @@ def get_tyy_data_3(*args, retry=2): raise ParseError(f"未知错误: {str(e)}") -def get_tyy_data(*args): - host, port = args[0], int(args[1]) - r = requests.get(f"http://127.0.0.1:2300?host={host}&port={port}") - res = r.json() - if "err_msg" in res: - raise ParseError(res["err_msg"]) +def _parse_vtfp_line(line): + """解析 SVS 的 VTFP 应答行,返回 (识别字符, 字段列表)。 + + 返回格式形如 ``VTFP 0 <用户自定义数据...>``: + - 第 1 个字段固定为 ``VTFP`` + - 第 2 个字段为状态码(0 成功,非 0 失败,失败时后面无其他数据) + - 其余字段为用户在程序中自定义输出的数据(即识别出的字符) + + 分隔符按协议同时兼容空格和逗号。 + """ + parts = [p for p in re.split(r"[,\s]+", line.strip()) if p != ""] + if not parts or parts[0].upper() != "VTFP": + raise ParseError(f"SVS返回格式错误-{line}") + if len(parts) < 2: + raise ParseError(f"SVS返回缺少状态码-{line}") + status = parts[1] + if status != "0": + raise ParseError(f"SVS触发失败,状态码-{status}") + fields = parts[2:] # 用户自定义数据(识别字符),可能含可选的流程单元号回显 + char = " ".join(fields) + return char, fields + + +def get_svs_char(host, port, flow_unit=0, timeout=10): + """连接 SVS(维视智造 VisionBank),用 VTFP 触发拍照并取回识别字符。 + + 返回 ``{"char": 识别字符, "fields": [...], "raw": 原始应答行}``。 + """ + cmd = f"VTFP {flow_unit}\r\n".encode("ascii") + try: + with socket.create_connection((host, int(port)), timeout=timeout) as sc: + sc.sendall(cmd) + buf = bytearray() + while True: + chunk = sc.recv(1024) + if not chunk: + break + buf.extend(chunk) + text = buf.decode("utf-8", errors="replace") + # VTFP 应答前可能先收到 VGIF 等行,定位到 VTFP 那一行再解析 + for ln in text.split("\r\n"): + if ln.startswith("VTFP"): + char, fields = _parse_vtfp_line(ln) + return {"char": char, "fields": fields, "raw": ln} + except ParseError: + raise + except (socket.timeout, ConnectionError, OSError) as e: + raise ParseError(f"SVS连接失败-{str(e)}") + raise ParseError("SVS未返回有效数据") + + +def get_tyy_data(host, port, svs_host=None, svs_port=None, svs_flow=0): + """获取采集器数据;若传入 svs_host/svs_port,并行获取 SVS 识别字符并合并返回。 + + 采集器与 SVS 任一失败均整体抛错。 + """ + port = int(port) + + def fetch_tyy(): + r = requests.get(f"http://127.0.0.1:2300?host={host}&port={port}") + res = r.json() + if "err_msg" in res: + raise ParseError(res["err_msg"]) + return res + + if not (svs_host and svs_port): + return fetch_tyy() + + # 采集器与 SVS 并行触发,尽量贴近同一时刻;任一失败在 result() 处抛出 + with ThreadPoolExecutor(max_workers=2) as ex: + tyy_future = ex.submit(fetch_tyy) + svs_future = ex.submit(get_svs_char, svs_host, svs_port, svs_flow) + res = tyy_future.result() + svs = svs_future.result() + + if not isinstance(res, dict): + # 采集器返回的不是 dict 时,包成一个完整 dict + res = {"tyy": res} + res["svs_char"] = svs["char"] + res["svs_fields"] = svs["fields"] + res["svs_raw"] = svs["raw"] return res \ No newline at end of file