Sfoglia il codice sorgente

Merge remote-tracking branch 'origin/master'

panqiuyao 7 mesi fa
parent
commit
ed9e954644

+ 25 - 13
python/action.json

@@ -2,8 +2,9 @@
     {
         "mode_type": "执行左脚程序",
         "execution_type": "程序1",
-        "action_name": "左脚俯拍鞋子",
+        "action_name": "俯视",
         "action_index": 20,
+        "is_system":true,
         "picture_index": 99,
         "camera_height": 200,
         "camera_angle": 14.0,
@@ -21,8 +22,9 @@
     {
         "mode_type": "执行左脚程序",
         "execution_type": "程序1",
-        "action_name": "侧视",
+        "action_name": "侧视",
         "action_index": 10,
+        "is_system": true,
         "picture_index": 99,
         "camera_height": 0,
         "camera_angle": 3.0,
@@ -40,9 +42,10 @@
     {
         "mode_type": "执行左脚程序",
         "execution_type": "程序1",
-        "action_name": "后跟",
+        "action_name": "后跟",
         "action_index": 30,
         "picture_index": 99,
+        "is_system": true,
         "camera_height": 0,
         "camera_angle": 3.0,
         "number_focus": 0,
@@ -59,9 +62,10 @@
     {
         "mode_type": "执行左脚程序",
         "execution_type": "程序2",
-        "action_name": "鞋底",
+        "action_name": "鞋底",
         "action_index": 40,
         "picture_index": 99,
+        "is_system": true,
         "camera_height": 0,
         "camera_angle": 3.0,
         "number_focus": 0,
@@ -78,10 +82,11 @@
     {
         "mode_type": "执行左脚程序",
         "execution_type": "程序2",
-        "action_name": "内里",
+        "action_name": "内里",
         "action_index": 50,
         "picture_index": 99,
         "camera_height": 0,
+        "is_system": true,
         "camera_angle": 3.0,
         "number_focus": 0,
         "take_picture": true,
@@ -102,6 +107,7 @@
         "picture_index": 99,
         "camera_height": 200,
         "camera_angle": 12.0,
+        "is_system": false,
         "number_focus": 1,
         "take_picture": false,
         "turntable_position": 300.0,
@@ -115,12 +121,13 @@
     },
     {
         "mode_type": "执行右脚程序",
-        "execution_type": "程序1",
-        "action_name": "右脚俯拍鞋子",
+        "execution_type": "程序2",
+        "action_name": "俯视",
         "action_index": 20,
         "picture_index": 99,
         "camera_height": 200,
         "camera_angle": 14.0,
+        "is_system": true,
         "number_focus": 2,
         "take_picture": true,
         "turntable_position": 300.0,
@@ -134,12 +141,13 @@
     },
     {
         "mode_type": "执行右脚程序",
-        "execution_type": "程序1",
-        "action_name": "侧视",
+        "execution_type": "程序2",
+        "action_name": "侧视",
         "action_index": 10,
         "picture_index": 99,
         "camera_height": 0,
         "camera_angle": 3.0,
+        "is_system": true,
         "number_focus": 0,
         "take_picture": true,
         "turntable_position": 300.0,
@@ -153,10 +161,11 @@
     },
     {
         "mode_type": "执行右脚程序",
-        "execution_type": "程序1",
-        "action_name": "后跟",
+        "execution_type": "程序2",
+        "action_name": "后跟",
         "action_index": 30,
         "picture_index": 99,
+        "is_system": true,
         "camera_height": 0,
         "camera_angle": 3.0,
         "number_focus": 0,
@@ -173,9 +182,10 @@
     {
         "mode_type": "执行右脚程序",
         "execution_type": "程序2",
-        "action_name": "鞋底",
+        "action_name": "鞋底",
         "action_index": 40,
         "picture_index": 99,
+        "is_system": true,
         "camera_height": 0,
         "camera_angle": 3.0,
         "number_focus": 0,
@@ -192,9 +202,10 @@
     {
         "mode_type": "执行右脚程序",
         "execution_type": "程序2",
-        "action_name": "内里",
+        "action_name": "内里",
         "action_index": 50,
         "picture_index": 99,
+        "is_system": true,
         "camera_height": 0,
         "camera_angle": 3.0,
         "number_focus": 0,
@@ -215,6 +226,7 @@
         "action_index": 60,
         "picture_index": 99,
         "camera_height": 200,
+        "is_system": false,
         "camera_angle": 12.0,
         "number_focus": 1,
         "take_picture": false,

+ 100 - 37
python/api.py

@@ -21,6 +21,8 @@ from databases import DeviceConfig, SqlQuery, CRUD, select
 from service.run_main import RunMain
 import importlib
 from service.auto_deal_pics.upload_pic import UploadPic
+from service.OnePicTest import OnePicTest
+
 # from service.AutoDealPics import AutoDealPics
 # for plugin in settings.plugins:
 #     module_path, class_name = plugin.rsplit(".", 1)
@@ -122,6 +124,7 @@ async def forwardRequest(request: HlmForwardRequest):
     except Exception as e:
         raise UnicornException(e)
 
+
 def fromExcelHandler(params: HandlerDetail):
     excel_path = params.excel_path
     excel_df = pd.read_excel(excel_path, sheet_name=0, header=0)
@@ -258,6 +261,8 @@ def fromExcelHandler(params: HandlerDetail):
         "msg": "",
         "data": {"output_folder": handler_result_folder, "list": handler_result},
     }
+
+
 @app.post("/handle_detail")
 async def handle_detail(request: Request, params: HandlerDetail):
     goods_art_no_array = params.goods_art_no
@@ -294,28 +299,30 @@ async def handle_detail(request: Request, params: HandlerDetail):
                 temp_class[tempItem.template_id] = tempItem.template_local_classes
                 temp_name_list.append(tempItem.template_id)
             config_data = {
-                    "image_dir": path,
-                    "image_order": params.template_image_order,
-                    "goods_art_no": goods_art_no,
-                    "is_check_number": False,
-                    "resize_image_view": "后跟",
-                    "cutout_mode": settings.CUTOUT_MODE,
-                    "logo_path": params.logo_path,
-                    "special_goods_art_no_folder_line": "",
-                    "is_use_excel": False if params.excel_path == "" else True,  # 是否使用excel
-                    "excel_path": params.excel_path,  # excel路径
-                    "is_check_color_is_all": False,
-                    "cutout_is_pass": True,
-                    "assigned_page_dict": {},
-                    "detail_is_pass": True,
-                    "upload_is_pass": False,
-                    "upload_is_enable": False,
-                    "is_filter": False,
-                    "temp_class": temp_class,
-                    "temp_name": params.temp_name,
-                    "temp_name_list": temp_name_list,
-                    "target_error_folder": f"{path}/软件-生成详情错误",
-                }
+                "image_dir": path,
+                "image_order": params.template_image_order,
+                "goods_art_no": goods_art_no,
+                "is_check_number": False,
+                "resize_image_view": "后跟",
+                "cutout_mode": settings.CUTOUT_MODE,
+                "logo_path": params.logo_path,
+                "special_goods_art_no_folder_line": "",
+                "is_use_excel": (
+                    False if params.excel_path == "" else True
+                ),  # 是否使用excel
+                "excel_path": params.excel_path,  # excel路径
+                "is_check_color_is_all": False,
+                "cutout_is_pass": True,
+                "assigned_page_dict": {},
+                "detail_is_pass": True,
+                "upload_is_pass": False,
+                "upload_is_enable": False,
+                "is_filter": False,
+                "temp_class": temp_class,
+                "temp_name": params.temp_name,
+                "temp_name_list": temp_name_list,
+                "target_error_folder": f"{path}/软件-生成详情错误",
+            }
             # 动态导入类
             temp_class_dict = {}
             for key, class_path in config_data["temp_class"].items():
@@ -326,18 +333,21 @@ async def handle_detail(request: Request, params: HandlerDetail):
 
             config_data["temp_class"] = temp_class_dict
             obj = None
-            run_main = RunMain(obj,token)
+            run_main = RunMain(obj, token)
             return_data = run_main.check_before_cutout(config_data)
             cutout_res = run_main.check_for_cutout_image_first_call_back(return_data)
             check_for_detail_first_res = None
             if cutout_res == True:
-                return_data_check_before_detail = run_main.check_before_detail(config_data)
+                return_data_check_before_detail = run_main.check_before_detail(
+                    config_data
+                )
                 print(
-                    "return_data_check_before_detail======> 测试 ==>", return_data_check_before_detail
+                    "return_data_check_before_detail======> 测试 ==>",
+                    return_data_check_before_detail,
                 )
                 check_for_detail_first_res = run_main.check_for_detail_first_call_back(
-                        return_data_check_before_detail
-                    )
+                    return_data_check_before_detail
+                )
             if isinstance(check_for_detail_first_res, partial):
                 result = check_for_detail_first_res()
                 try:
@@ -353,22 +363,24 @@ async def handle_detail(request: Request, params: HandlerDetail):
                     print("to_deal_dir", to_deal_dir)
                     if os.path.exists(to_deal_dir):
                         upload_pic = UploadPic(
-                                windows=None,
-                                to_deal_dir=to_deal_dir,
-                                config_data=config_data,
-                                token=token,
-                            )
+                            windows=None,
+                            to_deal_dir=to_deal_dir,
+                            config_data=config_data,
+                            token=token,
+                        )
                         upload_pic.run()
                 out_put_dir = config_data["out_put_dir"]
-                out_put_dir_path = "{}/{}".format(os.getcwd(), out_put_dir).replace("\\", "/")
+                out_put_dir_path = "{}/{}".format(os.getcwd(), out_put_dir).replace(
+                    "\\", "/"
+                )
                 handler_result_folder = os.path.dirname(out_put_dir_path)
                 handler_result.append(
-                        {"goods_art_no": goods_art_no, "success": True, "info": "处理成功"}
-                    )
+                    {"goods_art_no": goods_art_no, "success": True, "info": "处理成功"}
+                )
             else:
                 handler_result.append(
-                        {"goods_art_no": goods_art_no, "success": False, "info": "处理失败"}
-                    )
+                    {"goods_art_no": goods_art_no, "success": False, "info": "处理失败"}
+                )
         except Exception as e:
             handler_result.append(
                 {"goods_art_no": goods_art_no, "success": False, "info": str(e)}
@@ -379,6 +391,7 @@ async def handle_detail(request: Request, params: HandlerDetail):
         "data": {"output_folder": handler_result_folder, "list": handler_result},
     }
 
+
 @app.post("/get_device_configs", description="获取可执行程序命令列表")
 def get_device_configs(params: ModelGetDeviceConfig):
     mode_type = params.mode_type
@@ -408,6 +421,20 @@ def get_device_configs(params: ModelGetDeviceConfigDetail):
     return {"code": 0, "msg": "", "data": model}
 
 
+@app.post("/device_config_detail_query", description="通过条件获取可执行程序详情")
+def get_device_configs(params: ModelGetDeviceConfigDetailQuery):
+    mode_type = params.mode_type
+    action_name = params.action_name
+    session = SqlQuery()
+    configModel = CRUD(DeviceConfig)
+    model = configModel.read(
+        session, conditions={"mode_type": mode_type, "action_name": action_name}
+    )
+    if model == None:
+        return {"code": 1, "msg": "数据不存在", "data": None}
+    return {"code": 0, "msg": "", "data": model}
+
+
 @app.post("/remove_config", description="删除一条可执行命令")
 def get_device_configs(params: ModelGetDeviceConfigDetail):
     action_id = params.id
@@ -416,6 +443,8 @@ def get_device_configs(params: ModelGetDeviceConfigDetail):
     model = configModel.read(session, conditions={"id": action_id})
     if model == None:
         return {"code": 1, "msg": "数据不存在", "data": None}
+    if model.is_system == True:
+        return {"code": 1, "msg": "系统配置不允许删除", "data": None}
     configModel.delete(session, obj_id=action_id)
     return {"code": 0, "msg": "删除成功", "data": None}
 
@@ -491,6 +520,22 @@ def get_photo_records(page: int = 1, size: int = 5):
     }
 
 
