5 Commits 4720f5e0b8 ... 2ead12abb7

Author SHA1 Message Date
  panqiuyao 2ead12abb7 Merge remote-tracking branch 'origin/multi_camera_version_0622' into feature-frontend 15 hours ago
  rambo edd2d348e2 feat(camera): 添加相机焦距控制功能 17 hours ago
  rambo f39f08416f feat(mcu): 添加动态参数配置管理功能 1 day ago
  rambo 022ca03e6e feat(mcu): 实现相机高度偏差补偿功能 2 days ago
  rambo c3164bd00f feat(api): 添加通过货号获取动作名称列表接口 2 weeks ago

+ 67 - 120
python/api.py

@@ -1232,6 +1232,73 @@ def get_photo_record_detail(goods_art_no: str = None):
     }
 
 
+@app.get("/get_action_names_by_goods", description="通过货号获取动作名称列表")
+def get_action_names_by_goods(goods_art_no: str = None):
+    """
+    通过货号查询PhotoRecord表,按自然排序获取action_id,
+    再查询DeviceConfig获取对应的action_name,按顺序返回action_name数组
+    
+    Args:
+        goods_art_no: 货号
+        
+    Returns:
+        如果货号不存在: {"code": 1, "msg": "货号不存在", "data": None}
+        如果货号存在: {"code": 0, "msg": "", "data": [action_name1, action_name2, ...]}
+    """
+    if goods_art_no is None or goods_art_no.strip() == "":
+        return {"code": 1, "msg": "参数错误", "data": None}
+    
+    session = SqlQuery()
+    try:
+        # 1. 查询该货号的所有记录
+        photos = CRUD(PhotoRecord)
+        photo_records = photos.read_all(
+            session, 
+            conditions={"goods_art_no": goods_art_no, "delete_time": None}
+        )
+        
+        # 2. 如果货号不存在,返回错误
+        if not photo_records or len(photo_records) == 0:
+            return {"code": 1, "msg": "货号不存在", "data": None}
+        
+        # 3. 按自然排序对记录进行排序(基于image_index或其他字段)
+        # 使用natsort对image_index进行自然排序
+        from natsort import natsorted, ns
+        
+        # 将记录转换为可排序的列表,按image_index排序
+        sorted_records = natsorted(
+            photo_records, 
+            key=lambda x: str(x.image_index) if x.image_index is not None else "",
+            alg=ns.PATH | ns.IGNORECASE
+        )
+        
+        # 4. 提取排序后的action_id列表
+        action_ids = [record.action_id for record in sorted_records if record.action_id is not None]
+        
+        if not action_ids:
+            return {"code": 0, "msg": "", "data": []}
+        
+        # 5. 根据action_id查询DeviceConfig获取action_name
+        device_config_crud = CRUD(DeviceConfig)
+        action_names = []
+        
+        for action_id in action_ids:
+            device_config = device_config_crud.read(
+                session, 
+                conditions={"id": action_id,'take_picture':True}
+            )
+            if device_config and device_config.action_name:
+                action_names.append(device_config.action_name)
+        
+        return {"code": 0, "msg": "", "data": action_names}
+        
+    except Exception as e:
+        logger.error(f"查询货号 {goods_art_no} 的动作名称失败: {str(e)}")
+        return {"code": 1, "msg": f"查询失败: {str(e)}", "data": None}
+    finally:
+        session.close()
+
+
 @app.post("/delect_goods_arts", description="通过货号删除记录")
 def delect_goods_arts(params: PhotoRecordDelete):
     limit_path = "{}/{}".format(settings.OUTPUT_DIR,
@@ -2077,123 +2144,3 @@ async def import_images_from_dir(params:ImportDirs):
         logger.error(f"API 调用异常: {str(e)}")
         raise UnicornException(f"{str(e)}")
 
-
-def auto_add_missing_columns():
-    """
-    自动检测并添加缺失的数据库字段(打包兼容版本)
-    - 使用原生 SQLite,避免 SQLAlchemy 在打包环境中的问题
-    - 简化字段类型处理,避免复杂类型导致的问题
-    - 只添加新字段,不会修改或删除现有字段
-    - 不会丢失任何数据
-    - 可以安全地重复执行(幂等性)
-    """
-    try:
-        import sqlite3
-
-        # 获取数据库文件路径
-        db_path = sqlite_file_name.replace("sqlite:///", "")
-
-        # 直接连接 SQLite 数据库
-        conn = sqlite3.connect(db_path)
-        cursor = conn.cursor()
-
-        # 定义模型和表名的映射
-        models_tables = [
-            (DeviceConfig, "device_config"),
-            (PhotoRecord, "photo_record"),
-            (DeviceConfigTabs, "device_config_tabs"),
-            (SysConfigs, "sys_configs"),
-        ]
-
-        for model_class, table_name in models_tables:
-            try:
-                # 检查表是否存在
-                cursor.execute(
-                    f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}'")
-                if not cursor.fetchone():
-                    print(f"⚠️ 表 {table_name} 不存在,跳过")
-                    continue
-
-                # 获取现有列
-                cursor.execute(f"PRAGMA table_info({table_name})")
-                existing_columns = {row[1] for row in cursor.fetchall()}
-                print(f"📋 表 {table_name} 现有字段: {existing_columns}")
-
-                # 从模型中获取所有字段
-                model_columns = model_class.__table__.columns
-
-                for column_name, column_obj in model_columns.items():
-                    # 如果列已存在,跳过
-                    if column_name in existing_columns:
-                        continue
-
-                    try:
-                        # 简化类型映射 - 使用 SQLite 原生类型
-                        column_type_str = str(column_obj.type).upper()
-
-                        # 映射 SQLAlchemy 类型到 SQLite 类型
-                        if 'INTEGER' in column_type_str or 'INT' in column_type_str:
-                            sqlite_type = 'INTEGER'
-                        elif 'FLOAT' in column_type_str or 'DOUBLE' in column_type_str or 'DECIMAL' in column_type_str or 'REAL' in column_type_str:
-                            sqlite_type = 'REAL'
-                        elif 'BOOLEAN' in column_type_str or 'BOOL' in column_type_str:
-                            sqlite_type = 'INTEGER'  # SQLite 用 0/1 表示布尔
-                        elif 'DATETIME' in column_type_str or 'TIMESTAMP' in column_type_str:
-                            sqlite_type = 'TEXT'  # SQLite 用 TEXT 存储时间
-                        elif 'VARCHAR' in column_type_str or 'CHAR' in column_type_str or 'STRING' in column_type_str or 'TEXT' in column_type_str:
-                            sqlite_type = 'TEXT'
-                        else:
-                            sqlite_type = 'TEXT'  # 默认使用 TEXT
-
-                        # 处理 NULL/NOT NULL
-                        nullable_str = "" if column_obj.nullable else "NOT NULL"
-
-                        # 简化默认值处理
-                        default_str = ""
-                        if column_obj.default is not None:
-                            default_value = column_obj.default.arg
-                            if callable(default_value):
-                                # 跳过函数默认值
-                                default_str = ""
-                            elif isinstance(default_value, bool):
-                                default_str = f"DEFAULT {int(default_value)}"
-                            elif isinstance(default_value, (int, float)):
-                                default_str = f"DEFAULT {default_value}"
-                            elif isinstance(default_value, str):
-                                default_str = f"DEFAULT '{default_value}'"
-
-                        # 构建 ALTER TABLE 语句
-                        parts = [
-                            f"ALTER TABLE {table_name}",
-                            f"ADD COLUMN {column_name}",
-                            sqlite_type,
-                        ]
-                        if nullable_str:
-                            parts.append(nullable_str)
-                        if default_str:
-                            parts.append(default_str)
-
-                        sql = " ".join(parts)
-
-                        cursor.execute(sql)
-                        print(
-                            f"✅ 成功添加字段: {table_name}.{column_name} ({sqlite_type})")
-
-                    except Exception as e:
-                        print(f"⚠️ 添加字段失败 {table_name}.{column_name}: {e}")
-                        # 继续处理下一个字段
-
-            except Exception as e:
-                print(f"⚠️ 处理表 {table_name} 时出错: {e}")
-                import traceback
-                traceback.print_exc()
-
-        conn.commit()
-        conn.close()
-        print("🎉 数据库迁移检查完成")
-
-    except Exception as e:
-        print(f"⚠️ 数据库迁移整体失败: {e}")
-        import traceback
-        traceback.print_exc()
-        # 不抛出异常,允许程序继续运行

+ 194 - 22
python/conifg_info.py

@@ -1,41 +1,169 @@
+import functools
+
+
+def return_false_on_error(func):
+    """装饰器:当函数执行出现异常时返回 False"""
+
+    @functools.wraps(func)
+    def wrapper(*args, **kwargs):
+        try:
+            return func(*args, **kwargs)
+        except Exception as e:
+            print(f"Error in {func.__name__}: {e}")
+            return False
+
+    return wrapper
+
+
 class ConfigManager:
-    def __init__(self):
+    def __init__(self, device_control):
         self.CONFIG_METADATA = {}
         self.CONFIG_METADATA_BY_ADDR = {}
+        self.device_control = device_control
 
         # 初始化配置项
         self._init_config_keys()
 
-    def _set_key_float(self, _key, _addr, _tips="", _readonly=False):
+    def _set_key_float(self, _key, _addr, _tips="", _readonly=False, _precision=1):
         self.CONFIG_METADATA[_key] = {
             "addr": _addr,
             "tips": _tips,
             "readonly": _readonly,
             "type": "float",
+            "precision": _precision,
         }
         if _addr in self.CONFIG_METADATA_BY_ADDR:
             print("{} 有重复".format(_addr))
             raise Exception("地址重复")
         self.CONFIG_METADATA_BY_ADDR[_addr] = _key
 
-    def _set_key_int(self, _key, _addr, _tips="", _readonly=False):
+    def _set_key_int(self, _key, _addr, _tips="", _readonly=False, _precision=0):
         self.CONFIG_METADATA[_key] = {
             "addr": _addr,
             "tips": _tips,
             "readonly": _readonly,
             "type": "int",
+            "precision": _precision,
         }
         if _addr in self.CONFIG_METADATA_BY_ADDR:
             print("{} 有重复".format(_addr))
             raise Exception("地址重复")
         self.CONFIG_METADATA_BY_ADDR[_addr] = _key
 
+    def get_dynamic_value(self, data):
+        cmd = [124]
+        addr = data["addr"]
+        cmd.extend([0xff & addr >> 8, 0xff & addr])
+        data = self.device_control.get_basic_info_mcu_without_async(data=cmd)
+        if not data:
+            return False
+        return_data = self.analysis_data(data[1:])
+        if return_data:
+            return str(return_data["value"])
+        return False
+
+        # 地址、正负、类型(整数、浮点)、精度(0.01)、数据(6个字节)、是否只读
+
+    def set_dynamic_value(self, data):
+        try:
+            value = float(data["value"])
+        except:
+            return False
+        addr = data['addr']
+        print("config_data", value, addr)
+        cmd = [125]
+        cmd.extend(self.read_addr_value(value, addr))
+        print("config_data cmd", cmd)
+        data = self.device_control.get_basic_info_mcu_without_async(data=cmd)
+        if not data:
+            return False
+        return_data = self.analysis_data(data[1:])
+        if return_data:
+            print("参数设置成功", return_data)
+            return True
+        return False
+
+    def read_addr_value(self, value, addr):
+        if addr not in self.CONFIG_METADATA_BY_ADDR:
+            return []
+        try:
+            buf = []
+            # 地址、正负、类型(整数、浮点)、精度(0.01)、数据(6个字节)、是否只读
+            __key_name = self.CONFIG_METADATA_BY_ADDR[addr]
+            __meta = self.CONFIG_METADATA[__key_name]
+            buf.extend([0xff & addr >> 8, 0xff & addr])  # 地址
+            _read_only = 1 if __meta["readonly"] else 0  # 是否只读
+            _dir = 1 if value >= 0 else 0  # 是否正负
+            _type = 1 if __meta["type"] == "int" else 0  # 是否整数
+            _precision = __meta["precision"]  # 精度
+            abs_int_value = int(abs(value * 10 ** __meta["precision"]))
+            buf.extend([0xff & abs_int_value >> 40, 0xff & abs_int_value >> 32, 0xff & abs_int_value >> 24,
+                        0xff & abs_int_value >> 16, 0xff & abs_int_value >> 8, 0xff & abs_int_value])
+            buf.extend([_read_only, _dir, _type, _precision])
+            print("127----buf-{}:{}".format(addr, buf))
+            return buf
+        except:
+            return []
+
+    @return_false_on_error
+    def analysis_data(self, _data):
+        _addr = _data[0] << 8 | _data[1]
+        if _addr not in self.CONFIG_METADATA_BY_ADDR:
+            return False
+
+        start = 2
+        _value = _data[start] << 40 | _data[start + 1] << 32 | _data[start + 2] << 24 | _data[start + 3] << 16 | \
+                 _data[start + 4] << 8 | _data[start + 5]
+
+        start = start + 5
+        _read_only = True if _data[start + 1] == 1 else False  # 是否只读
+        _dir = 1 if _data[start + 2] == 1 else -1
+        _type = "int" if _data[start + 3] == 1 else "float"
+        _precision = _data[start + 4]
+
+        if _dir < 0:
+            _value = _value * _dir
+
+        if _type == "float":
+            if _precision > 0:
+                _round_x = _precision
+                _precision = _precision * -1
+                _value = _value * 10 ** _precision
+                _value = round(_value, _round_x)
+        else:
+            _value = int(_value)
+
+        return_data = {"addr": _addr,
+                       "key_name": self.CONFIG_METADATA_BY_ADDR[_addr],
+                       "readonly": _read_only,
+                       "value": _value}
+        return return_data
+
+    def camera_focal_data(self):
+        """
+        定焦器参数获取
+        """
+        dataParams = {}
+        data = [{"addr": 208, "key": "is_camera_has_focal"},
+                {"addr": 209, "key": "camera_focal_distance"},
+                {"addr": 210, "key": "camera_focal_ratio"}]
+        for item in data:
+            value = self.get_dynamic_value(item)
+            match item["key"]:
+                case "is_camera_has_focal":
+                    dataParams[item["key"]] = True if value else False
+                case "camera_focal_distance":
+                    dataParams[item["key"]] = int(value)
+                case "camera_focal_ratio":
+                    dataParams[item["key"]] = float(value)
+        return dataParams
+
     def _init_config_keys(self):
         # test keys
-        self.test_key1 = 0.5
-        self._set_key_float(_key="test_key1", _addr=1)
-        self.test_key2 = 1
-        self._set_key_int(_key="test_key2", _addr=2, _tips="测试-2")
+        # self.test_key1 = 0.5
+        # self._set_key_float(_key="test_key1", _addr=1, _precision=2)
+        # self.test_key2 = 1
+        # self._set_key_int(_key="test_key2", _addr=2, _tips="测试-2")
 
         # 升降机上次命令位置
         self.camera_high_motor_target_value = 0
@@ -234,6 +362,54 @@ class ConfigManager:
             _key="turntable_move_is_exist", _addr=122, _tips="前后移动电机是否存在"
         )
 
