feat(cd): get_tyy_data 支持并行获取维视智造SVS识别字符

新增 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) <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-06-01 10:13:15 +08:00
parent 804746fde8
commit 2f8beec37f
1 changed files with 83 additions and 6 deletions

View File

@ -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