+@app.get("/get_last_photo_record", description="获取最后一条拍照记录")
+def get_last_photo_record():
+
+    session = SqlQuery()
+    statement = (
+        select(PhotoRecord).where(PhotoRecord.image_path!=None).order_by(desc("photo_create_time"))
+    )
+    result = session.exec(statement).first()
+    session.close()
+    return {
+        "code": 0,
+        "msg": "",
+        "data": result,
+    }
+
+
 @app.get("/get_photo_record_detail", description="通过货号获取拍照记录详情")
 def get_photo_records(goods_art_no: str = None):
     if goods_art_no == None:
@@ -549,3 +594,21 @@ def save_sys_configs(params: SysConfigParams):
         session, conditions={"key": params.key}, **kwargs
     )
     return {"code": 0, "msg": "操作成功", "data": save_device_config}
+
+
+@app.post("/create_main_image", description="创建主图测试")
+def create_main_image(params: MaineImageTest):
+    file_path = params.file_path
+    onePic = OnePicTest(pic_path=file_path)
+    # session = SqlQuery()
+    # sysConfig = CRUD(SysConfigs)
+    # model = sysConfig.read(session, conditions={"key": params.key})
+    # if model == None:
+    #     return {"code": 1, "msg": "配置不存在", "data": None}
+    # # 走编辑逻辑
+    # kwargs = params.__dict__
+    # save_device_config = sysConfig.updateConditions(
+    #     session, conditions={"key": params.key}, **kwargs
+    # )
+    main_out_path = onePic.HandlerMainImage()
+    return {"code": 0, "msg": "操作成功", "data": {"main_out_path": main_out_path}}