+        # 升降电机的到位偏差
+        self.camera_high_motor_arrived_deviation = 100
+        self._set_key_int(
+            _key="camera_high_motor_arrived_deviation", _addr=123, _tips="升降电机的到位偏差"
+        )
+
+        # 相机角度电机的到位偏差
+        self.camera_steering_arrived_deviation = 100
+        self._set_key_int(
+            _key="camera_steering_arrived_deviation", _addr=124, _tips="相机角度电机的到位偏差"
+        )
+
+        # 焦段电机的到位偏差
+        self.camera_zoom_arrived_deviation = 100
+        self._set_key_int(
+            _key="camera_zoom_arrived_deviation", _addr=125, _tips="焦段电机的到位偏差"
+        )
+
+        # 前后移动电机的到位偏差
+        self.move_turntable_arrived_deviation = 100
+        self._set_key_int(
+            _key="move_turntable_arrived_deviation", _addr=126, _tips="前后移动电机的到位偏差"
+        )
+
+        # 转盘角度电机的到位偏差
+        self.turntable_steering_arrived_deviation = 100
+        self._set_key_int(
+            _key="turntable_steering_arrived_deviation", _addr=127, _tips="转盘角度电机的到位偏差"
+        )
+
+        # 转盘转速比例
+        self.turntable_speed_raito = 1.0
+        self._set_key_float(
+            _key="turntable_speed_raito", _addr=128, _tips="转盘转速比例"
+        )
+
+        # 变焦初始化力矩
+        self.c_torque_max_electric = 800
+        self._set_key_int(
+            _key="c_torque_max_electric", _addr=129, _tips="变焦初始化力矩"
+        )
+
+        # 变焦运动方向
+        self.zoom_dir = 0
+        self._set_key_int(
+            _key="zoom_dir", _addr=130, _tips="变焦运动方向"
+        )
+
         # -------------------------------------
         # 转盘偏移角度 单位1度
         self.turntable_steering_deviation = 0
@@ -287,35 +463,31 @@ class ConfigManager:
         self.camera_has_focal = 0
         self._set_key_int(_key="camera_has_focal", _addr=208, _tips="相机调焦功能支持")
 
-        # 相机最大焦段
-        self.camera_max_focal = 55
-        self._set_key_int(_key="camera_max_focal", _addr=209, _tips="相机最大焦段")
-
-        # 相机最小焦段
-        self.camera_min_focal = 15
-        self._set_key_int(_key="camera_min_focal", _addr=210, _tips="相机最小焦段")
+        # 相机焦段最大运动距离
+        self.camera_max_distance = 8000
+        self._set_key_int(_key="camera_max_distance", _addr=209, _tips="相机焦段最大运动距离")
 
         # 相机焦段步进ratio
         self.camera_focal_ratio = 0.0
         self._set_key_float(
-            _key="camera_focal_ratio", _addr=211, _tips="相机焦段步进ratio"
-        )
-
-        # 相机焦段步进自动归零
-        self.camera_zoom_auto_to_zero = 1
-        self._set_key_float(
-            _key="camera_zoom_auto_to_zero", _addr=212, _tips="相机焦段步进自动归零"
+            _key="camera_focal_ratio", _addr=210, _tips="相机焦段步进ratio"
         )
 
         # 相机焦段当前实时位置
         self.camera_zoom_motor_current_value = 0
         self._set_key_float(
             _key="camera_zoom_motor_current_value",
-            _addr=213,
+            _addr=211,
             _tips="相机焦段当前实时位置",
             _readonly=True,
         )
 
+        # 伺服电机每次运动后的偏移数值
+        self.servo_add_v = 0
+        self._set_key_int(
+            _key="servo_add_v", _addr=212, _tips="伺服电机每次运动后的偏移数值"
+        )
+
         # =====================================================
         # 获取剩余内存
         self.get_memory = 0

+ 38 - 40
python/databases.py

@@ -4,7 +4,7 @@ from typing import Dict
 from datetime import datetime
 from typing import Optional
 import json
-from sqlalchemy import and_, desc, asc, delete,inspect
+from sqlalchemy import and_, desc, asc, delete, inspect
 import settings
 from utils.utils_func import check_path
 from sqlalchemy.dialects import sqlite
@@ -18,10 +18,10 @@ engine = create_engine(
     sqlite_url,
     echo=False,
     connect_args={"check_same_thread": False},
-    pool_size=20,        # 增加基础连接池大小
-    max_overflow=30,     # 增加最大溢出连接数
-    pool_timeout=60,     # 保持合理的超时时间
-    pool_recycle=1800,   # 连接回收时间(秒)
+    pool_size=20,  # 增加基础连接池大小
+    max_overflow=30,  # 增加最大溢出连接数
+    pool_timeout=60,  # 保持合理的超时时间
+    pool_recycle=1800,  # 连接回收时间(秒)
     pool_pre_ping=True,  # 检查连接有效性
 )
 
@@ -66,11 +66,11 @@ class CRUD:
         return result.rowcount
 
     def read(
-        self,
-        session: Session,
-        conditions: Optional[Dict] = None,
-        order_by: Optional[str] = None,
-        ascending: bool = True,
+            self,
+            session: Session,
+            conditions: Optional[Dict] = None,
+            order_by: Optional[str] = None,
+            ascending: bool = True,
     ):
         query = select(self.model)
         if conditions:
@@ -91,12 +91,12 @@ class CRUD:
         return data
 
     def read_all(
-        self,
-        session: Session,
-        conditions: Optional[Dict] = None,
-        order_by: Optional[str] = None,
-        ascending: bool = True,
-        join_conditions: Optional[list] = None,  # 新增:支持多个JOIN
+            self,
+            session: Session,
+            conditions: Optional[Dict] = None,
+            order_by: Optional[str] = None,
+            ascending: bool = True,
+            join_conditions: Optional[list] = None,  # 新增:支持多个JOIN
     ):
         query = select(self.model)
         # 处理JOIN逻辑
@@ -139,15 +139,15 @@ class CRUD:
         return db_obj
 
     def deleteConditions(
-        self,
-        session: Session,
-        conditions: Optional[Dict] = None,
-        is_soft_delete: bool = True,
+            self,
+            session: Session,
+            conditions: Optional[Dict] = None,
+            is_soft_delete: bool = True,
     ):
         query = select(self.model)
         if conditions is None:
             return False
-        
+
         # 构建查询条件
         query = query.where(
             and_(
@@ -157,13 +157,13 @@ class CRUD:
                 )
             )
         )
-        
+
         # 获取需要删除的对象
         objects_to_delete = session.exec(query).all()
-        
+
         # 检查模型是否包含 delete_time 字段
         model_columns = {column.name for column in inspect(self.model).columns}
-        if 'delete_time' in model_columns and is_soft_delete ==True:
+        if 'delete_time' in model_columns and is_soft_delete == True:
             # 软删除:更新 delete_time 字段
             for obj in objects_to_delete:
                 setattr(obj, 'delete_time', datetime.now())
@@ -175,7 +175,7 @@ class CRUD:
                 session.delete(obj)
             session.commit()
             print("硬删除完成")
-        
+
         return True
 
     def delete(self, session: Session, obj_id: int):
@@ -201,7 +201,7 @@ class CRUD:
                 )
             )
         )
-        print("SQL 打印==>",str(query))
+        print("SQL 打印==>", str(query))
         result = session.exec(query).first()
         if result:
             for key, value in kwargs.items():
