diff --git a/apps/em/cd.py b/apps/em/cd.py index 38fd8edc..8d94c67d 100644 --- a/apps/em/cd.py +++ b/apps/em/cd.py @@ -271,23 +271,41 @@ def get_svs_char(host, port, flow_unit=0, timeout=10): """连接 SVS(维视智造 VisionBank),用 VTFP 触发拍照并取回识别字符。 返回 ``{"char": 识别字符, "fields": [...], "raw": 原始应答行}``。 + + 不同流程的输出配置可能不一样:有的回 ``VTFP 0 <数据>`` 完整应答,有的只回 + 用户自定义的识别内容(如 ``1``)。这里读到第一行完整数据就返回:带 ``VTFP`` + 前缀的按协议解析,否则整行当作识别内容。 """ - cmd = f"VTFP {flow_unit}\r\n".encode("ascii") + cmd = f"VTFP {flow_unit}\r".encode("ascii") # 该设备只认单 CR 结尾, 带 \n 会无响应 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 + sc.sendall(cmd) + # 第一次用完整超时等设备响应; 设备返回的识别内容(如 b'1')末尾可能没有 + # 换行符, 因此拿到数据后只用很短的超时把剩余字节收干净(兼容多位数分包), + # 不再死等换行, 否则会卡到超时 + chunk = sc.recv(1024) + if chunk: 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} + sc.settimeout(0.3) + while b"\n" not in buf and b"\r" not in buf: + try: + more = sc.recv(1024) + except socket.timeout: + break + if not more: + break + buf.extend(more) + text = buf.decode("utf-8", errors="replace") + for ln in re.split(r"[\r\n]+", text): + ln = ln.strip() + if not ln: + continue + if ln.upper().startswith("VTFP"): + char, fields = _parse_vtfp_line(ln) + return {"char": char, "fields": fields, "raw": ln} + # 无 VTFP 前缀,设备直接回识别内容 + return {"char": ln, "fields": ln.split(), "raw": ln} except ParseError: raise except (socket.timeout, ConnectionError, OSError) as e: diff --git a/apps/em/views.py b/apps/em/views.py index 9524b78f..e872f421 100644 --- a/apps/em/views.py +++ b/apps/em/views.py @@ -14,6 +14,7 @@ from rest_framework.decorators import action from rest_framework.serializers import Serializer from django.db import transaction from apps.em.services import daoru_equipment +from apps.em import cd from rest_framework.response import Response from django.conf import settings from django.db.models import Count, Case, When, IntegerField @@ -127,6 +128,26 @@ class EquipmentViewSet(CustomModelViewSet): return Response(json_result) + @swagger_auto_schema( + manual_parameters=[ + openapi.Parameter(name="host", in_=openapi.IN_QUERY, description="SVS IP", type=openapi.TYPE_STRING, required=True), + openapi.Parameter(name="port", in_=openapi.IN_QUERY, description="SVS 端口", type=openapi.TYPE_STRING, required=True), + openapi.Parameter(name="flow_unit", in_=openapi.IN_QUERY, description="识别码(流程单元号), 多台 SVS 共用 IP 时用于区分设备", type=openapi.TYPE_STRING, required=True), + ] + ) + @action(methods=["get"], detail=False, perms_map={"get": "*"}) + def get_svs_char(self, request, *args, **kwargs): + """获取 SVS 识别字符 + + 触发 SVS 拍照并取回识别字符, flow_unit 为识别码用于区分共用 IP 的不同设备 + """ + host = request.query_params.get("host", None) + port = request.query_params.get("port", None) + if not host or not port: + raise ParseError("请传入host和port参数") + flow_unit = request.query_params.get("flow_unit", 0) + return Response(cd.get_svs_char(host, port, flow_unit)) + class EcheckRecordViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, CustomGenericViewSet): """