+ 36 - 18
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
+from sqlalchemy import and_, desc, asc
 
 from sqlalchemy.dialects import sqlite
 from model import DeviceConfig, PhotoRecord, SysConfigs
@@ -22,9 +22,12 @@ engine = create_engine(
     pool_recycle=1800,
 )
 
+
 # 创建表
 def create_all_database():
     SQLModel.metadata.create_all(engine)
+
+
 # 创建会话
 def __get_session():
     with Session(engine) as session:
@@ -35,7 +38,7 @@ def __get_session():
 
 
 def batch_insert_device_configs(session: Session, data_list: list):
-    '''批量插入数据到设备配置表'''
+    """批量插入数据到设备配置表"""
     for data in data_list:
         device_config = DeviceConfig(**data)
         session.add(device_config)
@@ -52,13 +55,16 @@ def batch_insert_sys_configs(session: Session, data_list: list):
     session.close()
 
 
-def insert_photo_records(image_deal_mode: int, goods_art_no: str, image_index:int):
+def insert_photo_records(
+    image_deal_mode: int, goods_art_no: str, image_index: int, action_id: int
+):
     session = SqlQuery()
     """批量插入数据到照片记录"""
     data = {
         "image_deal_mode": image_deal_mode,
         "goods_art_no": goods_art_no,
         "image_index": image_index,
+        "action_id": action_id,
     }
     device_config = PhotoRecord(**data)
     session.add(device_config)
@@ -82,21 +88,28 @@ class CRUD:
         return db_obj
 
     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:
-            query = query.where(and_(*(getattr(self.model, key) == value for key, value in conditions.items())))
+            query = query.where(
+                and_(
+                    *(
+                        getattr(self.model, key) == value
+                        for key, value in conditions.items()
+                    )
+                )
+            )
         if order_by:
             if ascending:
                 query = query.order_by(asc(getattr(self.model, order_by)))
             else:
                 query = query.order_by(desc(getattr(self.model, order_by)))
-        data =  session.exec(query).first()
+        data = session.exec(query).first()
         session.close()
         return data
 