@@ -258,7 +258,7 @@ def batch_insert_device_configs(session: Session, action_tabs: list, data_list:
 
 
 def batch_insert_device_configsNew(
-    session: Session, action_tabs: list, data_list: list
+        session: Session, action_tabs: list, data_list: list
 ):
     """批量插入数据到设备配置表"""
     for idx, tab in enumerate(action_tabs):
@@ -291,7 +291,7 @@ def batch_insert_sys_configs(session: Session, data_list: list):
 
 # 插入照片记录
 async def insert_photo_records(
-    image_deal_mode: int, goods_art_no: str, image_index: int, action_id: int
+        image_deal_mode: int, goods_art_no: str, image_index: int, action_id: int
 ):
     with SqlQuery() as session:  # 使用上下文管理器复用会话
         """批量插入数据到照片记录"""
@@ -314,42 +314,42 @@ async def insert_photo_records(
             "action_id": action_id,
         }
         # 异步插入一条数据
-        settings.syncPhotoRecord(syncData,action_type=1)
+        settings.syncPhotoRecord(syncData, action_type=1)
         return True, record_id
 
 
 def auto_add_missing_columns():
-
     """
     自动检测并添加缺失的数据库字段(最简化版本)
     只为 device_config 表添加缺失的字段
     """
     try:
         import sqlite3
-        
+
         # 连接数据库
         conn = sqlite3.connect(sqlite_file_name)
         cursor = conn.cursor()
-        
+
         # 检查表是否存在
         cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='device_config'")
         if not cursor.fetchone():
             conn.close()
             return
-        
+
         # 获取现有字段
         cursor.execute("PRAGMA table_info(device_config)")
         existing_columns = [row[1] for row in cursor.fetchall()]
-        
+
         # 定义需要添加的字段
         # 格式: (字段名, SQL类型, 默认值)
         # 根据你在 DeviceConfig model 中新增的字段来配置
         new_fields = [
             # 请在这里添加你实际新增的字段
             ("point_name", "VARCHAR", "DEFAULT 'A'"),
-            ("is_move_device", "BOOLEAN", "DEFAULT 1")
+            ("is_move_device", "BOOLEAN", "DEFAULT 1"),
+            ("camera_focal_distance", "INT", "DEFAULT 0")
         ]
-        
+
         # 添加缺失的字段
         for field_name, field_type, default_clause in new_fields:
             if field_name not in existing_columns:
@@ -360,16 +360,14 @@ def auto_add_missing_columns():
                 except Exception as e:
                     print(f"⚠️ 添加字段失败: {e}")
                     pass  # 忽略错误,继续下一个
-        
+
         conn.close()
-        
+
     except Exception as e:
         print(f"⚠️ 添加字段失败: {e}")
         pass  # 静默失败,不影响启动
 
 
-
-
 def SqlQuery():
     return next(__get_session())
 

+ 330 - 103
python/docs/socket命令.md

@@ -1,13 +1,18 @@
 ## 智慧拍 socket 命令说明
+
 ### Socket 连接信息
+
     * 本地: ws://10.56.42.176:7074/ws
     * 打包后: ws://127.0.0.1:7074/ws
+
 #### 统一响应示例说明
+
 * code:为0时代表成功操作
 * msg:为接口执行命令时的提示信息
-* status:-1连接失败  0未连接 1连接中  2链接成功  3端口占用
+* status:-1连接失败 0未连接 1连接中 2链接成功 3端口占用
 * msg_type:mcu为设备控制,blue_tooth为蓝牙控制,blue_tooth_scan 为蓝牙扫码时发送的消息数据
 * data:为返回的数据信息,无实际交互需求时,前端可忽略
+
 ```python
 {
     "code": 0,
@@ -23,39 +28,53 @@
     "msg_type": "mcu"
 }
 ```
+
 ### 设备连接
-*  type 
+
+* type
     * 值为connect_mcu时,为连接mcu设备操作
     * 值为connect_bluetooth时,为连接蓝牙遥控器
 * data:可以忽略为null
+
 #### 请求示例
+
 ```python
-{"data":null,"type":"connect_mcu"}
+{"data": null, "type": "connect_mcu"}
 ```
+
 _<mark>以下操作前必须保证设备已连接!!!<mark>_
+
 ### 初始化mcu设备
+
 _<mark>目前连接设备后,后端会自动进行设备初始化操作,如无特殊需求可忽略<mark>_
-*  type 固定为 init_mcu
+
+* type 固定为 init_mcu
 * data:可以忽略为null
+
 #### 请求示例
+
 ```python
-{"data":null,"type":"init_mcu"}
+{"data": null, "type": "init_mcu"}
 ```
+
 ### 单独控制设备得某一个功能运行命令
+
 _(该命令用于单独自定义配置中某一项的单独调整测试,不进行任何存储操作)_
 
-* type 为control_mcu时,为设备得单独控制 
+* type 为control_mcu时,为设备得单独控制
 * data中的 device_name释义:
-  *  camera_high_motor:相机高度;步长1;最小0;最大400
-  *  camera_steering:相机倾角;步长0.1,最小-40;最大40
-  *  camera_steering:相机倾角
-  * turntable_steering:转盘角度;步长1;最小-720;最大720
-  * turntable_position_motor:转盘位置;步长1,最小0;最大800
-  * overturn_steering:翻转,当device_name为当前类型时,value可以忽略或传递任意值,后端不做任何处理
-  * take_picture:拍照测试;当device_name为当前类型时,value为0或者大于0的数值;提交对焦次数参数
-  * laser_position:激光开关;当device_name为当前类型时,value为0或者1;0为关   1为开
+    * camera_high_motor:相机高度;步长1;最小0;最大400
+    * camera_steering:相机倾角;步长0.1,最小-40;最大40
+    * camera_steering:相机倾角
+    * turntable_steering:转盘角度;步长1;最小-720;最大720
+    * turntable_position_motor:转盘位置;步长1,最小0;最大800
+    * overturn_steering:翻转,当device_name为当前类型时,value可以忽略或传递任意值,后端不做任何处理
+    * take_picture:拍照测试;当device_name为当前类型时,value为0或者大于0的数值;提交对焦次数参数
+    * laser_position:激光开关;当device_name为当前类型时,value为0或者1;0为关 1为开
 * value为设备得控制值,参考上述的最大最小值进行处理
+
 #### 请求示例
+
 ```python
 {
     "data": {
@@ -66,15 +85,17 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 }
 ```
 
-
 ### 执行拍摄任务
+
 * type 为run_mcu时,为设备拍照动作连贯执行
 * data中的 action参数释义:
     * 执行左脚程序 : 为用户配置【执行左脚程序】,其状态必须至少有一条为true,否则不会运行并且报错无可用参数
     * 执行右脚程序 : 为用户配置【执行右脚程序】,其状态必须至少有一条为true,否则不会运行并且报错无可用参数
 * data中的 goods_art_no参数释义:
     * 商品货号,必须存在且为字符串形式传递
+
 #### 请求示例
+
 ```python
 {
     "data": {
@@ -84,7 +105,9 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "type": "run_mcu"
 }
 ```
+
 ### 执行拍摄任务命令发送完成后-服务端响应消息
+
 * msg_type 为image_process时,代表上述命令已发送给设备执行任务
 * data参数释义:
     * image_counts : 执行拍照任务的数量,可理解为照片张数
@@ -92,10 +115,12 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     * action_names : 执行的动作名称
     * current_time : 当前时间
 * msg 消息提示
+
 #### 响应示例
+
 ```python
 {
-    "msg":"MCU 命令已发送完成",
+    "msg": "MCU 命令已发送完成",
     "data": {
         "image_counts": 4,
         "goods_art_no": "1234556",
@@ -108,10 +133,12 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
         ],
         "current_time": "年月日时分秒"
     },
-    "msg_type":"image_process"
+    "msg_type": "image_process"
 }
 ```
+
 ### 单张照片拍照完成后-服务端响应消息
+
 * msg_type:
     * 为photo_take时,代表拍照已完成
     * 为photo_take_finish时,代表所有拍照任务已完成
@@ -120,65 +147,80 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     * goods_art_no : 货号
     * current_time : 当前时间
 * msg 消息提示
+
 #### 响应示例-单个任务拍照成功(前端需要刷新列表)
+
 ```python
 {
     "code": 0,
     "msg": "拍鞋底 执行完成~",
     "status": 2,
     "data": null,
-    "msg_type":"photo_take"
+    "msg_type": "photo_take"
 }
 ```
+
 #### 响应示例-单个任务拍照失败
+
 ```python
 {
     "code": 1,
     "msg": "拍鞋底 执行失败~",
     "status": 2,
     "data": null,
-    "msg_type":"photo_take"
+    "msg_type": "photo_take"
 }
 ```
+
 ### 响应示例-整体任务拍照完成
+
 ```python
 {
     "code": 1,
     "msg": "执行左脚程序 执行成功~",
     "status": 2,
     "data": null,
-    "msg_type":"photo_take_finish"
+    "msg_type": "photo_take_finish"
 }
 ```
+
 ### 接收遥控器扫码货号动作处理
+
 #### 消息示例
+
 * data中的子data参数释义:
     * action:【执行左脚程序】或【执行右脚程序】
     * goods_art_no :扫描到的货号,当货号为空时代表用户未扫码,直接按遥控器的左右操作,需要前端判断用户是否在前端页面填写了货号,或者提醒用户填写货号
 * msg_type:当该字段为blue_tooth_scan时,前端可直接解析得到data数据包,将数据直接发送给socket服务端即可【执行拍摄任务】
+
 ```python
 {
     "code": 0,
     "msg": "准备执行[左脚程序]",
     "status": 2,
     "data": {
-            "data": {
-                    "action": "执行左脚程序",
-                    "goods_art_no": "货号信息",
-                    },
-                    "type": "run_mcu",
+        "data": {
+            "action": "执行左脚程序",
+            "goods_art_no": "货号信息",
+        },
+        "type": "run_mcu",
     },
     "msg_type": "blue_tooth_scan"
 }
 ```
+
 ### 接收遥控器点击拍照事件处理
+
 <mark>遥控器得拍照命令需要由后端转发到前端,再由前端将消息转发给后端对应得拍照命令<mark>
 当蓝牙遥控器点击拍照后,后端消息响应如下:
+
 * data中参数释义:
     * type:handler_take_picture 为后端单拍任务得类型
     * data :忽略
 * msg_type:当该字段为handler_take_picture时,前端可直接解析得到data数据包,将数据直接发送给socket服务端即可【执行单任务】
+
 #### 消息示例
+
 ```python
 {
     "code": 0,
@@ -191,23 +233,31 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "handler_take_picture"
 }
 ```
+
 <mark>注:在多次按下拍照按钮时,前端需要在命令发送给后端时且拍照未完成之前处理拦截操作,否则会出现拍照任务在不断执行<mark>
 
 ### 单拍任务命令
+
 #### 发送-消息示例
+
 ```python
 {
-        "type": "handler_take_picture",
-        "data": null
+    "type": "handler_take_picture",
+    "data": null
 }
 ```
+
 #### 响应-消息示例
-<mark>注意:当前响应可能会出现失败得情况,如:用户清空了所有货号数据;用户未打开拍照软件等其他错误消息响应;前端需要判断code是否为0;去释放拦截<mark>
+
+<mark>
+注意:当前响应可能会出现失败得情况,如:用户清空了所有货号数据;用户未打开拍照软件等其他错误消息响应;前端需要判断code是否为0;去释放拦截<mark>
+
 * data中参数释义:
     * msg: 消息文本内容
     * data :
         * goods_art_no:货号,便于前端查询详情数据
 * msg_type:当该字段为photo_take时,代表照片已拍摄完成
+
 ```python
 {
     "code": 0,
@@ -219,6 +269,7 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 ```
 
 #### 修改配置-独立操作MCU
+
 * data中参数释义:
     * msg: 消息文本内容
     * data :
@@ -237,7 +288,9 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
         * action_index:固定1
         * pre_delay:1;拍照后延迟;这里因为后端不控制拍照,所以无效
 * type:当该字段为run_mcu_single时,代表独立执行MCU设备
+
 ##### 请求示例
+
 ```python
 {
     "data": {
@@ -259,8 +312,11 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "type": "run_mcu_single"
 }
 ```
+
 ##### 响应示例
+
 ###### 当 msg_type 为run_mcu_single时,代表独立执行MCU设备完成,前端将此命令作为完成依据
+
 ```python
 {
     "code": 0,
@@ -270,11 +326,16 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "run_mcu_single_finish"
 }
 ```
+
 #### 设备调平-获取
+
 <mark>以下操作需要连接设备且初始化<mark>
+
 * data:空对象或忽略,任何值无意义
 * type:当该字段为get_deviation时,代表获取调平设备信息
+
 ##### 请求示例
+
 ```python
 {
     "data": {
@@ -282,7 +343,9 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "type": "get_deviation"
 }
 ```
+
 ##### 响应示例
+
 * data:
     * camera_high_motor_deviation:相机高度偏移 mm
     * camera_steering_deviation: 相机角度偏移 度
@@ -293,6 +356,7 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     * overturn_steering_down_speed: 翻转舵机-下降速度
     * turntable_front_end_deviation: 转盘前后电机偏移度
 * type:当该字段为get_deviation_data时,代表成功获取到调平设备信息
+
 ```python
 {
     "code": 0,
@@ -306,49 +370,56 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
         "overturn_steering_high": 150.0,
         "overturn_steering_up_speed": 2,
         "overturn_steering_down_speed": 4,
-        "turntable_front_end_deviation":300
+        "turntable_front_end_deviation": 300
     },
     "msg_type": "get_deviation_data"
 }
 ```
 
-
 #### 设备调平-设置
+
 <mark>以下操作需要连接设备且初始化<mark>
+
 * data:
     * value: 偏移量值,最好传递浮点型
     * action_name:
-        * "相机电机",  # min 0 max 400,步长1
-        * "相机舵机",  # min -40 max 40,步长0.1
-        * "转盘舵机",  # min -720 max 720,步长1
-        * "转盘前后电机",  # min 0 max 950,步长1
-        * "翻板舵机中位",  # min 0 max 180,步长0.5
-        * "翻板舵机高位",  # min 0 max 180,步长0.5
-        * "翻板舵机上升速度",  # min 1 max 10,步长1
-        * "翻板舵机下降速度",  # min 1 max 10,步长1
-    * type: 
+        * "相机电机", # min 0 max 400,步长1
+        * "相机舵机", # min -40 max 40,步长0.1
+        * "转盘舵机", # min -720 max 720,步长1
+        * "转盘前后电机", # min 0 max 950,步长1
+        * "翻板舵机中位", # min 0 max 180,步长0.5
+        * "翻板舵机高位", # min 0 max 180,步长0.5
+        * "翻板舵机上升速度", # min 1 max 10,步长1
+        * "翻板舵机下降速度", # min 1 max 10,步长1
+    * type:
         * "move" 为移动
         * "set" 为设置
 * type:
     * 当该字段为set_deviation时,代表设置调平设备信息
     * 当该字段为move_deviation时,代表移动调平设备偏移
+
 ##### 请求示例
+
 ```python
 {
     "data": {
         "value": 10,
-        "action_name":"相机电机",
-        "type":"move"
+        "action_name": "相机电机",
+        "type": "move"
     },
     "type": "set_deviation"
 }
 ```
+
 ##### 响应示例
+
 * data:忽略
 * type:
     * 当该字段为set_deviation时,代表响应调平设备设置
     * 当该字段为move_deviation时,代表响应调平设备移动
+
 ###### 成功状态
+
 ```python
 {
     "code": 0,
@@ -358,7 +429,9 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "set_deviation"
 }
 ```
+
 ###### 失败状态
+
 ```python
 {
     "code": 1,
@@ -369,26 +442,32 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 }
 ```
 
-
-
 #### 获取MCU其他设置信息
+
 <mark>以下操作需要连接设备且初始化<mark>
-* data:为null或其他值  后端忽略
+
+* data:为null或其他值 后端忽略
 * type:
     * 当该字段为get_mcu_other_info时,代表获取MCU其他设置信息
+
 ##### 请求示例
+
 ```python
 {
-    "data":null,
+    "data": null,
     "type": "get_mcu_other_info"
 }
 ```
+
 ##### 响应示例
+
 * data:忽略
 * type:
     * 当该字段为get_other_mcu_info,代表成功获取MCU其他设置信息
-<mark>以下数据包作为写入设备时对应得字段名称<mark>
+      <mark>以下数据包作为写入设备时对应得字段名称<mark>
+
 ###### 成功状态
+
 ```python
 {
     "code": 0,
@@ -416,17 +495,19 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 }
 ```
 
-
-
 #### 设置MCU其他设置信息
+
 <mark>以下操作需要连接设备且初始化<mark>
+
 * data:设置得数据包
 * type:
     * 当该字段为set_mcu_other_info时,代表设置MCU其他设置信息
+
 ##### 请求示例
+
 ```python
 {
-    "data":{
+    "data": {
         "is_auto_send_base_info": 0,
         "is_move_retry": 0,
         "is_data_response": 0,
@@ -447,12 +528,16 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "type": "set_mcu_other_info"
 }
 ```
+
 ##### 响应示例
+
 * data:忽略
 * type:
     * 当该字段为get_other_mcu_info,代表成功获取MCU其他设置信息
-<mark>以下数据包作为写入设备时对应得字段名称<mark>
+      <mark>以下数据包作为写入设备时对应得字段名称<mark>
+
 ###### 成功状态
+
 ```python
 {
     "code": 0,
@@ -463,14 +548,17 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 }
 ```
 
-
 #### 通过命令行发送设备指令
+
 <mark>以下操作需要连接设备且初始化<mark>
+
 * data:
     * command:命令行指令,字符串“0x01 0x42 0x6C 0x6b”为前端默认展示得字符串值,写死即可
 * type:
     * 当该字段为send_command时,代表设置通过命令行发送指令
+
 ##### 请求示例
+
 ```python
 {
     "data": {
@@ -479,7 +567,9 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "type": "send_command"
 }
 ```
+
 ##### 响应示例
+
 * data:
     * command:收到的指令
     * type:
@@ -487,8 +577,10 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
         * 为output时代表收到指令回复,前端需要填充到回复得文本展示框中;
 * type:
     * 当该字段为send_command,代表成功获取到指令回复
-<mark>以下数据包作为写入设备时对应得字段名称<mark>
+      <mark>以下数据包作为写入设备时对应得字段名称<mark>
+
 ###### 发送后指令转换
+
 ```python
 {
     "code": 0,
@@ -501,7 +593,9 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "send_command"
 }
 ```
+
 ###### 接收到指令回复
+
 ```python
 {
     "code": 0,
@@ -514,33 +608,45 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "send_command"
 }
 ```
+
 #### 执行重拍操作
+
 <mark>以下操作需要连接设备且初始化<mark>
+
 * data:
     * record_id:原记录id
 * type:
     * 当该字段为re_take_picture时,代表进行重拍操作
-<mark>注:后续得拍照动作参考消息回复为的type均为:re_take_picture,照片拍摄完成的消息依旧为photo_take<mark>
+      <mark>注:后续得拍照动作参考消息回复为的type均为:re_take_picture,照片拍摄完成的消息依旧为photo_take<mark>
+
 ##### 请求示例
+
 ```python
 {
-    "data":{"record_id":1},
+    "data": {"record_id": 1},
     "type": "re_take_picture"
 }
 ```
+
 #### 停止拍摄
+
 <mark>以下操作需要连接设备且初始化<mark>
+
 * data:null
 * type:
     * 当该字段为stop_action时,代表停止拍摄
+
 ##### 请求示例
+
 ```python
 {
-    "data":null,
+    "data": null,
     "type": "stop_action"
 }
 ```
+
 <mark>当通过物理遥控器按键停止拍摄时,会收到以下消息,前端需要把上方的【停止拍摄】消息转发给后端即可<mark>
+
 ```
 {
     "code": 0,
@@ -554,23 +660,29 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 }
 ```
 
-
-
 #### 使用smart shooter进行相机控制
+
 <mark>以下操作需要连接连接相机并拉起smartshooter5软件<mark>
+
 ##### 获取相机是否连接
+
 * data:{}
 * type:
     * 当该字段为smart_shooter_getinfo时,代表获取相机连接信息,是否连接成功
+
 ##### 请求示例
+
 ```python
 {
-    "data":{},
+    "data": {},
     "type": "smart_shooter_getinfo"
 }
 ```
+
 ##### 响应示例-连接成功
+
 ###### 发送后指令转换
+
 ```python
 {
     "code": 0,
@@ -580,8 +692,11 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "smart_shooter_getinfo"
 }
 ```
+
 ##### 响应示例-连接失败
+
 ###### 发送后指令转换
+
 ```python
 {
     "code": 0,
@@ -593,18 +708,23 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 ```
 
 ##### 启动/关闭预览
+
 * data:
     * value:为true时代表打开预览,为false时代表关闭预览
 * type:
     * 当该字段为smart_shooter_enable_preview时,代表操作预览启动关闭操作
+
 ##### 请求示例
+
 ```python
 {
-    "data":{"value":false},
+    "data": {"value": false},
     "type": "smart_shooter_enable_preview"
 }
 ```
+
 ##### 响应示例-启动预览
+
 ```python
 {
     "code": 0,
@@ -614,7 +734,9 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": ""
 }
 ```
+
 ##### 响应示例-关闭预览
+
 ```python
 {
     "code": 0,
@@ -625,19 +747,23 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 }
 ```
 
-
 ##### 拍照
+
 * data:{}
 * type:
     * 当该字段为smart_shooter_photo_take时,代表操作拍照动作
+
 ##### 请求示例
+
 ```python
 {
-    "data":{},
+    "data": {},
     "type": "smart_shooter_photo_take"
 }
 ```
+
 ##### 响应示例-拍照成功
+
 ```python
 {
     "code": 0,
@@ -647,7 +773,9 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": ""
 }
 ```
+
 ##### 响应示例-照片获取成功
+
 ```python
 {
     "code": 1,
@@ -659,24 +787,30 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 }
 ```
 
-
-
-
 ##### 获取相机属性-ISO等相关信息
+
 * data:{}
 * type:
     * 当该字段为smart_shooter_get_camera_property时,代表获取相机属性-ISO等相关信息
+
 ##### 请求示例
+
 ```python
 {
-    "data":{},
+    "data": {},
     "type": "smart_shooter_get_camera_property"
 }
 ```
+
 ##### 响应示例-获取成功
+
 <mark>如果相机未连接可能data参数为空,需要注意判断</mark>
+
 * data释意:
-    * 该值是通过smartshooter直接获取的,是当前相机的所有属性值,前端在使用对应值得时候,需要遍历数组,判断对应:CameraPropertyType,如CameraPropertyType=="ISO" 后获取当前对象,其中“CameraPropertyValue”为相机当前属性得当前值,“CameraPropertyRange”为当前属性值得范围
+    *
+  该值是通过smartshooter直接获取的,是当前相机的所有属性值,前端在使用对应值得时候,需要遍历数组,判断对应:CameraPropertyType,如CameraPropertyType=="
+  ISO" 后获取当前对象,其中“CameraPropertyValue”为相机当前属性得当前值,“CameraPropertyRange”为当前属性值得范围
+
 ```python
 {
     "code": 0,
@@ -909,13 +1043,14 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 ```
 
 ##### 获取详情图处理接口中的<mark>[详情页模板]</mark>执行进度信息
+
 * data:{}
     * goods_no:款号
     * temp_name:模板名称
     * status:状态
     * goods_art_nos:货号-数组
 * msg_type:详情图处理固定为[detail_progress]
-<mark>以下为消息发送得示例</mark>
+  <mark>以下为消息发送得示例</mark>
 
 ```
 {
@@ -933,6 +1068,7 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "detail_progress"
 }
 ```
+
 ```
 {
     "code": 0,
@@ -946,6 +1082,7 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "detail_progress"
 }
 ```
+
 ```
 {
     "code": 0,
@@ -959,6 +1096,7 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "detail_progress"
 }
 ```
+
 ```
 {
     "code": 0,
@@ -973,13 +1111,13 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
 }
 ```
 
-
 ##### 获取详情图处理接口中的<mark>[抠图]</mark>执行进度信息
+
 * data:{}
     * status:状态
     * goods_art_nos:货号-数组
 * msg_type:详情图处理固定为[segment_progress]
-<mark>以下为消息发送得示例</mark>
+  <mark>以下为消息发送得示例</mark>
 
 ```
 {
@@ -996,6 +1134,7 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "segment_progress"
 }
 ```
+
 ```
 {
     "code": 0,
@@ -1011,12 +1150,14 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "detail_progress"
 }
 ```
+
 ##### 获取详情图处理接口中的<mark>[上传商品到第三方]</mark>执行进度信息
+
 * data:{}
     * status:状态
     * online_stores:上传商品到第三方得渠道名称,数组形式
 * msg_type:详情图处理固定为[upload_goods_progress]
-<mark>以下为消息发送得示例</mark>
+  <mark>以下为消息发送得示例</mark>
 
 ```
 {
@@ -1032,6 +1173,7 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "upload_goods_progress"
 }
 ```
+
 ```
 {
     "code": 0,
@@ -1046,83 +1188,99 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "upload_goods_progress"
 }
 ```
+
 ##### 发送独立抠图命令任务
+
 * data:{}
     * token:用户token信息
     * goods_art_no:货号-数组
     * uuid:前端传递uuid
 * msg_type:固定为[segment_progress]
+
 ###### 请求示例
+
 ```json
 {
-    "type": "segment_progress",
-    "data": {
-        "token": "",
-        "uuid": "",
-        "goods_art_no": []
-    }
+  "type": "segment_progress",
+  "data": {
+    "token": "",
+    "uuid": "",
+    "goods_art_no": []
+  }
 }
 ```
+
 ###### 响应示例-成功
+
 ```json
 {
-    "code": 0,
-    "msg": "开始处理抠图",
-    "status": 2,
-    "data": {
-        "status": "进行中",
-        "goods_art_nos": [
-            "AQN1411322",
-            "B411351"
-        ]
-    },
-    "msg_type": "segment_progress"
+  "code": 0,
+  "msg": "开始处理抠图",
+  "status": 2,
+  "data": {
+    "status": "进行中",
+    "goods_art_nos": [
+      "AQN1411322",
+      "B411351"
+    ]
+  },
+  "msg_type": "segment_progress"
 }
 ```
+
 ```json
 {
-    "code": 0,
-    "msg": "抠图结束",
-    "status": 2,
-    "data": {
-        "status": "已完成",
-        "goods_art_nos": [
-            "AQN1411322",
-            "B411351"
-        ]
-    },
-    "msg_type": "segment_progress"
+  "code": 0,
+  "msg": "抠图结束",
+  "status": 2,
+  "data": {
+    "status": "已完成",
+    "goods_art_nos": [
+      "AQN1411322",
+      "B411351"
+    ]
+  },
+  "msg_type": "segment_progress"
 }
 ```
+
 ###### 响应示例-失败-异常
+
 * code:1
     * 异常情况下code会固定为1
 * msg:异常输出得错误内容;前端需要判断code是否等于1,如果等于1则代表异常,输出msg得内容即可,因为是异步执行,也可以忽略异常,不做处理
 * msg_type:固定为[segment_progress]
+
 ```json
 {
-    "code": 1,
-    "msg": "异常输出得错误内容",
-    "status": 2,
-    "data": null,
-    "msg_type": "segment_progress"
+  "code": 1,
+  "msg": "异常输出得错误内容",
+  "status": 2,
+  "data": null,
+  "msg_type": "segment_progress"
 }
 ```
+
 ##### 发送获取设备状态命令
+
 * data:null
 * msg_type:固定为[get_mcu_info]
+
 ```
 {
     "type": "get_mcu_info",
     "data": null
 }
 ```
+
 ###### 响应示例-成功
+
 * state_camera_motor:相机高度状态
 * state_camera_steering:相机角度状态
 * state_turntable_steering:转盘状态
 * state_move_turntable_steering:转盘前后移动状态
 * state_overturn_steering:翻板状态
+
 ```
 {
     "code": 0,
@@ -1143,4 +1301,73 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "get_mcu_info"
 }
 ```
+
+###### 增加参数设置页面(给设备设置其他动态参数)-批量获取动态参数
+
+* data:{}
+* msg_type:固定为[get_dynamic_config]
+  <mark>以下为消息发送得示例</mark>
+
+```
+{
+    "data": {
+    },
+    "msg_type": "get_dynamic_config"
+}
+```
+
+* 响应示例-成功
+  注意,前端需要遍历data中的数据进行渲染设置栏,并且需要注意readonly属性,如果为true则代表该参数不可修改
+
+```
+{
+    "code": 0,
+    "msg": "成功",
+    "data": {
+        "camera_high_motor_target_value": {
+            "addr": 3,
+            "tips": "升降机当前位置",
+            "readonly": true,
+            "type": "float",
+            "precision": 1,
+            "value": "0.0"
+        }
+        }}
+```
+
+###### 增加参数设置页面(给设备设置其他动态参数)-设置对应项动态参数
+
+* data:{}
+    * "addr": 113,
+    * "readonly": false,
+    * "value": "38.6"
+* msg_type:固定为[set_dynamic_config]
+  <mark>以下为消息发送得示例</mark>
+
+```
+{
+    "data": {
+    "addr": 113,
+            "tips": "转盘转速比",
+            "readonly": false,
+            "type": "float",
+            "precision": 1,
+            "value": "38.6"
+    },
+    "msg_type": "set_dynamic_config"
+}
+```
+
+* 响应示例-成功
+
+```
+{
+    "code": 0,
+    "msg": "成功",
+    "data": {
+        "status": true
+        }
+ }
+```
+
 ##### 未完待续.....

+ 20 - 15
python/mcu/BaseClass.py

@@ -1,4 +1,4 @@
-import asyncio,time
+import asyncio, time
 from sockets import ConnectionManager
 from utils.common import message_queue
 from mcu.capture.smart_shooter_class import SmartShooter
@@ -7,7 +7,7 @@ from mcu.capture.smart_shooter_class import SmartShooter
 class BaseClass:
 
     def __init__(
-        self, websocket_manager: ConnectionManager, smart_shooter: SmartShooter = None
+            self, websocket_manager: ConnectionManager, smart_shooter: SmartShooter = None
     ):
         self.websocket_manager = websocket_manager
         self.smart_shooter = smart_shooter
@@ -16,34 +16,39 @@ class BaseClass:
         # -1连接失败  0未连接 1连接中  2连接成功  3端口占用
         # self.device_status = 2
 
-    def sendSocketMessage(self, code=0, msg="", data=None, device_status=2):
+    def sendSocketMessage(self, code=0, msg="", data=None, device_status=2, msg_type=None):
         t_start = time.time()
+        if msg_type:
+            message_type = msg_type
+        else:
+            message_type = self.msg_type
         payload = {
             "code": code,
             "msg": msg,
             "status": device_status,
             "data": data,
-            "msg_type": self.msg_type,
+            "msg_type": message_type,
         }
-        
+
         print(f"[T1: {t_start:.4f}] sendSocketMessage 调用, msg={msg}")
 
         loop = asyncio.get_event_loop()
         if self.websocket == None:
-            print(f"[T1: {time.time()-t_start:.4f}s] 走队列路径")
+            print(f"[T1: {time.time() - t_start:.4f}s] 走队列路径")
             loop.create_task(message_queue.put(payload))
         else:
-            print(f"[T1: {time.time()-t_start:.4f}s] 走直接发送路径")
-            
+            print(f"[T1: {time.time() - t_start:.4f}s] 走直接发送路径")
+
             async def _do_send():
                 t2 = time.time()
-                print(f"[T2: {t2-t_start:.4f}s] 任务开始执行, 准备调用 send_personal_message")
+                print(f"[T2: {t2 - t_start:.4f}s] 任务开始执行, 准备调用 send_personal_message")
                 await self.websocket_manager.send_personal_message(payload, self.websocket)
                 t3 = time.time()
-                print(f"[T3: {t3-t_start:.4f}s] send_personal_message 完成, 总耗时: {t3-t2:.4f}s")
-                
+                print(f"[T3: {t3 - t_start:.4f}s] send_personal_message 完成, 总耗时: {t3 - t2:.4f}s")
+
             loop.create_task(_do_send())
         print("\033[1;32;40m 发送消息===>sendSocketMessage \033[0m", data)
+
     async def asyncSendSocketMessage(self, code=0, msg="", data=None, device_status=2):
         data = {
             "code": code,
@@ -88,9 +93,9 @@ class BaseClass:
                     #     return None
                     # time.sleep(0.01)
                     continue
-                _data = self.receive_data[3 : data_len + 4]
+                _data = self.receive_data[3: data_len + 4]
                 # 更新缓存区
-                self.receive_data = self.receive_data[data_len + 4 :]
+                self.receive_data = self.receive_data[data_len + 4:]
                 # 校验数据
                 if 0xFF & ~sum(_data[:-1]) == _data[-1]:
                     # print("receive_data:", self.change_hex_to_int(self.receive_data[:-1]))
@@ -108,8 +113,8 @@ class BaseClass:
                             self.receive_data = b""
                     else:
                         if (
-                            self.receive_data[0] == 0x55
-                            and self.receive_data[1] == 0x55
+                                self.receive_data[0] == 0x55
+                                and self.receive_data[1] == 0x55
                         ):
                             break
                         else:

+ 169 - 164
python/mcu/DeviceControl.py

@@ -23,6 +23,7 @@ import logging
 from mcu.capture.smart_shooter_class import SmartShooter
 import logging
 from conifg_info import ConfigManager
+
 logger = logging.getLogger(__name__)
 
 
@@ -31,29 +32,38 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
     lock = threading.Lock()
 
     def __init__(
-        self, websocket_manager: ConnectionManager, smart_shooter: SmartShooter = None
+            self, websocket_manager: ConnectionManager, smart_shooter: SmartShooter = None
     ):
         super().__init__(
             websocket_manager=websocket_manager, smart_shooter=smart_shooter
         )
+        self.camera_high_motor_deviation_offset = 0
         self.camera_height = 400
         self.config_manager = None
         self.msg_type = "mcu"
         self.command = {
             "to_device_move": 1,  # 设备运动
-            "to_init_device": 2,  # 初始化设备
+            'to_init_device': 2,  # 初始化设备
             "to_deal_other_device": 3,  # 处理其他设备
-            "get_all_info": 29,  # 获取所有信息
-            "set_deviation": 40,  # 设置偏移量
-            "get_deviation": 41,  # 读取偏移量
+            'get_all_info': 29,  # 获取所有信息
+            'set_deviation': 40,  # 设置偏移量
+            'get_deviation': 41,  # 读取偏移量
             "signal_forwarding": 91,  # 信号转发处理
             "signal_forwarding_return": 92,  # 信号转发返回
             "get_other_info": 44,  # 获取其他信息
             "open_rgb_led": 43,  ## RGB灯的处理与通讯
             "set_other_info": 45,  # 设置其他信息
             "query_remote_control_battery": 47,  # 查询遥控器电量
-            "set_turntable_mode": 48,  # 设置转盘通讯方式 1、串口、2、无线、3 混合
+            # "set_turntable_mode": 48,  # 设置转盘通讯方式 1、串口、2、无线、3 混合
+            "get_mcu_device_id": 49,  # 查询MCU的设备号
+            "mcu_power_off": 53,  # 设备关机
             "stop_mcu": 93,  # 停止运行mcu
+            "get_steering_temperature": 108,  # 查询舵机的当前的温度
+            # "steering_forwarding": 113,  # 舵机信号转发处理
+            # "steering_forwarding_callback": 114,  # 舵机信号转发返回
+            "get_motor_value": 119,  # 获取当前电机值;逻辑值
+            "set_zero_debug": 120,  # 简单的设备归零,用于调试
+            "set_stepper_disable": 121,  # 释放,或恢复使能
         }
         self.serial_ins = None
         self.mcu_deviation_set = McuDeviationSet(self)
@@ -100,19 +110,21 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             "turntable_position_motor": 7,
             "mp3_player": 8,
             "mcu": 99,
+            "camera_zoom_motor": 10,
         }
         self.last_move_time = time.time()
         self.device_name_dict_mapping = {
-            0:"相机角度",
-            1:"相机高度",
-            2:"转盘角度",
-            3:"翻板角度",
-            4:"激光灯位置",
-            5:"蜂鸣器",
-            6:"split",
-            7:"转盘位置",
-            8:"播放音频",
-            99:"mcu命令",
+            0: "相机角度",
+            1: "相机高度",
+            2: "转盘角度",
+            3: "翻板角度",
+            4: "激光灯位置",
+            5: "蜂鸣器",
+            6: "split",
+            7: "转盘位置",
+            8: "播放音频",
+            99: "mcu命令",
+            10: "相机焦段",
         }
         # 最近的mcu基础信息,用于获取数据状态检查
         self.last_mcu_info_data = {
@@ -127,7 +139,6 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             "data": {},
         }
 
-
         # self.window = window
         self.last_push_time = defaultdict(float)
         self.is_running = False
@@ -153,6 +164,39 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             126: self.get_all_registers_list_by_usb,  # 获取所有寄存器
             150: self.dynamic_parameter_issuance,  # 动态参数下发
         }
