OpenPLC Editor v4 架构概述

发布时间:2026/6/29 17:55:08
OpenPLC Editor v4 架构概述 OpenPLC Editor 是一款Electron React桌面 IDE支持使用 IEC 61131-3 语言结构化文本、梯形图、功能块图、指令表以及 Python 和 C 扩展进行 PLC 编程。该架构遵循端口与适配器六边形模式强制执行严格的依赖倒置前端从不直接导入后端或 Electron API——所有特定于平台的行为都通过带类型的端口接口传递使得相同的 UI 代码库可在 Electron 编辑器和 Web 应用程序中复用。本页提供了系统的结构蓝图其分层、层间关系以及支配其交互的规则。系统拓扑该应用程序作为多进程 Electron 应用程序运行具有三个独立的进程边界。主进程Node.js拥有文件系统访问、硬件通信、编译器编排和模拟器的控制权。渲染进程Chromium承载 React UI、Zustand 状态存储以及所有面向用户的逻辑。预加载脚本通过 Electron 的contextBridge桥接两者将window.bridge.*作为唯一的 IPC 接口暴露出来。中间件层位于渲染进程内部但充当架构的隔离膜——它定义了端口接口与平台无关的契约并提供适配器实现将这些契约转化为 IPC 调用。关键的架构不变量是依赖方向导入始终指向内部。端口接口仅依赖于共享类型和工具。适配器依赖于端口反之不然。组件通过 Provider 依赖于端口从不直接依赖于适配器。这种倒置确保了替换整个平台层——从 Electron IPC 切换到 HTTP/WebRTC——无需对 UI 或 Store 进行任何更改。层级分解src/目录被组织成六个架构层每层具有不同的职责和受限的允许依赖集。验证脚本npm run validate:arch在构建时通过扫描所有 TypeScript 导入并标记违规来强制执行这些规则。层级目录职责关键允许依赖Mainsrc/main/Electron 主进程、窗口生命周期、IPC 处理程序注册Node.js API、后端模块Backendsrc/backend/与平台无关的业务逻辑XML 生成、项目解析、模拟器、编译器编排ports、utils、typesPortssrc/middleware/shared/ports/抽象接口契约14 个端口接口 领域类型utils、ports自引用Providersrc/middleware/shared/providers/用于依赖注入的 React Contextports、utilsAdapterssrc/middleware/adapters/具体的端口实现编辑器IPCWebHTTP/WebRTCports、provider、utils、backend-shared、storeFrontendsrc/frontend/React UI组件、Store、Hooks、Services、Data、Utilsports、provider、store、hooks、services、data、utils、assets架构验证脚本根据目录路径将每个源文件映射到一个层级然后根据允许依赖表检查每个导入。如果frontend/store/中的文件导入了backend/editor/验证将会失败因为store只能依赖于ports、provider、store、utils和assets。详细依赖图下表再现了验证脚本强制执行的完整层级依赖规则。每行指定了给定层级允许导入的其他层级层级允许的导入assetsutils、datautilsutils、ports、data、assetsdataports、utils、data、assetstypesstore、utilsportsutils、portsproviderports、utilsadaptersports、provider、utils、backend-shared、backend-web、store、assetsadapter-componentsports、provider、store、hooks、services、components、data、utils、assets、adapters、adapter-componentsbackend-sharedports、utils、typesbackend-webports、utils、types、backend-sharedstoreports、provider、store、utils、assetsservicesports、provider、store、services、utils、assetshooksports、provider、store、hooks、services、utils、assetscomponentsports、provider、store、hooks、services、components、data、utils、assets来源validate.ts端口与适配器平台抽象src/middleware/shared/ports/中的端口接口定义了与平台无关的契约将 UI 与特定平台的实现解耦。每个端口都是一个 TypeScript 接口指定了方法签名以及参数/结果类型。UI 仅通过PlatformProviderReact Context 及其便捷 Hooks 来消费端口。// 组件用法 —— 没有 Electron 导入没有 HTTP 导入 import { useCompiler, useCapabilities } from root/middleware/shared/providers function MyComponent() { const compiler useCompiler() const capabilities useCapabilities() if (capabilities.hasLocalSerialPorts) { /* 原生串口 UI */ } await compiler.compileProgram(args, onProgress) }端口接口目录系统定义了 14 个主要端口接口以及用于功能切换的PlatformCapabilities。每个端口封装了一个独立的领域关注点端口接口文件职责CompilerPortcompiler-port.tsPLC 编译管道XML → ST → C → 二进制及进度流RuntimePortruntime-port.ts远程 PLC 运行时控制登录、启动/停止、状态轮询DebuggerPortdebugger-port.ts调试协议读写变量、MD5 校验SimulatorPortsimulator-port.ts内置 AVR 模拟器生命周期ProjectPortproject-port.ts项目 CRUD创建、打开、保存、POU 管理DevicePortdevice-port.ts开发板发现、串口枚举、引脚映射OrchestratorPortorchestrator-port.ts设备集群管理仅限 WebSystemPortsystem-port.ts平台服务Store 访问、日志记录、外部链接WindowPortwindow-port.ts原生窗口管理最小化、最大化、关闭AcceleratorPortaccelerator-port.ts键盘快捷键注册ThemePorttheme-port.ts主题检测与切换暗色/亮色/系统VersionControlPortversion-control-port.tsGit 操作分支、提交、差异对比EsiPortesi-port.tsEtherCAT ESI 仓库管理NavigationPortnavigation-port.ts基于 URL 的导航抽象AIPortai-port.tsAI 辅助编码可选仅限 Web双适配器策略端口架构支持从单一 UI 代码库实现两种完全不同的部署目标编辑器适配器src/middleware/adapters/editor/——委托给预加载脚本暴露的window.bridge.*方法该方法调用ipcRenderer.invoke()与主进程通信Web 适配器独立仓库——委托给 HTTP REST 端点和 WebRTC 对等连接用于云端编译、运行时代理和实时调试CompilerPort清楚地说明了适配器的分歧编辑器适配器调用window.bridge.runCompileProgram()后者通过 IPC 调用本地二进制文件xml2st、iec2c、arduino-cliWeb 适配器调用compile.getedge.me上的callGenerateSt()/callCompileSt()HTTP 端点。UI 只知道compiler.compileProgram(args, onProgress)—— 它完全不知道哪个后端在处理请求。平台能力功能切换PlatformCapabilities对象充当功能标志系统允许 UI 根据部署目标有条件地渲染或启用功能。组件不再基于平台检测字符串进行分支判断而是检查布尔类型的能力标志能力编辑器WebisNativeApplication✅❌hasNativeFileDialogs✅❌hasAuthentication❌✅hasLocalSerialPorts✅❌hasOrchestratorDevices❌✅hasWebRTC❌✅hasInProcessSimulator✅✅hasLocalFilesystem✅❌hasVersionControl❌✅hasAIAssistant❌✅hasEthercat✅❌连接发生在应用根组件App.tsx中PlatformProvider接收完整的editorPorts对象——所有适配器已预连接到其 IPC 委托import { editorPorts } from ./middleware/editor-platform PlatformProvider ports{editorPorts} AppLayout{path ? StartScreen / : WorkspaceScreen /}/AppLayout /PlatformProvider组合根与启动序列应用程序的组合根是App.tsx它在首次渲染之前执行三个关键的初始化步骤Store 初始化—— 系统功能块库在模块评估时通过openPLCStoreBase.getState().libraryActions.setSystemLibraries(...)加载到 Zustand Store 中确保在任何组件挂载之前所有库数据可用Store 到适配器的同步—— 两个useEffectHook 将运行时 IP 地址和项目路径从 Zustand Store 同步到平台适配器的模块作用域变量_runtimeIpAddress、_projectPath将响应式 Store 状态桥接到命令式适配器层平台连接——PlatformProvider使用editorPorts对象包裹整个组件树使所有端口接口通过 React Context 可用导航模型是标签页驱动而非 URL 驱动。这里没有 React Router。App.tsx渲染StartScreen未加载项目或WorkspaceScreen项目已打开这完全由 Store 中的project.meta.path值决定。在工作区内打开一个 POU 会在 Store 中创建一个标签项点击标签会设置活动的EditorModel——这是一个可辨识联合体决定了渲染哪个编辑器组件。主进程架构主进程充当基础设施层它拥有文件系统访问、硬件通信、编译器编排和网络服务的控制权。它不了解 React 或 UI 状态。其入口点src/main/main.ts创建BrowserWindow配置特定平台的标题栏样式在 macOS/Windows 上隐藏在 Linux 上为默认管理启动画面生命周期并实例化服务对象。MainProcessBridge类src/main/modules/ipc/main.ts是核心的 IPC 调度器。它实现了 50 多个ipcMain.handle()注册将渲染进程的请求转换为后端服务调用。关键的内部模块包括模块路径用途CompilerModulebackend/editor/compiler/使用本地二进制文件编排 XML → ST → C → 二进制管道HardwareModulebackend/editor/hardware/开发板检测、串口枚举SimulatorModulebackend/shared/simulator/带虚拟串口的 avr8js ATmega2560 模拟器ProjectServicebackend/editor/services/project-service/文件系统项目 I/O读、写、创建PouServicebackend/editor/services/pou-service/POU 文件管理ModbusTcpClientbackend/editor/modbus/Modbus TCP 调试协议客户端ModbusRtuClientbackend/editor/modbus/Modbus RTU 调试协议客户端WebSocketDebugClientbackend/editor/websocket/WebSocket 调试协议客户端ESIServicebackend/editor/ethercat/EtherCAT 设备描述仓库预加载脚本src/main/modules/preload/preload.ts通过 Electron 的contextBridge.exposeInMainWorld(bridge, rendererProcessBridge)暴露渲染进程桥接使window.bridge.*成为渲染进程可用的唯一 IPC 接口。状态管理Zustand Store 组合应用状态存在于由19 个切片组成的单个 Zustand Store 中每个切片遵循一致的三文件模式types.ts状态形状 Action 签名、slice.ts使用 Immer 的produce()进行不可变更新的实现和index.ts重新导出。Store 由createOpenPLCStore()创建它将所有切片工厂合并到带有subscribeWithSelector中间件的单个create()调用中。选择器 Hook 通过auto-zustand-selectors-hook自动生成。切片目录核心职责projectslices/project/PLC 项目结构POU、数据类型、配置、服务器、设备deviceslices/device/开发板配置、引脚映射、运行时连接状态editorslices/editor/编辑器模型可辨识联合体文本、图形、设备、服务器tabsslices/tabs/打开的文件标签、活动标签选择workspaceslices/workspace/UI 视口状态、调试变量值ladderslices/ladder/每个 POU 的梯形图逻辑fbdslices/fbd/每个 POU 的功能块图流图consoleslices/console/日志输出聚合libraryslices/library/系统 用户功能块库fileslices/file/文件保存状态、脏跟踪aislices/ai/AI 助手状态仅限 Webversion-controlslices/version-control/Git 分支、提交、待处理更改webrtcslices/webrtc/WebRTC 对等连接仅限 Web所有切片强制执行的约定Action 分组在*Actions命名空间下例如projectActions、deviceActions复杂 Action 返回{ ok: boolean; message?: string }响应对象状态从不直接修改——始终通过 Immer 的produce()React 外部的直接状态访问openPLCStoreBase.getState()组件架构原子设计组件树遵循原子设计方法论组织成五个组合复杂度层级src/frontend/components/ ├── _atoms/ ├── _molecules/ # 分子级组合模式菜单栏、模态框、变量表、标签页、项目树 ├── _organisms/ # 有机级复杂区块资源管理器、控制台、调试器、导航、面板 ├── _features/ # 上下文特定的功能包 │ ├── [app]/ # 应用级加载遮罩、Toast 通知 │ ├── [start]/ # 启动屏新建项目模态框、最近项目 │ └── [workspace]/ # 工作区Monaco 编辑器、图形编辑器 (LD/FBD/SFC)、设备编辑器 └── _templates/ # 布局包装器app-layout、workspace-layout、编辑器布局_features/下的功能切片组织聚合了所有工作区编辑器实现用于文本语言ST、IL、Python、C的 Monaco 代码编辑器、基于 DnD Kit 的梯形图编辑器、基于 xyflow/react 的功能块图和 SFC 编辑器以及设备/服务器配置编辑器。每个编辑器从editorStore 切片接收其作为可辨识联合体的模型这决定了渲染策略避免了条件判断代码的臃肿。后端共享与平台无关的逻辑src/backend/shared/目录包含独立于 Electron 和 HTTP 传输的业务逻辑。该层可以被任何适配器消费——编辑器的 IPC 适配器、Web 的 HTTP 适配器甚至 CLI 工具。关键模块包括模块用途simulator/avr8js ATmega2560 模拟器、虚拟串口、Modbus RTU 客户端ethercat/ESI 解析器、设备匹配器、EtherCAT 配置生成和验证utils/PLC/POU 预处理注释包装、Python→ST 桩代码、C 校验utils/modbus/Modbus 地址计算和数据类型转换utils/cpp/从 POU 定义生成 C 代码这种分离在架构上意义重大因为backend-shared仅依赖于ports、utils和types所以它在没有 Electron 或网络模拟的情况下依然完全可测试。例如编译器适配器在将数据传递给 IPC 之前调用backend/shared/utils/PLC/preprocess-pous中的preprocessPous()——这是一个纯函数在没有任何副作用的情况下转换项目数据结构。数据流编译作为参考追踪编译管道提供了一个数据如何跨越所有架构层流动的具体示例。当用户点击“编译”时组件通过useCompiler()Hook 调用compiler.compileProgram(args, onProgress)——它对 IPC 或本地二进制文件一无所知编辑器适配器通过preprocessPous()backend-shared预处理 POU通过toIpcProjectData()将端口格式转换为 IPC 格式然后调用window.bridge.runCompileProgram()预加载桥接通过ipcRenderer.invoke()转发调用MainProcessBridge处理程序编排管道XML 生成 → ST 转译 → C 代码生成 → 二进制编译通过event.sender.send()流式传输回进度适配器解码消息推断编译阶段并将CompileProgressEvent对象转发给onProgress回调组件根据进度事件更新 UI架构保障该架构提供了三项可强制执行的保障以在代码库演进过程中维护系统完整性依赖方向倒置—— 端口层middleware/shared/ports/定义了 UI 所依赖的接口而适配器实现这些接口。这意味着 UI 核心从不耦合到任何特定平台。从 Electron 切换到纯浏览器部署只需要新的适配器实现——对组件、Store、Hook 或 Service 零更改。编译时类型安全—— 所有 IPC 通信都通过端口接口进行类型化。providers/types.ts中的PlatformPorts聚合类型确保每个适配器提供每个所需端口的完整实现。可选端口esi?、ai?被显式标记TypeScript 编译器在组合根强制执行它们的存在或缺失。运行时架构验证——npm run validate:arch命令扫描所有.ts/.tsx文件根据目录路径将每个文件映射到其架构层提取所有导入语句并验证每个导入是否遵守允许依赖表。违规会导致非零退出代码使该检查适合 CI 管道。验证脚本本身位于__architecture__/中这是一个具有零允许依赖的层——它永远不能成为它所验证的依赖图的一部分。