@@ -109,21 +122,28 @@ class CRUD:
     ):
         query = select(self.model)
         if conditions:
-            query = query.where(and_(*(getattr(self.model, key) == value for key, value in conditions.items())))
+            query = query.where(
+                and_(
+                    *(
+                        getattr(self.model, key) == value
+                        for key, value in conditions.items()
+                    )
+                )
+            )
         if order_by:
             if ascending:
                 query = query.order_by(asc(getattr(self.model, order_by)))
             else:
                 query = query.order_by(desc(getattr(self.model, order_by)))
-        data =  session.exec(query).all()
+        data = session.exec(query).all()
         session.close()
         return data
 
     def update(self, session: Session, obj_id: int, **kwargs):
         db_obj = session.get(self.model, obj_id)
         for key, value in kwargs.items():
-            if value == None or value =="":
-                continue
+            # if value == None or value == "":
+            #     continue
             setattr(db_obj, key, value)
         session.commit()
         session.refresh(db_obj)
@@ -131,10 +151,7 @@ class CRUD:
         return db_obj
 
     def updateConditions(
-        self,
-        session: Session,
-        conditions: Optional[Dict] = None,
-        **kwargs
+        self, session: Session, conditions: Optional[Dict] = None, **kwargs
     ):
         query = select(self.model)
         if conditions:
@@ -189,6 +206,7 @@ class CRUD:
 def SqlQuery():
     return next(__get_session())
 
+
 # 使用示例
 if __name__ == "__main__":
     pass

+ 14 - 0
python/docs/socket命令.md

