AOI/SPI等检测设备对接MES系统插件化架构设计

一、项目背景

该项目是一个基于Python和PyQt5开发的制造执行系统(MES)集成工具,为解决 AOI/SPI 等检测设备与不同客户 MES 系统对接难的问题,设计并开发了一套插件化中间件工具。该系统采用插件化架构设计,支持200+客户的定制化需求,已在多家制造企业的生产环境中稳定运行。

二、架构设计理念

2.1 核心设计原则

  1. 开闭原则:对扩展开放,对修改封闭
  2. 依赖倒置:高层模块不依赖低层模块,都依赖抽象
  3. 单一职责:每个插件专注于特定客户的业务逻辑
  4. 接口隔离:定义清晰的扩展点接口

2.2 架构分层

┌─────────────────────────────────────┐
│   Presentation Layer (PyQt5 UI)    │
├─────────────────────────────────────┤
│   Service Layer (Socket/HTTP)      │
├─────────────────────────────────────┤
│   Plugin Layer (Custom Engines)    │
├─────────────────────────────────────┤
│   Core Layer (BaseEngine)          │
├─────────────────────────────────────┤
│   Data Layer (Entity/VO)           │
└─────────────────────────────────────┘

三、插件化核心实现

3.1 抽象基类设计

class BaseEngine(metaclass=abc.ABCMeta):
    """插件基类,定义所有扩展点"""
    
    @abc.abstractmethod
    def send_data_to_mes(self, data_vo: DataVo, other_data: dict, other_param: Any) -> dict:
        """发送数据到MES"""
        pass
    
    @abc.abstractmethod
    def check_sn(self, other_vo: OtherVo, main_window: Any) -> dict:
        """条码校验"""
        pass
    
    # 其他扩展点...

3.2 插件目录结构

services/
├── default/              # 默认实现
│   └── main.py
├── custom/               # 客户定制实现
│   ├── customer1/            # xx定制
│   │   └── main.py
│   ├── customer2/    # xx定制
│   │   ├── main.py
│   │   ├── iot_data_collector.py
│   │   └── xmodule.py
│   ├── standard_en/      # 标准英文版
│   │   └── main.py
│   └── ...               # 200+客户目录

四、扩展点设计

4.1 核心扩展点

扩展点 功能描述 使用场景
send_data_to_mes 发送检测数据到MES 所有客户
check_sn 条码校验 需要条码验证的客户
send_device_status_to_mes 设备状态上报 需要状态监控的客户
get_sn_by_mes 从MES获取条码 需要MES下发条码的客户
custom_cron_function 定时任务 需要定时处理的客户

4.2 钩子函数

class BaseEngine:
    def init_main_window(self, main_window, other_vo: OtherVo):
        """初始化主窗口钩子"""
        pass
    
    def save_btn_on_window(self, main_window):
        """保存按钮钩子"""
        pass
    
    def combo_index_changed(self, combo_vo: ComboVo, main_window: Any):
        """下拉框改变钩子"""
        pass

五、配置驱动设计

5.1 UI配置

class Engine(ErrorMapEngine):
    # 表单配置
    form = {
        "username": {
            "ui_name": "用户名",
            "value": "admin"
        }
    }
    
    # 下拉框配置
    combo = {
        "device_type": {
            "item": ["AOI", "SPI"],
            "value": "AOI",
            "ui_name": "机台类型"
        }
    }
    
    # 按钮配置
    button = {
        "test_connect": {
            "ui_name": "测试连接"
        }
    }

5.2 配置持久化

# 配置文件位置:~/.config/mesconfig.json
{
    "form": {
        "username": {"value": "admin"}
    },
    "combo": {
        "device_type": {"value": "AOI"}
    },
    "app_setting": {
        "merge_send": false,
        "send_error_info": true
    }
}

六、数据流设计

6.1 数据实体

class PcbEntity:
    """整板实体"""
    def __init__(self, pcb_info: dict, board_data: dict):
        self.pcb_barcode = pcb_info.get("pcb_barcode")
        self.project_name = pcb_info.get("project_name")
        self.final_result = pcb_info.get("final_result")
    
    def yield_board_entity(self):
        """生成拼板实体"""
        for board_item in self.board_data:
            yield BoardEntity(board_item, self)

6.2 数据访问对象

class DataVo:
    """数据访问层"""
    def __init__(self, pcb_info: dict, board_data: dict, config_data: dict):
        self.pcb_entity = PcbEntity(pcb_info, board_data)
        self.all_config_json = ChainMap(
            config_data.get("combo", {}),
            config_data.get("form", {}),
            config_data.get("path", {})
        )
    
    def get_value_by_cons_key(self, cons_key):
        """获取配置参数"""
        return self.all_config_json.get(cons_key, {}).get("value")

七、通信架构

7.1 Socket服务

class SocketThread(QThread):
    def run(self):
        port = engine.mes_config_port
        self.sk.bind(("0.0.0.0", port))
        self.sk.listen()
        
        while True:
            conn, addr = self.sk.accept()
            data = conn.recv(4086)
            json_data = json.loads(data.decode("utf-8"))
            
            # 路由到对应的插件方法
            if "ReviewPath" in json_data:
                ret_data = self.send_data_to_mes(json_data)
            else:
                ret_data = self.call_other_function(json_data)