+
+    def set_dynamic_config(self, config):
+        """
+        设置动态参数配置
+        """
+        print("set_dynamic_config", config)
+        if not self.init_state:
+            self.sendSocketMessage(code=1, msg="mcu设备未初始化", device_status=4)
+            return False
+        return self.config_manager.set_dynamic_value(config)
+
+    def get_dynamic_config(self):
+        """
+        获取动态参数配置
+        """
+        if not self.init_state:
+            self.sendSocketMessage(code=1, msg="mcu设备未初始化", device_status=4)
+            return False
+        # 先收集需要删除的键
+        keys_to_delete = []
+        for item in list(self.config_manager.CONFIG_METADATA.keys()):
+            item_value = self.config_manager.get_dynamic_value(self.config_manager.CONFIG_METADATA[item])
+            if not item_value:
+                keys_to_delete.append(item)
+                continue
+            self.config_manager.CONFIG_METADATA[item]['value'] = item_value
+
+        # 遍历结束后再删除
+        for key in keys_to_delete:
+            del self.config_manager.CONFIG_METADATA[key]
+
+        return self.config_manager.CONFIG_METADATA
+
     def get_device_info(self):
         if not self.init_state:
             self.sendSocketMessage(code=1, msg="mcu设备未初始化", device_status=4)