@@ -506,4 +506,18 @@ _(该命令用于单独自定义配置中某一项的单独调整测试,不进
     "msg_type": "send_command"
 }
 ```
+#### 执行重拍操作
+<mark>以下操作需要连接设备且初始化<mark>
+* data:
+    * record_id:原记录id
+* type:
+    * 当该字段为re_take_picture时,代表进行重拍操作
+<mark>注:后续得拍照动作参考消息回复为的type均为:re_take_picture,照片拍摄完成的消息依旧为photo_take<mark>
+##### 请求示例
+```python
+{
+    "data":{"record_id":1},
+    "type": "re_take_picture"
+}
+```
 ##### 未完待续.....

+ 9 - 0
python/mcu/BlueToothMode.py

@@ -22,6 +22,7 @@ class BlueToothMode(BaseClass,metaclass=SingletonType):
         else:
             self.init_flag = True
         self.msg_type = "blue_tooth"
+        self.bluetooth_exit = False
         self.remote_control_v2 = RemoteControlV2(self, websocket_manager)
         # 用于存储找到的目标设备的地址
         self.target_device_address = None
@@ -241,15 +242,23 @@ class BlueToothMode(BaseClass,metaclass=SingletonType):
         await self.scan_for_esp32()
         # 定期重新扫描以发现新设备
         while True:
+            print("正在扫描设备...")
+            if self.bluetooth_exit:
+                break
             if self.devices:
                 await asyncio.sleep(20)
             else:
                 await asyncio.sleep(3)
             await self.scan_for_esp32()
+        print("蓝牙断开连接,已释放")
     def run(self):
         self.print_error("开启蓝牙扫描")
         asyncio.run(self.main_func())
 
+    def clearMyInstance(self):
+        print("清除蓝牙实例...")
+        SingletonType.clear_instance()
+
     def write_cmd(self, address, data: list):
         buf = []
         buf.extend([0x55, 0x55, (0xFF & len(data))])

+ 22 - 8
python/mcu/DeviceControl.py

@@ -41,6 +41,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
         self.camera_motor_value = 0
         self.init_state = False
         self.port_name = ""
+        self.mcu_exit = False
         self.t_n = 0
         self.serial_ins = None
         self.connected_ports_dict = {}  # 已连接的ports
@@ -163,6 +164,10 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                 code=1, msg="mcu设备未连接,请先连接设备", device_status=0
             )
             return False
+        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 开始循环~")
@@ -250,7 +255,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             )
             self.add_send_data_queue(buf)
 
-    async def get_deviation(self):
+    async def getDeviationInfo(self):
         await asyncio.sleep(0.01)
         # 发送获取偏移量
         data = [self.command["get_deviation"], 1]
@@ -869,7 +874,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                 1,
                 msg="串口:{} 被占用,或无法识别".format(port_name).format(port_name),
                 data=message_data,
-                device_status=3,
+                device_status=-1,
             )
             print("串口:{} 被占用".format(port_name))
             return
@@ -939,7 +944,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             self.sendSocketMessage(
                 msg="MCU连接成功", data=message_data, device_status=2
             )
-            time.sleep(1)
+            time.sleep(2)
             loop = asyncio.get_event_loop()
             loop.create_task(self.initDevice(), name="init_mcu")
             # async def getBaseInfo():
@@ -960,7 +965,8 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
         # 正常跳过;记录为其他列表
         # 不正常进行尝试连接
         # 连接不上,记录为其他列表
-
+    def clearMyInstance(self):
+        SingletonType.clear_instance()
     def to_connect_com(self, port_name):
         # 关闭串口
         print("to_connect_com", port_name)
@@ -1361,11 +1367,17 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
         if config_list:
             for idx, item in enumerate(config_list):
                 is_take_picture = item["take_picture"]
+                action_id = item["id"]
                 if is_take_picture:
                     image_counts += 1
                     # 批量插入
                     image_deal_mode = 0 if action_info == "执行左脚程序" else 1
-                    insert_photo_records(image_deal_mode=image_deal_mode, goods_art_no=goods_art_no, image_index=idx)
+                    insert_photo_records(
+                        image_deal_mode=image_deal_mode,
+                        goods_art_no=goods_art_no,
+                        image_index=idx,
+                        action_id=action_id,
+                    )
             total_len = len(config_list)
             self.action_state = 1
             self.msg_type = "image_process"
@@ -1441,7 +1453,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             self.msg_type = "mcu"
             self.controlDevice("laser_position", 1)
 
-    async def run_mcu_config_single(self, config_info, goods_art_no):
+    async def run_mcu_config_single(self, config_info, goods_art_no,msg_type="run_mcu_single_finish",image_index=-1):
         '''独立拍照  仅作测试用'''
         if self.checkDevice() == False:
             return
@@ -1449,7 +1461,6 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             self.action_state = 1
             self.msg_type = "mcu"
             await asyncio.sleep(0.1)
-            image_index = -1
             program_item = ProgramItem(
                     websocket_manager=self.websocket_manager,
                     action_data=config_info,
@@ -1482,7 +1493,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                     )
             self.msg_type = "mcu"
             self.action_state = 2
-            self.msg_type = "run_mcu_single_finish"
+            self.msg_type = msg_type
             self.sendSocketMessage(
                     code=0,
                     msg=f"执行完成",
@@ -1504,6 +1515,8 @@ async def checkMcuConnection(device_ctrl: DeviceControl):
     """实时检测串口是否连接"""
     while True:
         await asyncio.sleep(0.5)
+        if device_ctrl.mcu_exit:
+            break
         ports_dict = device_ctrl.scan_serial_port()
         device_ctrl.temp_ports_dict = ports_dict
         if not ports_dict:
@@ -1526,3 +1539,4 @@ async def checkMcuConnection(device_ctrl: DeviceControl):
                         device_ctrl.add_port_by_linkage(_port_name)
                     except BaseException as e:
                         print("串口不存在{} {}".format(_port_name, e))
+    print("MCU断开连接,已释放")

+ 4 - 4
python/mcu/McuDeviationSet.py

@@ -20,7 +20,8 @@ class McuDeviationSet:
         # self.show()
         self.last_value = defaultdict(float)
         self.mcu_debug = McuDebug(mcu, is_debug=True, is_deviation=False)
-        self.get_mcu_deviation()
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.get_mcu_deviation(), name="get_mcu_deviation")
         # # 运动到设定位
         # QTimer.singleShot(2500, self.init_pos)
 
@@ -233,9 +234,8 @@ class McuDeviationSet:
     def _to_init_all(self, *args):
         self.mcu.to_init_device_origin_point(device_name="mcu", is_force=True)
 
-    def get_mcu_deviation(self):
-        loop = asyncio.get_event_loop()
-        loop.create_task(self.mcu.get_deviation(), name="get_mcu_deviation")
+    async def get_mcu_deviation(self):
+        await self.mcu.getDeviationInfo()
 
     def get_mcu_deviation_info(self, data):
         if "_type" not in data:

+ 4 - 4
python/mcu/ProgramItem.py

@@ -23,6 +23,7 @@ class ProgramItem(BaseClass):
         self.watch_dog = FileEventHandler()
         self.watch_dog.goods_art_no = goods_art_no
         self.watch_dog.image_index = image_index
+        self.watch_dog.mcu = mcu
         self.watch_dog.start_observer(captrure_folder_path)
         print("21 =========ProgramItem=======action_data=====")
         print(action_data)
@@ -274,10 +275,9 @@ class ProgramItem(BaseClass):
         # print("{} 拍照时间:{}".format(self.action_name, time.time() - start_time))
         print("{}-{}已完成".format(self.mode_type, self.action_name))
 
-        if True:
-            if self.after_delay_time:
-                print("拍照后延时:{}".format(self.after_delay_time))
-                time.sleep(self.after_delay_time)
+        if self.after_delay_time:
+            print("拍照后延时:{}".format(self.after_delay_time))
+            time.sleep(self.after_delay_time)
         return True
 
     def rephotograph_one_pic(self, *args):

+ 4 - 1
python/mcu/RemoteControlV2.py

@@ -263,7 +263,10 @@ class RemoteControlV2(BaseClass):
             image_index = record.image_index + 1
             self.photo_take_state = 1
             insert_photo_records(
-                record.image_deal_mode, record.goods_art_no, image_index
+                record.image_deal_mode,
+                record.goods_art_no,
+                image_index,
+                record.action_id,
             )
             print("开始单拍1-插入数据")
             capture_one = DigiCam()

+ 46 - 13
python/mcu/capture/module_watch_dog.py

@@ -6,8 +6,10 @@ import os
 from utils.utils_func import get_folder, check_path
 import datetime
 from utils.SingletonType import SingletonType
-from databases import CRUD,SqlQuery,PhotoRecord
 from databases import CRUD, SqlQuery, PhotoRecord
+import asyncio
+
+
 def updateImageRaw(time_str, image_path, goods_art_no, image_index):
     session = SqlQuery()
     crud = CRUD(PhotoRecord)
@@ -43,6 +45,7 @@ class FileEventHandler(FileSystemEventHandler, metaclass=SingletonType):
         #     self.init_flag = True
         self.goods_art_no = None
         self.image_index = -1
+        self.mcu = None
         super().__init__()
         # self.window = window
         FileSystemEventHandler.__init__(self)
@@ -52,7 +55,7 @@ class FileEventHandler(FileSystemEventHandler, metaclass=SingletonType):
         self.observer = None
         self.last_create_time = datetime.datetime.now()
 
-    def start_observer(self,path):
+    def start_observer(self, path):
         if self.observer != None:
             return
         print("图片保存目录:", path)
@@ -62,7 +65,9 @@ class FileEventHandler(FileSystemEventHandler, metaclass=SingletonType):
 
         watch_path = self.check_and_get_real_dir(path)
         if watch_path:
-            self.observer.schedule(self, watch_path, recursive=True)  # recursive 遍历目录
+            self.observer.schedule(
+                self, watch_path, recursive=True
+            )  # recursive 遍历目录
             self.observer.start()
             print("开启开门狗目录监听")
         else:
@@ -73,7 +78,7 @@ class FileEventHandler(FileSystemEventHandler, metaclass=SingletonType):
         逻辑:
         1、检查当前路径如包含Originals 则定位到对应Originals的父级目录
         2、如没有Originals,则认定为一个普通目录(且子文件夹没有Originals),否则定位到父级目录
-        
+
         """
         if not os.path.exists(watch_path):
             return None
@@ -91,9 +96,11 @@ class FileEventHandler(FileSystemEventHandler, metaclass=SingletonType):
         year = now.year
         month = now.month
         day = now.day
-        path = r"{root_path}\Originals\{year}\{month}\{day}".format(root_path=root_path, year=year, month=month, day=day)
+        path = r"{root_path}\Originals\{year}\{month}\{day}".format(
+            root_path=root_path, year=year, month=month, day=day
+        )
         check_path(path)
-        print("watch_path:",path)
+        print("watch_path:", path)
         return path
 
     def on_moved(self, event):
@@ -104,10 +111,24 @@ class FileEventHandler(FileSystemEventHandler, metaclass=SingletonType):
         #     if os.path.split(event.dest_path)[0] == settings.PhotoOutputDir:
         #         print("1111111")
         pass
+
     def updatePhotoRecord(self):
         session = SqlQuery()
         crud = CRUD(PhotoRecord)
         crud.read()
+
+    async def sendCallback(self, file_path):
+        await asyncio.sleep(0.01)
+        self.mcu.msg_type = "run_mcu_single"
+        print("货号不存在,监听不写入", self.mcu.msg_type)
+        self.mcu.sendSocketMessage(
+            code=0,
+            msg="主图测试拍摄完成",
+            device_status=2,
+            data={"file_path": file_path.replace("\\", "/")},
+        )
+        self.msg_type = "mcu"
+
     def on_created(self, event):
         if not event.is_directory:
             file_path = event.src_path
@@ -119,17 +140,27 @@ class FileEventHandler(FileSystemEventHandler, metaclass=SingletonType):
             try:
                 take_time = time.time()
                 self.send_log("获取文件file_path:{}".format(file_path))
-                create_time = datetime.datetime.fromtimestamp(os.path.getctime(file_path))
+                create_time = datetime.datetime.fromtimestamp(
+                    os.path.getctime(file_path)
+                )
                 # print("获取文件create_time:{}".format(create_time))
-                self.get_photo_info(raw_path=file_path, create_time=create_time, take_time=take_time)
+                self.get_photo_info(
+                    raw_path=file_path, create_time=create_time, take_time=take_time
+                )
                 self.send_log("获取文件create_time:{}".format(create_time))
-                if self.goods_art_no == None:
-                    print("货号不存在,监听不写入")
-                    return
                 if file_path == None:
                     print("file_path不存在,监听不写入")
                     return
-                updateImageRaw(create_time, file_path, self.goods_art_no,self.image_index)
+                if self.goods_art_no == None:
+                    # print("货号不存在,监听不写入")
+                    loop = asyncio.new_event_loop()
+                    asyncio.set_event_loop(loop)
+                    loop.run_until_complete(self.sendCallback(file_path))
+                    loop.close()
+                    return
+                updateImageRaw(
+                    create_time, file_path, self.goods_art_no, self.image_index
+                )
             except BaseException as e:
                 print("获取文件create_time失败", e)
                 self.send_log("获取文件处理失败{}".format(e))
@@ -155,7 +186,9 @@ class FileEventHandler(FileSystemEventHandler, metaclass=SingletonType):
                     # 判断是否为文件
                     if os.path.isfile(file_path):
                         # 获取文件创建时间
-                        create_time = datetime.datetime.fromtimestamp(os.path.getctime(file_path))
+                        create_time = datetime.datetime.fromtimestamp(
+                            os.path.getctime(file_path)
+                        )
                         # 更新最早时间
                         if create_time > self.last_create_time:
                             self.last_create_time = create_time

+ 1 - 0
python/model/device_config.py

@@ -10,6 +10,7 @@ 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")
     camera_height: Optional[int] = Field(default=None, description="相机高度;步长1;最小0;最大400")

+ 4 - 1
python/model/photo_record.py

@@ -1,10 +1,13 @@
 from sqlmodel import SQLModel, Field
 from datetime import datetime
 from typing import Optional
-from settings import TIME_ZONE
+import pytz
+
+TIME_ZONE = pytz.timezone("Asia/Shanghai")
 class PhotoRecord(SQLModel, table=True):
     __tablename__ = "photo_record"
     id: Optional[int] = Field(default=None, primary_key=True, index=True)
+    action_id: Optional[int] = Field(default=None)
     goods_art_no: Optional[str] = Field(max_length=128, nullable=False)
     image_path: Optional[str] = Field(default=None)
     image_index: Optional[int] = Field(default=None)

+ 14 - 0
python/models.py

@@ -23,6 +23,13 @@ class ModelGetDeviceConfigDetail(BaseModel):
     )
 
 
+class ModelGetDeviceConfigDetailQuery(BaseModel):
+    """获取可执行程序命令列表"""
+
+    mode_type: Optional[str] = Field(default="执行左脚程序", index=True, max_length=128)
+    action_name: Optional[str] = Field(default="", index=True, max_length=128)
+
+
 class SaveDeviceConfig(BaseModel):
     """获取可执行程序命令列表"""
 
@@ -31,6 +38,7 @@ class SaveDeviceConfig(BaseModel):
     action_name: Optional[str] = Field(default="", index=True, max_length=128)
     action_index: Optional[int] = Field(default=None)
     action_status: Optional[bool] = Field(default=None)
+    is_system: Optional[bool] = Field(default=False)
     camera_height: Optional[int] = Field(default=None)
     camera_angle: Optional[float] = Field(default=None)
     number_focus: Optional[int] = Field(default=None)
@@ -64,6 +72,12 @@ class TemplateItem(BaseModel):
     template_local_classes: str = Field(description="模板名称")
 
 
+class MaineImageTest(BaseModel):
+    """模板项"""
+
+    file_path: str = Field(description="图片地址")
+
+
 class HandlerDetail(BaseModel):
     """获取可执行程序命令列表"""
 

+ 102 - 0
python/service/OnePicTest.py

@@ -0,0 +1,102 @@
+import threading
+import time
+import os
+import settings
+
+from service.remove_bg_ali import RemoveBgALi
+from service.generate_main_image.grenerate_main_image_test import (
+    GeneratePic,
+)
+from service.generate_main_image.image_deal_base_func import *
+from functools import partial
+import io
+from PIL import Image
+from .base import check_path, get_date_time_original
+from middleware import UnicornException
+
+class OnePicTest():
+    # is_end = Signal()
+
+    def __init__(self,  pic_path="", is_auto_closed=False):
+        # super().__init__()
+        # 加载默认配置
+        # self.ui = Ui_Form()
+        # self.ui.setupUi(self)
+        self.pic_path = pic_path
+        self.is_auto_closed = False
+        # self.init()
+        # self.show()
+        # if pic_path:
+        #     self.run()
+
+    def call_back_deal_image(self, data):
+        # 处理后回调
+        print(data)
+        if data["code"] == 0:
+            return data
+        else:
+            if data["message"]:
+                raise UnicornException(data["message"])
+
+    def deal_image(self, image_path):
+        # 处理加工图片
+        return_data = {"code": 99, "message": "", "data": {}}
+
+        # =============清空temp文件夹================
+        check_path("temp")
+        check_path("temp\pic_test")
+        root = r"{}\temp\pic_test".format(os.getcwd())
+        for file_name in os.listdir(root):
+            path = "{}\{}".format(root, file_name)
+            if os.path.isfile(path):
+                os.remove(path)
+
+        # ==============抠图处理=====================
+        remove_pic_ins = RemoveBgALi()
+        file = os.path.split(image_path)[1]
+        cut_image_path = r"{}\temp\pic_test\{}.png".format(
+            os.getcwd(), os.path.splitext(file)[0]
+        )
+        remove_pic_ins.get_image_cut(file_path=image_path, out_file_path=cut_image_path)
+
+        # ==============生成主图====================
+        main_out_path = r"{}\temp\pic_test\{}-主图.jpg".format(
+            os.getcwd(), os.path.splitext(file)[0]
+        )
+        print("image_path", image_path)
+        print("cut_image_path", cut_image_path)
+        print("main_out_path", main_out_path)
+        try:
+            GeneratePic().run(
+                image_path=image_path,
+                cut_image_path=cut_image_path,
+                out_path=main_out_path,
+                image_deal_mode=1,
+                resize_mode=1,
+                out_pic_size=1024,
+                is_logo=False,
+            )
+        except:
+            raise UnicornException("处理失败,请重试")
+
+        # return_data["code"] = 0
+        # return_data["data"]["image_path"] = image_path
+        # return_data["data"]["image_cutout_path"] = cut_image_path
+        # return_data["data"]["image_main_path"] = main_out_path
+        return main_out_path
+
+    def HandlerMainImage(self):
+        # 先做整体校验
+        if not os.path.exists(self.pic_path):
+            raise UnicornException("图片不存在")
+        image_pth = Image.open(self.pic_path)
+        if image_pth.mode != "RGB":
+            raise UnicornException("抠图图片不能是PNG")
+        return_data = self.deal_image(self.pic_path)
+        # if return_data["message"]:
+        #     raise UnicornException(return_data["message"])
+        return return_data.replace("\\", "/")
+
+if __name__ == "__main__":
+    pic = OnePicTest(pic_path="C:/Users/15001/Desktop/mmexport1739929178317.jpg")
+    pic.HandlerMainImage()

+ 58 - 2
python/sockets/message_handler.py

@@ -34,11 +34,13 @@ async def handlerSend(
             await manager.broadcast(dictMsg)
         case "connect_mcu":
             device_ctrl = DeviceControl(websocket_manager=manager)
+            device_ctrl.mcu_exit = False
             # if device_ctrl.serial_ins.check_connect():
             #     print("未连接")
             loop.create_task(checkMcuConnection(device_ctrl), name="mcu")
         case "connect_bluetooth":
             blue_tooth = BlueToothMode(websocket_manager=manager)
+            blue_tooth.bluetooth_exit = False
             # await  blue_tooth.main_func()
             print("连接蓝牙信息")
             loop.create_task(blue_tooth.main_func(), name="blue_tooth")
@@ -120,7 +122,7 @@ async def handlerSend(
         case "run_mcu_single":
             device_ctrl = DeviceControl(websocket_manager=manager)
             loop.create_task(
-                device_ctrl.run_mcu_config_single(data, None),
+                device_ctrl.run_mcu_config_single(data, None, "run_mcu_single"),
                 name="run_mcu_single",
             )
         case "handler_take_picture":
@@ -129,10 +131,64 @@ async def handlerSend(
                 blue_tooth.remote_control_v2.handlerTakePhoto(),
                 name="run_mcu_config",
             )
+        case "re_take_picture":#重拍
+            try:
+                # 判断拍照软件是否初始化
+                digicam = DigiCam()
+                camera_is_connect = digicam.checkCameraConnect()
+                if camera_is_connect is not True:
+                    data = manager.jsonMessage(
+                        code=1, msg="相机未连接,请检查", msg_type=msg_type
+                    )
+                    await manager.send_personal_message(data, websocket)
+                    return
+                digicam.getCaptureFolderPath()
+            except:
+                data = manager.jsonMessage(
+                    code=1, msg="digicam未初始化,请检查", msg_type=msg_type
+                )
+                await manager.send_personal_message(data, websocket)
+                return
+            msg_type = "re_take_picture"
+            record_id = data.get("record_id")
+            session = SqlQuery()
+            photoRecord = CRUD(PhotoRecord)
+            goods_art_record = photoRecord.read(session, conditions={"id": record_id})
+            if goods_art_record == None:
+                data = manager.jsonMessage(
+                    code=1,
+                    msg=f"记录不存在,请核实后重新操作~",
+                    msg_type=msg_type,
+                )
+                await manager.send_personal_message(data, websocket)
+                return
+            action_id = goods_art_record.action_id
+            goods_art_no = goods_art_record.goods_art_no
+            image_index = goods_art_record.image_index
+            crud = CRUD(DeviceConfig)
+            condtions = {"id": action_id}
+            device_action = crud.read(
+                session, conditions=condtions
+            )
+            if device_action == None:
+                # 判断是否有可用配置
+                data = manager.jsonMessage(code=1, msg="当前没有可用配置")
+                await manager.send_personal_message(data, websocket, msg_type=msg_type)
+                return
+            # 清除图片记录,执行重拍
+            reset_data = {"image_path": None}
+            photoRecord.update(session, record_id, **reset_data)
+            device_ctrl = DeviceControl(websocket_manager=manager)
+            loop.create_task(
+                device_ctrl.run_mcu_config_single(
+                    device_action.model_dump(), goods_art_no, msg_type=msg_type,image_index=image_index
+                ),
+                name="run_mcu_config_single",
+            )
         case "get_deviation":
             device_ctrl = DeviceControl(websocket_manager=manager)
             loop.create_task(
-                device_ctrl.get_deviation(),
+                device_ctrl.getDeviationInfo(),
                 name="get_deviation",
             )
         case "set_deviation":

+ 6 - 0
python/sockets/socket_server.py

@@ -26,14 +26,20 @@ async def websocket_endpoint(websocket: WebSocket):
                     socket_type = byteDats.get("type")
                     if socket_type == "websocket.disconnect":
                         device_ctrl.close_connect()
+                        device_ctrl.mcu_exit = True
+                        device_ctrl.clearMyInstance()
                         diviceList = blue_tooth.devices
                         if len(diviceList) == 0:
+                            blue_tooth.bluetooth_exit = True
+                            blue_tooth.clearMyInstance()
                             break
                         diviceAddress = list(diviceList.keys())[0]
                         if diviceAddress != "":
                             print(diviceList.get(diviceAddress))
                             diviceName = diviceList[diviceAddress]["name"]
                             blue_tooth.disconnect_device(diviceAddress, diviceName)
+                        blue_tooth.bluetooth_exit = True
+                        blue_tooth.clearMyInstance()
                         break
                     print("byteDats", byteDats)
                     await handlerSend(conn_manager, json.dumps(byteDats), websocket)

+ 4 - 1
python/utils/SingletonType.py

@@ -9,4 +9,7 @@ class SingletonType(type):
                     cls._instance = super(SingletonType, cls).__call__(*args, **kwargs)
         return cls._instance
 
-
+    @classmethod
+    def clear_instance(cls):
+        if hasattr(cls, "_instance"):
+            delattr(cls, "_instance")