7.2 HTTP服务

class HttpService:
    def handle_request(self, request):
        """处理HTTP请求"""
        func_name = request.get("funcName")
        if func_name == "CheckBarcode":
            return engine.check_sn(...)
        elif func_name == "SendDeviceStatus":
            return engine.send_device_status_to_mes(...)

八、设计模式应用

8.1 模板方法模式

class ErrorMapEngine(BaseEngine):
    """错误码映射引擎"""
    ERROR_MAP = {
        "1": {"standard": "漏件", "custom_code": "1"},
        "2": {"standard": "错件", "custom_code": "2"}
    }
    
    def send_data_to_mes(self, data_vo, other_data, other_param):
        # 模板方法:定义算法骨架
        pcb_entity = data_vo.pcb_entity
        
        # 1. 数据转换
        transformed_data = self.transform_data(pcb_entity)
        
        # 2. 错误码映射
        mapped_data = self.map_error_code(transformed_data)
        
        # 3. 发送到MES
        return self.send_to_mes(mapped_data)

8.2 策略模式

# 不同客户使用不同的数据发送策略
class XXX1Engine(ErrorMapEngine):
    def send_data_to_mes(self, data_vo, other_data, other_param):
        # customer1特定的发送策略
        return self._send_xml_format(data_vo)

class XXX2Engine(ErrorMapEngine):
    def send_data_to_mes(self, data_vo, other_data, other_param):
        # customer2特定的发送策略
        return self._send_json_format(data_vo)

九、实际案例分析

9.1 客户1定制案例

class Engine(ErrorMapEngine):
    """客户1定制"""
    version = {
        "customer": ["客户1", "customer1"],
        "feature": ["相机扫码校验", "板式切换", "发送数据到MES"]
    }
    
    def check_project_by_barcode(self, other_vo: OtherVo, param: dict):
        """板式切换的条码校验"""
        pcb_barcode = param.get("barcode")
        resp = self._send_event(MesEvent.PART_RECEIVED, INSPECTOR)
        
        if resp.get('partForStation') == 'true':
            if resp.get('changeOver') == 'true':
                # 执行板式切换
                return self.switch_program(INSPECTOR, True)

9.2 客户2定制案例

class Engine(ErrorMapEngine):
    """客户2定制"""
    version = {
        "customer": ["客户2", "customer2"],
        "feature": ["从mes获取条码", "条码校验", "上传数据", "数采功能"]
    }
    
    def __init__(self):
        self.iot_data_collector = IoTDataCollector()
    
    def send_data_to_mes(self, data_vo, other_data, other_param):
        # 1. 发送检测数据
        result = self._send_detection_data(data_vo)
        
        # 2. 上传图片
        if self._should_upload_image():
            self._upload_images(data_vo)
        
        # 3. 发送IoT数据
        if self.iot_data_collector:
            self.iot_data_collector.send_data(data_vo)
        
        return result

十、优势与挑战

10.1 架构优势

  1. 高扩展性:新增客户只需创建新的插件,无需修改核心代码
  2. 易维护性:每个客户逻辑独立,互不影响
  3. 代码复用:通过继承机制,客户可以复用基础功能
  4. 灵活配置:通过配置文件驱动,减少硬编码

10.2 面临挑战

  1. 版本管理:200+插件的版本同步和兼容性管理
  2. 测试复杂度:每个插件都需要独立测试
  3. 性能优化:动态加载可能影响启动性能
  4. 文档维护:保持插件文档的更新

十一、最佳实践

11.1 插件开发规范

class Engine(ErrorMapEngine):
    version = {
        "customer": ["客户中文名", "客户英文名"],
        "version": "release v1.0.0.1",
        "device": "设备型号",
        "feature": ["功能列表"],
        "author": "开发者",
        "release": """
date: 2023-04-25 11:07  init
date: 2023-04-26 10:00  新增功能A
"""
    }

11.2 错误处理

def send_data_to_mes(self, data_vo, other_data, other_param):
    try:
        # 业务逻辑
        result = self._do_send(data_vo)
        return self.x_response("true", "发送成功")
    except Exception as e:
        self.log.error(f"发送失败: {e}")
        return self.x_response("false", f"发送失败: {str(e)}")

十二、总结

该MES配置器通过插件化架构设计,成功实现了对200+不同客户需求的支持。核心设计思想包括:

  1. 基于抽象的插件机制:通过BaseEngine定义扩展点,客户通过继承实现定制逻辑
  2. 配置驱动的UI生成:通过form、combo等配置动态生成界面
  3. 清晰的分层架构:表现层、服务层、插件层、核心层、数据层各司其职
  4. 灵活的扩展点设计:提供丰富的钩子函数和扩展方法
  5. 标准化的数据流:通过Entity和VO对象规范数据传递

这种架构设计不仅满足了当前的业务需求,也为未来的扩展和维护奠定了良好的基础。通过插件化,系统能够快速响应客户的定制化需求,同时保持核心代码的稳定性和可维护性。

posted @ 2026-03-27 11:03  孙昌恒  阅读(1)  评论(0)    收藏  举报