@@ -165,27 +209,32 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
         if not data:
             return False
         return_data = self.analysis_data(data[1:])
+        camera_focal_data = self.config_manager.camera_focal_data()
+        # print("return_data", return_data)
+        # print("camera_high_motor_deviation_offset", self.camera_high_motor_deviation_offset)
         if return_data:
-            camera_height = return_data.get('value',35)
-            self.camera_height = camera_height
+            camera_height = return_data.get('value', 35)
+            self.camera_height = camera_height - self.camera_high_motor_deviation_offset
         else:
-            self.camera_height = 400
+            self.camera_height = 400 - self.camera_high_motor_deviation_offset
         self.msg_type = 'get_device_info'
         self.sendSocketMessage(
             code=0,
-            msg="设置mcu其他配置信息完成",
+            msg="获取其他设备信息",
             device_status=2,
-            data={"camera_height":self.camera_height}
+            data={"camera_height": self.camera_height, "camera_focal_data": camera_focal_data}
         )
         self.msg_type = 'mcu'
         return self.camera_height
+
     # 获取异步数据
     def analysis_data(self, _data):
         _addr = _data[0] << 8 | _data[1]
         if _addr not in self.config_manager.CONFIG_METADATA_BY_ADDR:
             return False
         start = 2
-        _value = _data[start] << 40 | _data[start + 1] << 32 | _data[start + 2] << 24 | _data[start + 3] << 16 | _data[start + 4] << 8 | _data[start + 5]
+        _value = _data[start] << 40 | _data[start + 1] << 32 | _data[start + 2] << 24 | _data[start + 3] << 16 | _data[
+            start + 4] << 8 | _data[start + 5]
 
         start = start + 5
         _read_only = True if _data[start + 1] == 1 else False  # 是否只读
@@ -211,7 +260,8 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                        "value": _value}
         return return_data
         # 跳过异步,直接查询某个数据信息
-    def get_basic_info_mcu_without_async(self, data,fiddler_cmd=0):
+
+    def get_basic_info_mcu_without_async(self, data, fiddler_cmd=0):
         """
         fiddler_cmd :只接收指定的命令内容
         """
@@ -223,11 +273,12 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             r_data = self.get_basic_info_mcu()
             print("264----------r_data:", r_data)
         except BaseException as e:
-            print("302---e",e)
+            print("302---e", e)
             r_data = []
         #
         # self.async_lock.release()
         return r_data
+
     def dynamic_parameter_issuance(self, receive_data):
         print("dynamic_parameter_issuance   receive_data", receive_data)
         func_code, status_code, out_par_data_list = dynamic_parameter_issuance_get(
@@ -322,7 +373,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             logger.info("已经初始化过,请勿重复初始化")
             self.sendSocketMessage(msg="设备初始化完成", device_status=2)
             return False
-        self.config_manager = ConfigManager()
+        self.config_manager = ConfigManager(self)
         self.serial_ins.clearn_flush()
         self.to_init_device_origin_point(device_name="mcu", is_force=is_force)
         print("MCU 开始循环~")
@@ -358,38 +409,6 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
         self.line_control.connect_state = True
         self.line_control.serial_ins = serial_handle
         await self.line_control.run()
-        # if self.init_state == True:
-        #     print("已经初始化过,请勿重复初始化")
-        #     self.sendSocketMessage(msg="设备初始化完成", device_status=2)
-        #     return False
-        # self.serial_ins.clearn_flush()
-        # self.to_init_device_origin_point(device_name="mcu", is_force=is_force)
-        # print("MCU 开始循环~")
-        # while 1:
-        #     await asyncio.sleep(0.01)
-        #     if not self.serial_ins or not self.connect_state:
-        #         break
-        #     try:
-        #         # print("mcu   send_cmd")
-        #         self.send_cmd()
-        #         # time.sleep(0.01)
-        #         self.get_basic_info_mcu()
-        #         # self.close_other_window()
-        #     except BaseException as e:
-        #         print("121231298908", e)
-        #         break
-
-        # self.is_running = False
-        # self.connect_state = False
-        # print("MCU 循环退出~")
-        # # self.sign_data.emit(
-        # #     {"_type": "show_info", "plugins_mode": "mcu", "data": "MCU 连接失败"}
-        # # )
-        # message = {"_type": "show_info", "plugins_mode": "mcu", "data": "MCU 连接失败"}
-        # self.sendSocketMessage(
-        #     code=1, msg="MCU 连接失败", data=message, device_status=-1
-        # )
-        # self.close_connect()
 
     def stop_mcu(self):
         buf = [self.command["stop_mcu"]]
@@ -421,14 +440,14 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             return [0xFF & data >> 24, 0xFF & data >> 16, 0xFF & data >> 8, 0xFF & data]
 
     def open_rgb_led(
-        self,
-        color_name,
-        led_command=1,
-        brightness=80,
-        enable=True,
-        mode="loop",
-        times=2,
-        interval=0.1,
+            self,
+            color_name,
+            led_command=1,
+            brightness=80,
+            enable=True,
+            mode="loop",
+            times=2,
+            interval=0.1,
     ):
         color_name_value = {
             "红色": (156, 6, 3),
@@ -452,8 +471,8 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             )
             self.add_send_data_queue(buf)
 
-    async def getDeviationInfo(self):
-        await asyncio.sleep(0.01)
+    def getDeviationInfo(self):
+        # await asyncio.sleep(0.01)
         try:
             # 发送获取偏移量
             data = [self.command["get_deviation"], 1]
@@ -506,6 +525,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             "翻板舵机高位",  # min 0 max 180,步长0.5
             "翻板舵机上升速度",  # min 1 max 10,步长1
             "翻板舵机下降速度",  # min 1 max 10,步长1
+            "相机焦段",  # min 1 max 10,步长1
         ]
         if action_name not in name_sets:
             self.msg_type = f"{type}_deviation"
@@ -580,32 +600,38 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                 logger.info(f"设备初始化完成:%{data}")
                 self.sendSocketMessage(msg=data, device_status=2)
             else:
-                print("设备异常数据打印:", data)
+                # print("设备异常数据打印:", data)
                 logger.info(f"115  设备异常数据打印:%{data}")
+                msg_data = {
+                    "message": data
+                }
+                self.sendSocketMessage(msg="设备信息打印", data=msg_data, device_status=2,
+                                       msg_type="print_mcu_error_data")
         except BaseException as e:
             print("117 error {}".format(e))
             logger.info(f"117 error %{e}")
         return
+
     def print_mcu_noraml_data(self, receive_data):
         # 扫码数据
         print("接收到255数据:", receive_data)
         try:
             command_mapping = {
-                1:"设备运动",
-                2:"初始化设备",  # 初始化设备
-                3:"处理其他设备",  # 处理其他设备
-                29:"获取所有信息",  # 获取所有信息
-                40:"设置偏移量",  # 设置偏移量
-                41:"读取偏移量",  # 读取偏移量
-                91:"信号转发处理",  # 信号转发处理
-                92:"信号转发返回",  # 信号转发返回
-                44:"获取其他信息",  # 获取其他信息
-                43:"RGB灯的处理与通讯",  ## RGB灯的处理与通讯
-                45:"设置其他信息",  # 设置其他信息
-                47:"查询遥控器电量",  # 查询遥控器电量
-                48:"设置转盘通讯方式 1、串口、2、无线、3 混合",  # 
-                93:"停止运行mcu",  # 停止运行mcu
-                90:"连接MCU",# 连接MCU
+                1: "设备运动",
+                2: "初始化设备",  # 初始化设备
+                3: "处理其他设备",  # 处理其他设备
+                29: "获取所有信息",  # 获取所有信息
+                40: "设置偏移量",  # 设置偏移量
+                41: "读取偏移量",  # 读取偏移量
+                91: "信号转发处理",  # 信号转发处理
+                92: "信号转发返回",  # 信号转发返回
+                44: "获取其他信息",  # 获取其他信息
+                43: "RGB灯的处理与通讯",  ## RGB灯的处理与通讯
+                45: "设置其他信息",  # 设置其他信息
+                47: "查询遥控器电量",  # 查询遥控器电量
+                48: "设置转盘通讯方式 1、串口、2、无线、3 混合",  #
+                93: "停止运行mcu",  # 停止运行mcu
+                90: "连接MCU",  # 连接MCU
             }
             # command = int(receive_data[0])
             command = int(receive_data[1])
@@ -613,11 +639,11 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             # receive_data_temp = receive_data[2:]
             receive_data_temp_text = " ".join([hex(x) for x in receive_data])
             # print("255  command_text:", command_text)
-            if command_text in ["设备运动","处理其他设备"]:
+            if command_text in ["设备运动", "处理其他设备"]:
                 device_id = int(receive_data[2])
                 device_value = int(receive_data[3])
                 device_name_info = self.device_name_dict_mapping[device_id]
-                message_info = {"设备名称":device_name_info,"运动值":device_value}
+                message_info = {"设备名称": device_name_info, "运动值": device_value}
                 print("【设备运动】消息回执:", message_info)
                 logger.info(f"设备运动消息回执:{message_info}")
             print("接收设备消息回执:", command_text)
@@ -626,6 +652,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             print(f"255 error {e}")
             logger.info(f"255 error {e}")
         return
+
     def get_from_mcu_move_respond_data(self, receive_data):
         self.last_from_mcu_move_respond_data = receive_data
 
@@ -712,17 +739,13 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
     def get_basic_info_mcu(self):
         receive_data = self.serial_ins.read_cmd(out_time=1)
         if receive_data is False:
-            print("------------------------------------------------4657564654")
-            print(
-                "------------------------------------------------get_basic_info_mcu------------------"
-            )
-            logger.info("------------------------------------------------4657564654")
+            logger.info("get_basic_info_mcu,------------------------------------------------4657564654")
             self.connect_state = False
             return False
         if not receive_data:
             return False
         command = receive_data[0]
-        print("get_basic_info_mcu",command)
+        print("get_basic_info_mcu", command)
         if command in self.deal_code_func_dict:
             _data = ' '.join([hex(x) for x in receive_data])
             return self.deal_code_func_dict[command](receive_data)
@@ -782,8 +805,8 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
 
             turntable_steering_deviation_dir = receive_data[7]
             turntable_steering_deviation = (
-                receive_data[8] << 8 | receive_data[9]
-            ) * 0.1
+                                                   receive_data[8] << 8 | receive_data[9]
+                                           ) * 0.1
             turntable_steering_deviation = (
                 turntable_steering_deviation * -1
                 if turntable_steering_deviation_dir == 0
@@ -808,35 +831,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
 
             overturn_steering_up_speed = receive_data[16]
             overturn_steering_down_speed = receive_data[17]
-
-            # self.sign_data.emit(
-            #     {
-            #         "_type": "get_deviation_data",
-            #         "plugins_mode": "mcu",
-            #         "data": {
-            #             "camera_high_motor_deviation": camera_high_motor_deviation,
-            #             "camera_steering_deviation": camera_steering_deviation,
-            #             "turntable_steering_deviation": turntable_steering_deviation,
-            #             "overturn_steering_middle": overturn_steering_middle,
-            #             "overturn_steering_high": overturn_steering_high,
-            #             "overturn_steering_up_speed": overturn_steering_up_speed,
-            #             "overturn_steering_down_speed": overturn_steering_down_speed,
-            #         },
-            #     }
-            # )
-            # message = {
-            #     "_type": "get_deviation_data",
-            #     "plugins_mode": "mcu",
-            #     "data": {
-            #         "camera_high_motor_deviation": camera_high_motor_deviation,
-            #         "camera_steering_deviation": camera_steering_deviation,
-            #         "turntable_steering_deviation": turntable_steering_deviation,
-            #         "overturn_steering_middle": overturn_steering_middle,
-            #         "overturn_steering_high": overturn_steering_high,
-            #         "overturn_steering_up_speed": overturn_steering_up_speed,
-            #         "overturn_steering_down_speed": overturn_steering_down_speed,
-            #     },
-            # }
+            self.camera_high_motor_deviation_offset = camera_high_motor_deviation
             get_deviation_data = {
                 "camera_high_motor_deviation": camera_high_motor_deviation,
                 "camera_steering_deviation": camera_steering_deviation,
@@ -851,7 +846,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             self.msg_type = "get_deviation_data"
             self.sendSocketMessage(msg="接收偏移量信息", data=get_deviation_data)
             self.msg_type = "mcu"
-            print("接收偏移量信息")
+            print("接收偏移量信息", get_deviation_data)
             logger.info("接收偏移量信息")
         return
 
@@ -965,7 +960,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
         self.msg_type = "mcu"
 
     def get_data_from_receive_data(
-        self, receive_data, start, len_data, data_magnification=1
+            self, receive_data, start, len_data, data_magnification=1
     ):
         # data_magnification 数据放大倍数,或缩小倍数,默认为1
         try:
@@ -977,10 +972,10 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                 return data * data_magnification
             elif len_data == 4:
                 data = (
-                    receive_data[start] << 24
-                    | receive_data[start + 1] << 16
-                    | receive_data[start + 2] << 8
-                    | receive_data[start + 3]
+                        receive_data[start] << 24
+                        | receive_data[start + 1] << 16
+                        | receive_data[start + 2] << 8
+                        | receive_data[start + 3]
                 )
                 return data * data_magnification
             return None
@@ -1060,13 +1055,13 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             }
             # if self.state_camera_motor
             if all(
-                value == 2
-                for value in [
-                    self.state_camera_motor,
-                    self.state_camera_steering,
-                    self.state_turntable_steering,
-                    self.state_overturn_steering,
-                ]
+                    value == 2
+                    for value in [
+                        self.state_camera_motor,
+                        self.state_camera_steering,
+                        self.state_turntable_steering,
+                        self.state_overturn_steering,
+                    ]
             ):
                 self.init_state = True
                 self.sendSocketMessage(msg="设备初始化完成", device_status=2)
@@ -1418,21 +1413,21 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
         else:
             if self.m_t == 1:
                 if (
-                    self.state_camera_motor == 2
-                    and self.state_camera_steering == 2
-                    and self.state_turntable_steering == 2
-                    and self.state_overturn_steering == 2
+                        self.state_camera_motor == 2
+                        and self.state_camera_steering == 2
+                        and self.state_turntable_steering == 2
+                        and self.state_overturn_steering == 2
                 ):
                     self._mcu_move_state = 2
                 else:
                     self._mcu_move_state = 1
             else:
                 if (
-                    self.state_camera_motor == 2
-                    and self.state_camera_steering == 2
-                    and self.state_turntable_steering == 2
-                    and self.state_overturn_steering == 2
-                    and self.state_move_turntable_steering == 2
+                        self.state_camera_motor == 2
+                        and self.state_camera_steering == 2
+                        and self.state_turntable_steering == 2
+                        and self.state_overturn_steering == 2
+                        and self.state_move_turntable_steering == 2
                 ):
                     self._mcu_move_state = 2
                 else:
@@ -1467,16 +1462,16 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
         return True
 
     def to_device_move(
-        self,
-        device_name,
-        value=0,
-        max_speed=None,
-        up_speed=None,
-        down_speed=None,
-        _is_debug=0,
-        is_relative=0,
-        is_deviation=1,
-        times=2, is_response=False
+            self,
+            device_name,
+            value=0,
+            max_speed=None,
+            up_speed=None,
+            down_speed=None,
+            _is_debug=0,
+            is_relative=0,
+            is_deviation=1,
+            times=2, is_response=False
     ):
         """
         此处输入单位为 毫米,以及度  需要先缩小,再放大
@@ -1513,7 +1508,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                     else down_speed
                 )
                 value = value / 10  # value 单位毫米
-                max_camera_hight = self.camera_height/10
+                max_camera_hight = self.camera_height / 10
                 # print("高度位置",max_camera_hight)
                 if value > max_camera_hight:
                     value = max_camera_hight
@@ -1559,6 +1554,13 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                 value = value / 10  # value 单位毫米
                 assert 0 <= value <= 900
                 assert 0 <= max_speed <= 15000
+            case "camera_zoom_motor":
+                #         相机焦段设置
+                # 精确到0.1焦段
+                max_speed = 500 if max_speed is None else max_speed
+                up_speed = 100 if up_speed is None else up_speed
+                down_speed = 100 if down_speed is None else down_speed
+                assert 0 <= value <= 150
 
         _dir = True if value >= 0 else False
 
@@ -1702,6 +1704,8 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                 await self.smart_shooter.CameraShooter(msg_type="run_mcu")
             case "to_deal_device":
                 self.to_deal_device(device_name, value=value, _type=0, times=1)
+            case "camera_zoom_motor":
+                self.to_deal_device(device_name="camera_zoom_motor")
             case _:
                 pass
 
@@ -1725,7 +1729,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             return False
 
     async def run_mcu_config(
-        self, config_list, goods_art_no, action_info, smart_shooter
+            self, config_list, goods_art_no, action_info, smart_shooter
     ):
         if self.checkDevice() == False:
             return
@@ -1860,14 +1864,15 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
         # await smart_shooter.EnableCameraPreview(
         #             enable_status=False, msg_type="smart_shooter_enable_preview"
         #         )
+
     async def run_mcu_config_single(
-        self,
-        config_info,
-        goods_art_no,
-        msg_type="run_mcu_single_finish",
-        image_index=-1,
-        smart_shooter=None,
-        action_id=-1,
+            self,
+            config_info,
+            goods_art_no,
+            msg_type="run_mcu_single_finish",
+            image_index=-1,
+            smart_shooter=None,
+            action_id=-1,
     ):
         """独立拍照  仅作测试用"""
         await asyncio.sleep(0.01)

+ 57 - 41
python/mcu/Mcu.py

@@ -5,12 +5,15 @@ from .SerialIns import SerialIns
 import time
 from threading import Lock
 from collections import defaultdict
+
+
 # from threading import Thread
 
 
 class Mcu(BaseClass, metaclass=SingletonType):
     instance = None
     init_flag = None
+
     # sign_data = Signal(dict)
     # self_sign = Signal(dict)
 
@@ -119,7 +122,7 @@ class Mcu(BaseClass, metaclass=SingletonType):
             data = receive_data[1:].decode()
             if data == "设备初始化完成":
                 self.init_state = False
-                self.sendSocketMessage(msg=data,device_status=2)
+                self.sendSocketMessage(msg=data, device_status=2)
             print("115  print_mcu_error_data:", data)
         except BaseException as e:
             print("117 error {}".format(e))
@@ -380,8 +383,8 @@ class Mcu(BaseClass, metaclass=SingletonType):
 
             turntable_steering_deviation_dir = receive_data[7]
             turntable_steering_deviation = (
-                receive_data[8] << 8 | receive_data[9]
-            ) * 0.1
+                                                   receive_data[8] << 8 | receive_data[9]
+                                           ) * 0.1
             turntable_steering_deviation = (
                 turntable_steering_deviation * -1
                 if turntable_steering_deviation_dir == 0
@@ -459,14 +462,14 @@ class Mcu(BaseClass, metaclass=SingletonType):
 
     # LED 灯光处理
     def open_rgb_led(
-        self,
-        color_name,
-        led_command=1,
-        brightness=80,
-        enable=True,
-        mode="loop",
-        times=2,
-        interval=0.1,
+            self,
+            color_name,
+            led_command=1,
+            brightness=80,
+            enable=True,
+            mode="loop",
+            times=2,
+            interval=0.1,
     ):
         color_name_value = {
             "红色": (156, 6, 3),
@@ -518,7 +521,7 @@ class Mcu(BaseClass, metaclass=SingletonType):
                     "plugins_mode": "mcu",
                     "data": "MCU 打开串口失败",
                 }
-                self.sendSocketMessage(code=1,msg="接收链接信息", data=message)
+                self.sendSocketMessage(code=1, msg="接收链接信息", data=message)
                 self.serial_ins = None
                 self.connect_state = False
                 return False
@@ -532,11 +535,11 @@ class Mcu(BaseClass, metaclass=SingletonType):
             #     }
             # )
             message = {
-                    "_type": "show_info",
-                    "plugins_mode": "mcu",
-                    "data": "MCU 打开串口失败",
-                }
-            self.sendSocketMessage(code=1,msg="MCU 打开串口失败", data=message)
+                "_type": "show_info",
+                "plugins_mode": "mcu",
+                "data": "MCU 打开串口失败",
+            }
+            self.sendSocketMessage(code=1, msg="MCU 打开串口失败", data=message)
             self.serial_ins = None
             self.connect_state = False
             return False
@@ -611,21 +614,21 @@ class Mcu(BaseClass, metaclass=SingletonType):
     def mcu_move_state(self):
         if self.m_t == 1:
             if (
-                self.state_camera_motor == 2
-                and self.state_camera_steering == 2
-                and self.state_turntable_steering == 2
-                and self.state_overturn_steering == 2
+                    self.state_camera_motor == 2
+                    and self.state_camera_steering == 2
+                    and self.state_turntable_steering == 2
+                    and self.state_overturn_steering == 2
             ):
                 self._mcu_move_state = 2
             else:
                 self._mcu_move_state = 1
         else:
             if (
-                self.state_camera_motor == 2
-                and self.state_camera_steering == 2
-                and self.state_turntable_steering == 2
-                and self.state_overturn_steering == 2
-                and self.state_move_turntable_steering == 2
+                    self.state_camera_motor == 2
+                    and self.state_camera_steering == 2
+                    and self.state_turntable_steering == 2
+                    and self.state_overturn_steering == 2
+                    and self.state_move_turntable_steering == 2
             ):
                 self._mcu_move_state = 2
             else:
@@ -674,7 +677,7 @@ class Mcu(BaseClass, metaclass=SingletonType):
         #     {"_type": "show_info", "plugins_mode": "mcu", "data": "MCU 连接失败"}
         # )
         message = {"_type": "show_info", "plugins_mode": "mcu", "data": "MCU 连接失败"}
-        self.sendSocketMessage(code=1,msg="MCU 连接失败",data=message)
+        self.sendSocketMessage(code=1, msg="MCU 连接失败", data=message)
         self.close_connect()
 
     def __del__(self):
@@ -844,15 +847,15 @@ class Mcu(BaseClass, metaclass=SingletonType):
         self.is_just_init_time = False
 
     def to_device_move(
-        self,
-        device_name,
-        value=0,
-        max_speed=None,
-        up_speed=None,
-        down_speed=None,
-        _is_debug=0,
-        is_relative=0,
-        is_deviation=1,
+            self,
+            device_name,
+            value=0,
+            max_speed=None,
+            up_speed=None,
+            down_speed=None,
+            _is_debug=0,
+            is_relative=0,
+            is_deviation=1,
     ):
         """
         此处输入单位为 毫米,以及度  需要先缩小,再放大
@@ -946,7 +949,7 @@ class Mcu(BaseClass, metaclass=SingletonType):
 
     # 通用串口数据解析器
     def get_data_from_receive_data(
-        self, receive_data, start, len_data, data_magnification=1
+            self, receive_data, start, len_data, data_magnification=1
     ):
         # data_magnification 数据放大倍数,或缩小倍数,默认为1
         try:
@@ -958,10 +961,10 @@ class Mcu(BaseClass, metaclass=SingletonType):
                 return data * data_magnification
             elif len_data == 4:
                 data = (
-                    receive_data[start] << 24
-                    | receive_data[start + 1] << 16
-                    | receive_data[start + 2] << 8
-                    | receive_data[start + 3]
+                        receive_data[start] << 24
+                        | receive_data[start + 1] << 16
+                        | receive_data[start + 2] << 8
+                        | receive_data[start + 3]
                 )
                 return data * data_magnification
             return None
@@ -1012,6 +1015,19 @@ class McuDebug(object):
             is_deviation=self.is_deviation,
         )
 
+    def camera_zoom(self, value, max_speed=None, up_speed=None, down_speed=None, times=1, is_response=False):
+        # 相机焦段
+        self.windows.mcu.to_device_move(device_name="camera_zoom_motor",
+                                        value=value,
+                                        max_speed=max_speed,
+                                        up_speed=up_speed,
+                                        down_speed=down_speed,
+                                        _is_debug=self.is_debug,
+                                        is_deviation=self.is_deviation,
+                                        times=times,
+                                        is_response=is_response,
+                                        )
+
     def turntable_steering(self, value):
         # 转盘舵机
         self.windows.mcu.to_device_move(

+ 15 - 0
python/mcu/McuDebug.py

@@ -1,4 +1,6 @@
 import time
+
+
 class McuDebug(object):
 
     def __init__(self, mcu, is_debug=True, is_deviation=False):
@@ -57,6 +59,19 @@ class McuDebug(object):
             is_deviation=self.is_deviation,
         )
 
+    def camera_zoom(self, value, max_speed=None, up_speed=None, down_speed=None, times=1, is_response=False):
+        # 相机焦段
+        self.mcu.to_device_move(device_name="camera_zoom_motor",
+                                value=value,
+                                max_speed=max_speed,
+                                up_speed=up_speed,
+                                down_speed=down_speed,
+                                _is_debug=self.is_debug,
+                                is_deviation=self.is_deviation,
+                                times=times,
+                                is_response=is_response,
+                                )
+
     def to_deal_device(self, device_name, value=1, _type=0, times=1):
         self.mcu.to_deal_device(
             device_name, value=value, _type=_type, times=times

+ 2 - 1
python/mcu/McuDeviationSet.py

@@ -236,7 +236,8 @@ class McuDeviationSet:
         self.mcu.to_init_device_origin_point(device_name="mcu", is_force=True)
 
     def get_mcu_deviation(self):
-        asyncio.run(self.mcu.getDeviationInfo())
+        self.mcu.getDeviationInfo()
+
     def get_mcu_deviation_info(self, data):
         if "_type" not in data:
             return

+ 2 - 0
python/mcu/OtherSet.py

@@ -108,6 +108,8 @@ class OtherSet():
                 self.mcu_debug.overturn_steering(value=value)
             if name == "翻板舵机高位":
                 self.mcu_debug.overturn_steering(value=value)
+            if name == "相机焦段":
+                self.mcu_debug.camera_zoom(value=value)
             if name == "翻板舵机上升速度":
                 pass
             print(value, name)

+ 30 - 22
python/mcu/ProgramItem.py

@@ -188,10 +188,22 @@ class ProgramItem(BaseClass):
         print("\033[1;31m执行结束\033[0m", self.mcu.action_state)
         # await asyncio.sleep(0.1)
         # self.mcu.to_get_mcu_base_info()
+    async def _do_camera_check(self):
+        """执行一次完整的状态查询检测,返回是否全部停止"""
+        self.mcu.send_get_all_info_to_mcu()
+        await asyncio.sleep(0.5)
+        return all(
+            value == 2
+            for value in [
+                self.mcu.state_camera_motor,
+                self.mcu.state_camera_steering,
+                self.mcu.state_turntable_steering,
+                self.mcu.state_overturn_steering,
+            ]
+        )
+
     async def camera_check_mcu_move_is_stop(self, re_check=False):
         self.error_info_text = ""
-        # 发送基础数据信息
-        # self.mcu.to_get_mcu_base_info()
         _s = time.time()
         check_times = 0
         await self.mcu.cleanAllReceiveData()
@@ -199,27 +211,22 @@ class ProgramItem(BaseClass):
             if self.mcu.action_state != 1:
                 return False
             # 发送获取设备状态消息
-            self.mcu.send_get_all_info_to_mcu()
-            await asyncio.sleep(0.5)
-            if all(
-                    value == 2
-                    for value in [
-                        self.mcu.state_camera_motor,
-                        self.mcu.state_camera_steering,
-                        self.mcu.state_turntable_steering,
-                        self.mcu.state_overturn_steering,
-                    ]
-                ):
-                logger.info("拍照前运动检测状态[成功]")
-                await asyncio.sleep(1)
-                return True
+            if await self._do_camera_check():
+                # 首次检测通过,追加一次确认检测,防止读到旧状态误判
+                logger.info("拍照前运动检测首次通过,发起确认检测")
+                if await self._do_camera_check():
+                    logger.info("拍照前运动检测状态[成功],确认通过,耗时%.1f秒", time.time() - _s)
+                    await asyncio.sleep(0.5)
+                    return True
+                else:
+                    logger.warning("拍照前运动检测确认失败,继续等待")
+                    check_times += 1
             else:
                 check_times += 1
                 if check_times > 5:
-                    logger.info("拍照前运动检测状态[失败]")
+                    logger.info("拍照前运动检测状态[失败],耗时%.1f秒", time.time() - _s)
                     return False
-                # return True
-        print("\033[1;31m执行结束\033[0m", self.mcu.action_state)
+            await asyncio.sleep(0.2)
     async def run(self, total_len=5, *args):
         if total_len == 1:
             self.mode_type = "其他配置"
@@ -393,9 +400,10 @@ class ProgramItem(BaseClass):
             #  配置设置是否运动设备,不运动直接去拍照
             if self.is_move_device:
                 if not await self.camera_check_mcu_move_is_stop(re_check=True):
-                    logger.info("拍照前运动检测失败===>,延迟0.5秒后拍摄")
-                    await asyncio.sleep(0.1)
-                # return
+                    logger.warning("拍照前运动检测失败===>,等待0.5秒后再检测一次")
+                    await asyncio.sleep(0.5)
+                    if not await self.camera_check_mcu_move_is_stop(re_check=True):
+                        logger.warning("拍照前运动检测二次失败===>,强制拍摄")
             is_af = True if self.af_times > 0 else False
             if self.smart_shooter != None:
                 # 拍照

+ 3 - 3
python/mcu/RemoteControlV2.py

@@ -234,7 +234,7 @@ class RemoteControlV2(BaseClass):
         self.msg_type = "blue_tooth"
         self.photo_take_state = 2
 
-    async def handlerTakePhoto(self, smart_shooter=None,session=None,record=None,PointName="A"):
+    async def handlerTakePhoto(self, smart_shooter=None,session=None,record=None,PointName="A",action_id=None):
         """处理单独拍照"""
         await asyncio.sleep(0.1)
         print("开始单拍1")
@@ -251,7 +251,7 @@ class RemoteControlV2(BaseClass):
         self.photo_take_state = 1
         deviceConfig = CRUD(DeviceConfig)
         deviceConfigData = deviceConfig.read(
-            session=session, conditions={"id": record.action_id}
+            session=session, conditions={"id": action_id}
         )
         if deviceConfigData == None:
             self.msg_type = "photo_take"
@@ -269,7 +269,7 @@ class RemoteControlV2(BaseClass):
             record.image_deal_mode,
             record.goods_art_no,
             image_index,
-            record.action_id,
+            action_id,
         )
         session.close()
         print("开始单拍1-插入数据")

+ 6 - 3
python/model/device_config.py

@@ -1,6 +1,8 @@
 from typing import Optional
 from datetime import datetime
 from sqlmodel import Field, SQLModel
+
+
 # 定义DeviceConfig模型类
 
 class DeviceConfig(SQLModel, table=True):
@@ -10,9 +12,9 @@ class DeviceConfig(SQLModel, table=True):
     action_name: Optional[str] = Field(
         default=None, index=True, max_length=128, description="动作名称"
     )
-    is_system: Optional[bool] = Field(default=False,description="是否是系统配置")
-    action_status: Optional[bool] = Field(default=True,description="是否启用;true或者false")
-    action_index: Optional[int] = Field(default=999,description="排序,默认999")
+    is_system: Optional[bool] = Field(default=False, description="是否是系统配置")
+    action_status: Optional[bool] = Field(default=True, description="是否启用;true或者false")
+    action_index: Optional[int] = Field(default=999, description="排序,默认999")
     camera_height: Optional[int] = Field(default=None, description="相机高度;步长1;最小0;最大400")
     camera_angle: Optional[float] = Field(default=None, description="相机倾角;步长0.1,最小-40;最大40")
     number_focus: Optional[int] = Field(default=None, description="对焦次数;最小0;最大1")
@@ -26,6 +28,7 @@ class DeviceConfig(SQLModel, table=True):
     )
     point_name: Optional[str] = Field(default="A", description="点位名称,默认A点")
     is_move_device: Optional[bool] = Field(default=True, description="是否移动设备,默认移动设备")
+    camera_focal_distance: Optional[int] = Field(default=None, description="相机焦距;最小0;最大动态获取")
     led_switch: Optional[bool] = Field(default=False, description="Led灯光开关;")
     is_wait: Optional[bool] = Field(default=False, description="没用;")
     is_need_confirm: Optional[bool] = Field(default=False, description="没用;")

+ 127 - 76
python/sockets/message_handler.py

@@ -18,19 +18,22 @@ from concurrent.futures import ThreadPoolExecutor
 from functools import partial
 from logger import logger
 import stat
+
 # 创建全局线程池
 executor = ThreadPoolExecutor(max_workers=4)
+
+
 async def handlerCutOut(
-    manager=None, run_main=None, config_data={}, websocket=None, msg_type=""
+        manager=None, run_main=None, config_data={}, websocket=None, msg_type=""
 ):
     max_retry_count = 1  # 最多重试1次
     retry_count = 0
-    
+
     while retry_count <= max_retry_count:
         try:
             if retry_count > 0:
                 logger.info(f"抠图操作重试第{retry_count}次")
-            
+
             # return_data = run_main.check_before_cutout(config_data)
             # await run_main.check_for_cutout_image_first_call_back(return_data)
             # 将阻塞操作放到线程池中执行
@@ -49,7 +52,7 @@ async def handlerCutOut(
             logger.error(f"抠图操作发生UnicornException: {e.msg}")
             logger.error(f"异常类型: UnicornException")
             logger.error(f"当前重试次数: {retry_count}/{max_retry_count}")
-            
+
             # data = manager.jsonMessage(
             #     code=1,
             #     msg=e.msg,
@@ -62,7 +65,7 @@ async def handlerCutOut(
             logger.error(f"抠图操作发生FileNotFoundError: {error_msg}")
             logger.error(f"异常类型: FileNotFoundError")
             logger.error(f"当前重试次数: {retry_count}/{max_retry_count}")
-            
+
             # 检查是否是原始图缺失错误
             if "原始图" in error_msg and retry_count < max_retry_count:
                 logger.info("检测到原始图缺失,尝试重新拷贝原图并重试...")
@@ -70,7 +73,7 @@ async def handlerCutOut(
                     # 重新拷贝原图
                     goods_art_nos = config_data.get("goods_art_nos", [])
                     image_dir = config_data.get("image_dir", "")
-                    
+
                     for goods_art_no in goods_art_nos:
                         dealImage = DealImage(image_dir)
                         resFlag, path = dealImage.dealMoveImageV2(
@@ -79,7 +82,7 @@ async def handlerCutOut(
                         if not resFlag:
                             logger.error(f"重新拷贝原图失败: {goods_art_no}")
                             raise UnicornException(f"重新拷贝原图失败: {goods_art_no}")
-                    
+
                     logger.info("原图重新拷贝成功,准备重试抠图操作")
                     retry_count += 1
                     continue  # 重试
@@ -93,12 +96,12 @@ async def handlerCutOut(
             import traceback
             error_msg = str(e)
             stack_trace = traceback.format_exc()
-            
+
             logger.error(f"抠图操作发生未知异常: {error_msg}")
             logger.error(f"异常类型: {type(e).__name__}")
             logger.error(f"当前重试次数: {retry_count}/{max_retry_count}")
             logger.error(f"完整堆栈跟踪:\n{stack_trace}")
-            
+
             # 如果是第一次执行且未达到最大重试次数,则重试
             if retry_count < max_retry_count:
                 logger.info("准备重试抠图操作...")
@@ -106,12 +109,13 @@ async def handlerCutOut(
                 continue
             return
 
+
 def handlerFolderDelete(limit_path, goods_art_no_arrays, is_write_txt_log):
     check_path(limit_path)
     move_folder_array = check_move_goods_art_no_folder(
         "output", goods_art_no_arrays, limit_path
     )
-    
+
     for goods_art_revice in goods_art_no_arrays:
         cutout_goods = f"{limit_path}/{goods_art_revice}"
         if os.path.exists(cutout_goods):
@@ -133,7 +137,8 @@ def handlerFolderDelete(limit_path, goods_art_no_arrays, is_write_txt_log):
                 except (PermissionError) as e:
                     retry_count -= 1
                     if retry_count == 0:
-                        logger.info(f"抠图前目录删除出现问题-PermissionError:{str(e)};{goods_art_revice};{cutout_goods}")
+                        logger.info(
+                            f"抠图前目录删除出现问题-PermissionError:{str(e)};{goods_art_revice};{cutout_goods}")
                         if is_write_txt_log:
                             error_file_path = f"{cutout_goods}/异常说明-出现目录丢失或缺少图片请点开查看原因.txt"
                             with open(error_file_path, 'w', encoding='utf-8') as f:
@@ -163,27 +168,31 @@ def handlerFolderDelete(limit_path, goods_art_no_arrays, is_write_txt_log):
                     else:
                         logger.info(f"抠图前目录删除出现问题--Exception:{str(e)};{retry_count}")
                         time.sleep(0.5)  # 等待0.5秒后重试
-    
+
     return move_folder_array
+
+
 def validate_goods_art_no(goods_art_no):
-        """
-        验证货号输入是否包含特殊字符
-        """
-        import re
-        # 定义不允许的特殊字符,主要是文件系统中可能导致路径问题的字符
-        invalid_chars = r'[<>:"/\\|?*]'
-        if re.search(invalid_chars, goods_art_no):
-            # 找出所有非法字符
-            invalid_found = re.findall(invalid_chars, goods_art_no)
-            invalid_str = ', '.join(set(invalid_found))
-            return False,f"货号包含非法字符: {invalid_str},请修改后再提交"
-        return True,""
+    """
+    验证货号输入是否包含特殊字符
+    """
+    import re
+    # 定义不允许的特殊字符,主要是文件系统中可能导致路径问题的字符
+    invalid_chars = r'[<>:"/\\|?*]'
+    if re.search(invalid_chars, goods_art_no):
+        # 找出所有非法字符
+        invalid_found = re.findall(invalid_chars, goods_art_no)
+        invalid_str = ', '.join(set(invalid_found))
+        return False, f"货号包含非法字符: {invalid_str},请修改后再提交"
+    return True, ""
+
+
 # socket消息发送逻辑处理方法
 async def handlerSend(
-    manager: ConnectionManager,
-    receiveData: str,
-    websocket: WebSocket,
-    smart_shooter: SmartShooter,
+        manager: ConnectionManager,
+        receiveData: str,
+        websocket: WebSocket,
+        smart_shooter: SmartShooter,
 ):
     loop = asyncio.get_event_loop()
     receiveData = json.loads(receiveData)
@@ -245,7 +254,7 @@ async def handlerSend(
             device_name = data.get("device_name")
             value = data.get("value")
             if (device_name == "" or device_name == None) or (
-                value == "" or value == None
+                    value == "" or value == None
             ):
                 data = manager.jsonMessage(code=1, msg="参数错误", msg_type="mcu")
                 await manager.send_personal_message(data, websocket)
@@ -296,7 +305,7 @@ async def handlerSend(
             tab_id = action_configs_json.get(action_flag)
             photoRecord = CRUD(PhotoRecord)
             goods_art_record = photoRecord.read(
-                session, conditions={"goods_art_no": goods_art_no,"delete_time": None}
+                session, conditions={"goods_art_no": goods_art_no, "delete_time": None}
             )
             if goods_art_record != None:
                 data = manager.jsonMessage(
@@ -339,11 +348,6 @@ async def handlerSend(
                 ),
                 name="run_mcu_single",
             )
-        case "get_device_info":
-            device_ctrl = DeviceControl(
-                websocket_manager=manager, smart_shooter=smart_shooter
-            )
-            device_ctrl.get_device_info()
         case "handler_take_picture":
             if data is None:
                 PointName = "A"
@@ -352,40 +356,53 @@ async def handlerSend(
             device_ctrl = DeviceControl(
                 websocket_manager=manager, smart_shooter=smart_shooter
             )
-            print("收到单拍指令",'handler_take_picture')
+            print("收到单拍指令", 'handler_take_picture')
             await device_ctrl.controlDevice("laser_position", 0)
             blue_tooth = BlueToothMode(
                 websocket_manager=manager, smart_shooter=smart_shooter
             )
             session = SqlQuery()
             crud = CRUD(PhotoRecord)
-            record = crud.read(session=session, order_by="id", ascending=False,conditions={"delete_time": None})
+            record = crud.read(session=session, order_by="id", ascending=False, conditions={"delete_time": None})
             if record == None:
                 # 发送失败消息
                 data = manager.jsonMessage(
-                            code=1,
-                            msg="单拍失败,请先输入货号或扫码进行组合拍摄",
-                            msg_type="handler_take_picture",
-                        )
+                    code=1,
+                    msg="单拍失败,请先输入货号或扫码进行组合拍摄",
+                    msg_type="handler_take_picture",
+                )
                 await manager.send_personal_message(data, websocket)
                 return
             try:
                 limit_path = "{}/{}".format(settings.OUTPUT_DIR,
-                    time.strftime("%Y-%m-%d", time.localtime(time.time()))
-                )
-                move_folder_array = handlerFolderDelete(limit_path,[record.goods_art_no],False)
+                                            time.strftime("%Y-%m-%d", time.localtime(time.time()))
+                                            )
+                move_folder_array = handlerFolderDelete(limit_path, [record.goods_art_no], False)
             except UnicornException as e:
                 data = manager.jsonMessage(
-                        code=1,
-                        msg=e.msg,
-                        msg_type="handler_take_picture",
-                    )
+                    code=1,
+                    msg=e.msg,
+                    msg_type="handler_take_picture",
+                )
                 await manager.send_personal_message(data, websocket)
                 return
+            record_action_id = record.action_id
+            deviceConfig = CRUD(DeviceConfig)
+            deviceConfigSignle = deviceConfig.read(session, conditions={"id": record_action_id})
+            deviceConfigAll = deviceConfig.read_all(session, conditions={"tab_id": deviceConfigSignle.tab_id},
+                                                    order_by="action_index",
+                                                    ascending=True, )
+            lastConfig = deviceConfigAll[-1]
+            if not lastConfig.take_picture:
+                used_config_id = lastConfig.id
+            else:
+                used_config_id = deviceConfigAll[0].id
             loop.create_task(
-                blue_tooth.remote_control_v2.handlerTakePhoto(smart_shooter,session,record,PointName),
+                blue_tooth.remote_control_v2.handlerTakePhoto(smart_shooter, session, record, PointName,
+                                                              used_config_id),
                 name="run_mcu_config",
             )
+            session.close()
             await asyncio.sleep(2.5)
             await device_ctrl.controlDevice("laser_position", 1)
         case "re_take_picture":  # 重拍
@@ -393,7 +410,7 @@ async def handlerSend(
             record_id = data.get("record_id")
             session = SqlQuery()
             photoRecord = CRUD(PhotoRecord)
-            goods_art_record = photoRecord.read(session, conditions={"id": record_id,"delete_time": None})
+            goods_art_record = photoRecord.read(session, conditions={"id": record_id, "delete_time": None})
             if goods_art_record == None:
                 data = manager.jsonMessage(
                     code=1,
@@ -437,10 +454,7 @@ async def handlerSend(
             device_ctrl = DeviceControl(
                 websocket_manager=manager, smart_shooter=smart_shooter
             )
-            loop.create_task(
-                device_ctrl.getDeviationInfo(),
-                name="get_deviation",
-            )
+            device_ctrl.getDeviationInfo()
         case "set_deviation":
             device_ctrl = DeviceControl(
                 websocket_manager=manager, smart_shooter=smart_shooter
@@ -544,9 +558,9 @@ async def handlerSend(
             msg_type = "smart_shooter_auto_focus"
             temp_A_point = camera_configs.get(PointName, None)
             CameraKey = temp_A_point.get("CameraKey", None) if temp_A_point else None
-            status,msg = await  smart_shooter.CameraAutofocus(
-                    CameraKey=CameraKey,
-                )
+            status, msg = await  smart_shooter.CameraAutofocus(
+                CameraKey=CameraKey,
+            )
             if not status:
                 data = manager.jsonMessage(
                     code=1,
@@ -607,15 +621,15 @@ async def handlerSend(
             if goods_art_no:
                 try:
                     limit_path = "{}/{}".format(settings.OUTPUT_DIR,
-                        time.strftime("%Y-%m-%d", time.localtime(time.time()))
-                    )
-                    move_folder_array = handlerFolderDelete(limit_path,[goods_art_no],False)
+                                                time.strftime("%Y-%m-%d", time.localtime(time.time()))
+                                                )
+                    move_folder_array = handlerFolderDelete(limit_path, [goods_art_no], False)
                 except UnicornException as e:
                     data = manager.jsonMessage(
-                            code=1,
-                            msg=e.msg,
-                            msg_type="smart_shooter_photo_take",
-                        )
+                        code=1,
+                        msg=e.msg,
+                        msg_type="smart_shooter_photo_take",
+                    )
                     await manager.send_personal_message(data, websocket)
                     return
             is_af = True
@@ -637,7 +651,7 @@ async def handlerSend(
             goods_art_no = data.get("goods_art_no", "")
             session = SqlQuery()
             photoRecord = CRUD(PhotoRecord)
-            goods_art_record = photoRecord.read(session, conditions={"id": id,"delete_time": None})
+            goods_art_record = photoRecord.read(session, conditions={"id": id, "delete_time": None})
             if goods_art_record == None:
                 data = manager.jsonMessage(
                     code=1,
@@ -648,7 +662,7 @@ async def handlerSend(
                 return
             reset_data = {"image_path": None}
             photoRecord.update(session, id, **reset_data)
-            device_ctrl = DeviceControl(websocket_manager=manager)#
+            device_ctrl = DeviceControl(websocket_manager=manager)  #
             loop.create_task(
                 device_ctrl.only_take_photo(
                     goods_art_no=goods_art_no,
@@ -667,23 +681,23 @@ async def handlerSend(
             run_main = RunMain(obj, token, uuid)
             goods_art_no_arrays = data.get("goods_art_no", [])
             limit_path = "{}/{}".format(settings.OUTPUT_DIR,
-                time.strftime("%Y-%m-%d", time.localtime(time.time()))
-            )
+                                        time.strftime("%Y-%m-%d", time.localtime(time.time()))
+                                        )
             try:
-                move_folder_array = handlerFolderDelete(limit_path,goods_art_no_arrays,True)
+                move_folder_array = handlerFolderDelete(limit_path, goods_art_no_arrays, True)
             except UnicornException as e:
                 data = manager.jsonMessage(
-                        code=1,
-                        msg=e.msg,
-                        msg_type=msg_type,
-                    )
+                    code=1,
+                    msg=e.msg,
+                    msg_type=msg_type,
+                )
                 await manager.send_personal_message(data, websocket)
                 return
             # 该数组表示是否需要后面的移动文件夹操作,减少重复抠图,提升抠图时间和速度
             session = SqlQuery()
             for goods_art_no in goods_art_no_arrays:
                 pr = CRUD(PhotoRecord)
-                images = pr.read_all(session, conditions={"goods_art_no": goods_art_no,"delete_time": None})
+                images = pr.read_all(session, conditions={"goods_art_no": goods_art_no, "delete_time": None})
                 if not images:
                     data = manager.jsonMessage(
                         code=1,
@@ -729,7 +743,7 @@ async def handlerSend(
             cutOutMode = (
                 "1"
                 if settings.getSysConfigs("other_configs", "cutout_mode", "普通抠图")
-                == "普通抠图"
+                   == "普通抠图"
                 else "2"
             )
             config_data = {
@@ -767,8 +781,45 @@ async def handlerSend(
                     websocket=websocket,
                     msg_type=msg_type,
                 ),
-                 name="handlerCutOut",
+                name="handlerCutOut",
             )
+        case "get_dynamic_config":
+            device_ctrl = DeviceControl(
+                websocket_manager=manager, smart_shooter=smart_shooter
+            )
+            dynamic_configs = device_ctrl.get_dynamic_config()
+            # print("dynamic_configs", dynamic_configs)
+            data = manager.jsonMessage(
+                code=0,
+                msg="成功",
+                msg_type="get_dynamic_config",
+                data=dynamic_configs,
+            )
+            await manager.send_personal_message(data, websocket)
+        case "set_dynamic_config":
+            # 设置动态参数
+            device_ctrl = DeviceControl(
+                websocket_manager=manager, smart_shooter=smart_shooter
+            )
+            readonly = data.get("readonly", False)
+            if not readonly:
+                dynamic_configs = device_ctrl.set_dynamic_config(data)
+            else:
+                dynamic_configs = False
+            data = manager.jsonMessage(
+                code=0,
+                msg="成功",
+                msg_type="set_dynamic_config",
+                data={"status": dynamic_configs},
+            )
+            await manager.send_personal_message(data, websocket)
+        case "get_device_info":
+            device_ctrl = DeviceControl(
+                websocket_manager=manager, smart_shooter=smart_shooter
+            )
+            device_ctrl.getDeviationInfo()
+            await asyncio.sleep(0.1)
+            device_ctrl.get_device_info()
         case _:
             data = manager.jsonMessage(code=1, msg="未知消息")
             await manager.send_personal_message(data, websocket)

+ 49 - 20
python/sockets/socket_server.py

@@ -14,12 +14,15 @@ import traceback
 import logging
 from utils import common
 from utils.common import message_queue
+
 logger = logging.getLogger(__name__)
 conn_manager = ConnectionManager()
 active_connections = set()
 device_ctrl = DeviceControl(websocket_manager=conn_manager)
 blue_tooth = BlueToothMode(websocket_manager=conn_manager)
 smart_shooter = SmartShooter(websocket_manager=conn_manager)
+
+
 async def updateDataRecord(PhotoFilename, id):
     await asyncio.sleep(0.01)
     create_time = datetime.datetime.fromtimestamp(os.path.getctime(PhotoFilename))
@@ -27,15 +30,19 @@ async def updateDataRecord(PhotoFilename, id):
     # record_model = PhotoRecord(**data)
     session = SqlQuery()
     record_model = CRUD(PhotoRecord)
-    model = record_model.read(session, conditions={"id": id,"delete_time": None})
+    model = record_model.read(session, conditions={"id": id, "delete_time": None})
     if model == None:
         print(f"smart shooter 拍照记录更新失败,记录id:{id},不存在")
+        session.close()
+        return None
     else:
         # 走编辑逻辑
-        settings.syncPhotoRecord(data,action_type=3)
-        record_model.updateConditions(session, conditions={"id": id}, **data)
+        settings.syncPhotoRecord(data, action_type=3)
+        record_data = record_model.updateConditions(session, conditions={"id": id}, **data)
+        session.close()
         print(f"smart shooter 拍照记录更新成功,记录id:{id}")
-    session.close()
+        return record_data
+
 
 @app.websocket("/ws")
 async def websocket_endpoint(websocket: WebSocket):
@@ -52,21 +59,21 @@ async def websocket_endpoint(websocket: WebSocket):
     # 启动 smart_shooter.connect_listen 服务
     listen_task = None
     tasks = set()
-    send_task = None # <--- 新增
+    send_task = None  # <--- 新增
     try:
         # 初始化回调函数
         smart_shooter.callback_listen = MsgCallback
         # 创建任务来并发处理不同类型的消息
         handler_task = asyncio.create_task(handler_messages(websocket))
         # send_task = asyncio.create_task(send_message(websocket))
-        send_task = asyncio.create_task(send_message(websocket)) # <--- 启动消费者
+        send_task = asyncio.create_task(send_message(websocket))  # <--- 启动消费者
         loop = asyncio.get_event_loop()
         listen_task = loop.run_in_executor(None, smart_shooter.connect_listen)
         # send_task = loop.run_in_executor(None, send_message(websocket))
         # 创建任务来启动 connect_listen
         # listen_task = asyncio.create_task(restart_smart_shooter_listener())
         # 等待所有任务完成
-        await asyncio.gather(handler_task,listen_task, return_exceptions=True)
+        await asyncio.gather(handler_task, listen_task, return_exceptions=True)
 
     except WebSocketDisconnect:
         print("Client disconnected")
@@ -75,21 +82,21 @@ async def websocket_endpoint(websocket: WebSocket):
     finally:
         # 确保任务被正确取消和清理
         tasks_to_cancel = []
-        
+
         if handler_task and not handler_task.done():
             tasks_to_cancel.append(handler_task)
-        
+
         if send_task and not send_task.done():
             tasks_to_cancel.append(send_task)
-        
+
         # 取消所有待处理的任务
         for task in tasks_to_cancel:
             task.cancel()
-        
+
         # 等待任务取消完成
         if tasks_to_cancel:
             await asyncio.gather(*tasks_to_cancel, return_exceptions=True)
-        
+
         # 清理连接
         active_connections.discard(websocket)
         print("WebSocket connection cleaned up")
@@ -141,7 +148,7 @@ async def handler_messages(websocket):
                 )
             )
         except Exception as e:
-            print("socket error",e)
+            print("socket error", e)
             break
 
 
@@ -169,17 +176,33 @@ async def send_message(websocket):
         try:
             # 1. 异步等待消息,不会阻塞其他协程
             data = await message_queue.get()
-            
+
             # 2. 发送消息
             if common.websocket_manager:
                 # 假设 broadcast 或 send_personal_message 是异步方法
-                await common.websocket_manager.send_personal_message(data,common.websocket) 
+                await common.websocket_manager.send_personal_message(data, common.websocket)
                 # 或者根据你的 ConnectionManager 实现调用具体发送方法
-            
+
             message_queue.task_done()
         except Exception as e:
             print(f"消息消费错误: {e}")
-            await asyncio.sleep(1) # 防止死循环报错
+            await asyncio.sleep(1)  # 防止死循环报错
+
+
+async def getActionInfo(record_info):
+    await asyncio.sleep(0.01)
+    if not record_info:
+        return None
+    action_id = record_info.action_id
+    if not action_id:
+        return None
+    session = SqlQuery()
+    device_model = CRUD(DeviceConfig)
+    model = device_model.read(session, conditions={"id": action_id})
+    if not model:
+        session.close()
+        return None
+    return model
 
 
 async def MsgCallback(msg):
@@ -190,7 +213,7 @@ async def MsgCallback(msg):
             PhotoLocation = msg.get("PhotoLocation")
             PhotoOrigin = msg.get("PhotoOrigin")
             if (PhotoFilename != "" and PhotoFilename != None) and (
-                PhotoLocation == "Local Disk"
+                    PhotoLocation == "Local Disk"
             ):
                 # temp_photo_name = PhotoFilename
                 # 更新拍照记录
@@ -201,15 +224,20 @@ async def MsgCallback(msg):
                     if PhotoOrigin != "" and PhotoOrigin not in ["external", "ui"]:
                         goods_art_no, id = PhotoOrigin.split(",")
                     # 创建任务来处理数据库更新,避免阻塞回调
-                    await updateDataRecord(PhotoFilename, id)
+                    recordResult = await updateDataRecord(PhotoFilename, id)
                 except Exception as e:
                     print("拍照更新异常", e)
+                    recordResult = None
+                actionModel = None
+                if recordResult:
+                    actionModel = await getActionInfo(recordResult)
                 data = conn_manager.jsonMessage(
                     code=0,
                     msg=f"照片获取成功",
                     data={
                         "photo_file_name": PhotoFilename,
                         "goods_art_no": goods_art_no,
+                        "action_name": actionModel.action_name if actionModel else None,
                     },
                     msg_type="smart_shooter_photo_take",
                 )
@@ -228,6 +256,7 @@ async def MsgCallback(msg):
         # case _:
         #     print("收到未知数据:{}".format(msg))
 
+
 # @app.on_event("startup")
 # async def startup_event():
 #     loop = asyncio.get_event_loop()
@@ -257,7 +286,7 @@ async def shutdown_event():
     if len(diviceList) == 0:
         blue_tooth.bluetooth_exit = True
         blue_tooth.clearMyInstance()
-    diviceAddress = "" if len(list(diviceList.keys()))==0 else list(diviceList.keys())[0]
+    diviceAddress = "" if len(list(diviceList.keys())) == 0 else list(diviceList.keys())[0]
     if diviceAddress != "":
         print(diviceList.get(diviceAddress))
         diviceName = diviceList[diviceAddress]["name"]