HarmonyOS 5.0 PC应用开发实战:构建跨设备协同的桌面生产力工具

HarmonyOS 5.0 PC应用开发实战:构建跨设备协同的桌面生产力工具

文章目录

在这里插入图片描述

每日一句正能量

当你感到压力大,觉得不顺心的时候,就去逛逛菜市场……当看到年迈的老人,严寒酷暑,一小堆菜,一小堆水果,只为挣那几块几十块钱的家用,你所有的矫情和懒惰都会掉在地上碎成渣!

前言

摘要: 本文基于HarmonyOS 5.0.0版本,详细介绍如何开发一款具备跨设备协同能力的PC端生产力应用。通过实战案例,深入讲解ArkUI-X在PC端的适配、分布式软总线技术、以及多窗口管理等核心能力,为开发者提供完整的PC应用开发解决方案。


一、HarmonyOS PC应用开发背景与机遇

1.1 生态发展现状

随着HarmonyOS NEXT的正式发布,鸿蒙生态正式进入"纯血"时代。华为在2024年开发者大会上宣布,HarmonyOS PC版将于2025年全面商用,这意味着PC端将成为鸿蒙生态的重要拼图。对于开发者而言,这是一个巨大的蓝海市场——目前Windows桌面应用市场饱和,而鸿蒙PC应用尚处于起步阶段,先发优势明显。

1.2 技术架构特点

HarmonyOS PC应用并非简单的移动端移植,而是基于统一生态的重新设计:

  • 统一内核:采用与移动端相同的OpenHarmony内核,确保API一致性
  • 多窗口架构:支持自由窗口、分屏、多开等PC典型交互模式
  • 键鼠优化:原生支持键盘快捷键、鼠标右键菜单、滚轮缩放等操作
  • 跨端协同:通过分布式技术实现手机、平板、PC间的无缝流转

二、实战项目:跨设备Markdown编辑器

2.1 项目需求分析

我们将开发一款名为**“HarmonyMark”**的Markdown编辑器,核心功能包括:

  1. 基础编辑:支持Markdown语法高亮、实时预览、文件管理
  2. PC特性:多标签页、快捷键支持、拖拽打开文件
  3. 跨端协同:手机拍照→PC插入、平板手绘→PC同步、文件跨设备流转

2.2 技术选型

模块技术方案说明
UI框架ArkUI-X支持PC端响应式布局
状态管理AppStorage + LocalStorage跨Ability数据共享
分布式能力DistributedObject + 软总线跨设备数据同步
文件处理@ohos.file.fsPC端文件系统访问
窗口管理@ohos.window多窗口生命周期管理

三、核心代码实现

3.1 工程架构搭建

首先创建Stage模型工程,配置PC设备支持:

// entry/src/main/module.json5{"module":{"name":"entry","type":"entry","deviceTypes":["default","tablet","2in1"// 支持PC/二合一设备],"abilities":[{"name":"EntryAbility","srcEntry":"./ets/entryability/EntryAbility.ets","description":"$string:EntryAbility_desc","icon":"$media:layered_image","label":"$string:EntryAbility_label","startWindowIcon":"$media:startIcon","startWindowBackground":"$color:start_window_background","exported":true,"skills":[{"entities":["entity.system.home"],"actions":["action.system.home"]}],// PC端多窗口配置"windowMode":"multi_window","maxWindowRatio":"4:3","minWindowRatio":"1:2"}]}}

3.2 PC端响应式布局

HarmonyOS PC应用需要适配多种窗口尺寸,采用栅格系统实现响应式:

// MainPage.etsimport{ BreakpointSystem, BreakpointType }from'../utils/BreakpointSystem'@Entry@Component struct MainPage {@StorageProp('currentBreakpoint') currentBreakpoint:string='sm'private breakpointSystem: BreakpointSystem =newBreakpointSystem()// 编辑器状态@State currentFile: FileItem |null=null@State isPreviewMode:boolean=false@State editorContent:string=''aboutToAppear(){// 注册断点监听this.breakpointSystem.register()// 初始化分布式数据this.initDistributedData()}aboutToDisappear(){this.breakpointSystem.unregister()}build(){GridRow({ columns:{ sm:4, md:8, lg:12},// 响应式列数 gutter:{ x:12, y:12}, breakpoints:{ value:['320vp','600vp','840vp'], reference: BreakpointsReference.WindowSize }}){// 左侧文件栏:lg显示,sm/md隐藏GridCol({ span:{ sm:0, md:2, lg:3}, offset:{ sm:0, md:0, lg:0}}){FileSidebar({onFileSelect:(file: FileItem)=>this.handleFileSelect(file)})}.backgroundColor('#f5f5f5').height('100%')// 中间编辑区GridCol({ span:{ sm:4, md:6, lg:this.isPreviewMode ?5:9}}){EditorPanel({ content: $editorContent,onContentChange:(val:string)=>this.handleContentChange(val)})}.padding(16)// 右侧预览区:仅lg且预览模式显示GridCol({ span:{ sm:0, md:0, lg:4}}){if(this.currentBreakpoint ==='lg'&&this.isPreviewMode){PreviewPanel({ markdown:this.editorContent })}}.backgroundColor('#fafafa')}.width('100%').height('100%').onBreakpointChange((breakpoint)=>{ AppStorage.setOrCreate('currentBreakpoint', breakpoint)})}// 处理文件选择privatehandleFileSelect(file: FileItem){this.currentFile = file // 读取文件内容 fs.readText(file.uri).then((content)=>{this.editorContent = content // 同步到分布式数据this.syncToDistributed(file.uri, content)})}// 内容变更自动保存privatehandleContentChange(content:string){this.editorContent = content if(this.currentFile){this.autoSave(this.currentFile.uri, content)}}}

3.3 分布式数据同步实现

核心功能:实现PC与手机间的实时内容同步:

// DistributedEditorManager.etsimport distributedObject from'@ohos.data.distributedDataObject'import distributedDeviceManager from'@ohos.distributedDeviceManager'classEditorData{ uri:string='' content:string='' lastModified:number=0 deviceId:string=''}exportclassDistributedEditorManager{private distributedObject: distributedObject.DistributedObject |null=nullprivate sessionId:string='harmonymark_editor_session'private deviceManager: distributedDeviceManager.DeviceManager |null=null// 创建分布式数据对象asynccreateDistributedObject(initialData: EditorData){try{this.distributedObject = distributedObject.create(getContext(this),this.sessionId, initialData )// 监听数据变更this.distributedObject.on('change',(sessionId, fields)=>{console.info(`Data changed from ${sessionId}: ${JSON.stringify(fields)}`)this.handleRemoteChange(fields)})// 绑定到本地awaitthis.distributedObject.setSessionId(this.sessionId)console.info('Distributed object created successfully')}catch(err){console.error('Failed to create distributed object:', err)}}// 同步数据到所有设备asyncsyncContent(uri:string, content:string){if(!this.distributedObject)returnconst updateData: EditorData ={ uri: uri, content: content, lastModified: Date.now(), deviceId:this.getLocalDeviceId()}// 更新分布式对象this.distributedObject.uri = updateData.uri this.distributedObject.content = updateData.content this.distributedObject.lastModified = updateData.lastModified this.distributedObject.deviceId = updateData.deviceId console.info('Content synced to distributed object')}// 处理远程数据变更privatehandleRemoteChange(fields:Array<string>){if(!this.distributedObject)return// 检查是否是其他设备的更新if(fields.includes('content')&&this.distributedObject.deviceId !==this.getLocalDeviceId()){const remoteContent =this.distributedObject.content const remoteUri =this.distributedObject.uri // 触发UI更新 AppStorage.setOrCreate('remoteContent', remoteContent) AppStorage.setOrCreate('remoteUri', remoteUri)// 显示协同提示this.showCollaborationNotification(remoteContent)}}// 获取在线设备列表asyncgetAvailableDevices():Promise<Array<distributedDeviceManager.DeviceBasicInfo>>{try{this.deviceManager = distributedDeviceManager.createDeviceManager(getContext(this).bundleName)returnthis.deviceManager.getAvailableDeviceListSync()}catch(err){console.error('Failed to get devices:', err)return[]}}privategetLocalDeviceId():string{returnthis.deviceManager?.getLocalDeviceNetworkId()||''}privateshowCollaborationNotification(content:string){// 实现协同提示UI promptAction.showToast({ message:'其他设备已更新内容', duration:2000})}}

3.4 PC端多窗口管理

实现类似VS Code的多窗口编辑体验:

// MultiWindowManager.etsimport window from'@ohos.window'exportclassMultiWindowManager{privatestatic instance: MultiWindowManager private windowMap: Map<string, window.Window>=newMap()private mainWindow: window.Window |null=nullstaticgetInstance(): MultiWindowManager {if(!MultiWindowManager.instance){ MultiWindowManager.instance =newMultiWindowManager()}return MultiWindowManager.instance }// 初始化主窗口asyncinitMainWindow(){this.mainWindow =await window.getLastWindow(getContext(this))awaitthis.setupWindowConfig(this.mainWindow,'main')}// 创建新窗口打开文件asyncopenNewWindow(fileUri:string, fileName:string):Promise<void>{try{// 创建子窗口const subWindow =await window.createSubWindow(getContext(this),`editor_${Date.now()}`)const windowId = subWindow.getWindowProperties().id.toString()// 配置窗口属性awaitthis.setupWindowConfig(subWindow,'sub')// 设置窗口内容await subWindow.setUIContent('pages/EditorWindow',(data)=>{// 传递参数 AppStorage.setOrCreate('windowFileUri', fileUri) AppStorage.setOrCreate('windowFileName', fileName)})// 显示窗口await subWindow.showWindow()// 移动到合适位置(级联窗口效果)awaitthis.cascadeWindow(subWindow)// 保存引用this.windowMap.set(windowId, subWindow)// 监听窗口关闭 subWindow.on('windowStageDestroy',()=>{this.windowMap.delete(windowId)})}catch(err){console.error('Failed to create sub window:', err)}}// 配置窗口属性privateasyncsetupWindowConfig(win: window.Window, type:'main'|'sub'){// 设置窗口大小范围await win.setWindowLimits({ minWidth: type ==='main'?800:600, minHeight: type ==='main'?600:400, maxWidth:3840, maxHeight:2160})if(type ==='sub'){// 子窗口默认大小await win.resize(1000,700)// 启用窗口拖拽调整大小await win.setWindowTouchable(true)}// PC端特定优化await win.setWindowDecorVisible(true)// 显示系统标题栏await win.setWindowBackgroundColor('#ffffff')}// 级联窗口布局privateasynccascadeWindow(win: window.Window){const offset =this.windowMap.size *30const display =await window.getLastWindow(getContext(this)).getWindowProperties().displayId // 基于主窗口位置偏移await win.moveWindowTo(100+ offset,100+ offset)}// 分屏模式支持asyncenterSplitScreenMode(){if(!this.mainWindow)returnawaitthis.mainWindow.setWindowMode(window.WindowMode.SPLIT_PRIMARY)}// 获取所有打开的窗口getAllWindows():Array<window.Window>{returnArray.from(this.windowMap.values())}// 关闭所有子窗口asynccloseAllSubWindows(){for(const[id, win]ofthis.windowMap){await win.destroyWindow()}this.windowMap.clear()}}

3.5 键盘快捷键系统

PC应用的核心体验,实现专业编辑器级快捷键:

// KeyboardShortcutManager.etsimport{ KeyCode }from'@kit.InputKit'interfaceShortcutConfig{ key: KeyCode modifiers:Array<'ctrl'|'shift'|'alt'>action:()=>void description:string}exportclassKeyboardShortcutManager{private shortcuts: Map<string, ShortcutConfig>=newMap()private isListening:boolean=false// 注册默认快捷键registerDefaultShortcuts(){this.register({ key: KeyCode.KEY_S, modifiers:['ctrl'],action:()=>this.saveFile(), description:'保存文件'})this.register({ key: KeyCode.KEY_N, modifiers:['ctrl'],action:()=>this.newFile(), description:'新建文件'})this.register({ key: KeyCode.KEY_O, modifiers:['ctrl'],action:()=>this.openFile(), description:'打开文件'})this.register({ key: KeyCode.KEY_Z, modifiers:['ctrl'],action:()=>this.undo(), description:'撤销'})this.register({ key: KeyCode.KEY_Z, modifiers:['ctrl','shift'],action:()=>this.redo(), description:'重做'})this.register({ key: KeyCode.KEY_B, modifiers:['ctrl'],action:()=>this.insertBold(), description:'粗体'})this.register({ key: KeyCode.KEY_P, modifiers:['ctrl','shift'],action:()=>this.togglePreview(), description:'切换预览'})// 开始监听this.startListening()}register(config: ShortcutConfig){const key =this.getShortcutKey(config)this.shortcuts.set(key, config)}privatestartListening(){if(this.isListening)return// 使用InputKit监听键盘事件 inputMonitor.on('key',(event)=>{if(event.type !=='keyDown')returnconst pressedKey =this.getShortcutKey({ key: event.keyCode, modifiers:this.getActiveModifiers(event)}as ShortcutConfig)const shortcut =this.shortcuts.get(pressedKey)if(shortcut){ event.stopPropagation() shortcut.action()console.info(`Shortcut triggered: ${shortcut.description}`)}})this.isListening =true}privategetShortcutKey(config: ShortcutConfig):string{const mods = config.modifiers.sort().join('+')return`${mods}+${config.key}`}privategetActiveModifiers(event: KeyEvent):Array<string>{const mods:Array<string>=[]if(event.ctrlKey) mods.push('ctrl')if(event.shiftKey) mods.push('shift')if(event.altKey) mods.push('alt')return mods }// 快捷键动作实现privatesaveFile(){const content = AppStorage.get<string>('currentContent')||''const uri = AppStorage.get<string>('currentUri')if(uri){ fs.writeText(uri, content) promptAction.showToast({ message:'保存成功'})}}privatenewFile(){// 创建新文件逻辑 router.pushUrl({ url:'pages/Editor', params:{ newFile:true}})}privateopenFile(){// 打开文件选择器let documentPicker =newpicker.DocumentViewPicker(getContext(this)) documentPicker.select().then((result)=>{if(result.length >0){ AppStorage.setOrCreate('selectedFileUri', result[0])}})}privateundo(){// 调用编辑器撤销 AppStorage.setOrCreate('editorAction','undo')}privateredo(){ AppStorage.setOrCreate('editorAction','redo')}privateinsertBold(){ AppStorage.setOrCreate('editorInsert','****')}privatetogglePreview(){const current = AppStorage.get<boolean>('isPreviewMode')||false AppStorage.setOrCreate('isPreviewMode',!current)}}

四、跨设备协同场景实战

4.1 手机拍照插入PC文档

利用分布式文件系统实现:

// PhotoTransferManager.etsimport distributedFile from'@ohos.file.distributedFile'exportclassPhotoTransferManager{// 发起拍照请求到手机asyncrequestPhotoFromPhone():Promise<string>{// 查找在线手机设备const devices =awaitthis.getPhoneDevices()if(devices.length ===0){thrownewError('No phone device found')}const targetDevice = devices[0]// 通过分布式软总线发送拍照指令const session =awaitthis.createSession(targetDevice.networkId)await session.sendMessage({ action:'TAKE_PHOTO'})// 等待照片传输完成returnnewPromise((resolve, reject)=>{ session.onMessage((msg)=>{if(msg.type ==='PHOTO_READY'){// 获取分布式文件路径const distributedPath = msg.data.path // 复制到本地this.copyToLocal(distributedPath).then(resolve).catch(reject)}})setTimeout(()=>reject(newError('Photo transfer timeout')),30000)})}privateasynccopyToLocal(distributedPath:string):Promise<string>{const fileName =`photo_${Date.now()}.jpg`const localPath =getContext(this).filesDir +'/'+ fileName // 使用分布式文件API复制await distributedFile.copyFile(distributedPath, localPath)return localPath }}

4.2 平板手绘同步到PC

利用分布式数据对象实时同步手绘数据:

// DrawingSyncManager.etsinterfaceDrawingPoint{ x:number y:number pressure:number timestamp:number}interfaceDrawingStroke{ points:Array<DrawingPoint> color:string width:number}exportclassDrawingSyncManager{private distributedObj:any=nullasyncinit(){this.distributedObj = distributedObject.create(getContext(this),'drawing_session',{ strokes:[]asArray<DrawingStroke>})// 监听笔画数据this.distributedObj.on('change',(sessionId, fields)=>{if(fields.includes('strokes')){const strokes =this.distributedObj.strokes this.renderStrokes(strokes)}})}// 平板端调用:添加笔画asyncaddStroke(stroke: DrawingStroke){const currentStrokes =this.distributedObj.strokes ||[] currentStrokes.push(stroke)this.distributedObj.strokes = currentStrokes }// PC端调用:渲染笔画到CanvasprivaterenderStrokes(strokes:Array<DrawingStroke>){const canvas = AppStorage.get<CanvasRenderingContext2D>('drawingCanvas')if(!canvas)return canvas.clearRect(0,0, canvas.width, canvas.height) strokes.forEach(stroke =>{ canvas.beginPath() canvas.strokeStyle = stroke.color canvas.lineWidth = stroke.width canvas.lineCap ='round' canvas.lineJoin ='round' stroke.points.forEach((point, index)=>{if(index ===0){ canvas.moveTo(point.x, point.y)}else{ canvas.lineTo(point.x, point.y)}}) canvas.stroke()})}}

五、性能优化与最佳实践

5.1 大文件处理优化

Markdown文件可能很大,需要虚拟列表优化:

// VirtualListController.etsclassVirtualListController{private itemHeight:number=40private visibleCount:number=50private bufferCount:number=10// 计算可见区域getVisibleRange(scrollOffset:number):{ start:number, end:number}{const start = Math.floor(scrollOffset /this.itemHeight)-this.bufferCount const end = start +this.visibleCount +this.bufferCount *2return{ start: Math.max(0, start), end: Math.min(this.totalItems, end)}}// 渲染优化buildVirtualList(items:Array<string>){List({ space:0}){LazyForEach(this.dataSource,(item:string, index:number)=>{ListItem(){MarkdownLine({ content: item, lineNumber: index +1})}.height(this.itemHeight).recycle(true)// 启用回收复用},(item:string, index:number)=> index.toString())}.cachedCount(this.bufferCount)// 缓存缓冲区.onScroll((scrollOffset)=>{this.updateVisibleRange(scrollOffset)})}}

5.2 内存管理

PC应用可能长时间运行,需要注意内存泄漏:

// MemoryManager.etsexportclassMemoryManager{privatestatic intervals:Array<number>=[]privatestatic listeners:Array<()=>void>=[]// 安全设置定时器staticsetSafeInterval(callback:()=>void, delay:number):number{const id =setInterval(callback, delay)this.intervals.push(id)return id }// 安全注册事件staticaddSafeListener(event:string,handler:()=>void){ emitter.on(event, handler)this.listeners.push(()=> emitter.off(event, handler))}// 页面销毁时清理staticcleanup(){this.intervals.forEach(id =>clearInterval(id))this.intervals =[]this.listeners.forEach(off =>off())this.listeners =[]// 释放大对象 AppStorage.delete('largeDataCache')}}

六、调试与发布

6.1 PC端调试技巧

# 连接PC设备(需开启开发者模式) hdc list targets hdc shell # 实时查看日志 hdc hilog |grep HarmonyMark # 性能分析 hdc shell hiprofiler -c /data/local/tmp/config.json 

6.2 发布配置

// 配置PC应用图标和分类{"app":{"icon":"$media:pc_icon","label":"HarmonyMark","category":"productivity","pcConfig":{"supportWindowMode":["fullscreen","split","float"],"defaultWindowSize":[1200,800],"minWindowSize":[800,600]}}}

七、总结与展望

本文完整演示了HarmonyOS 5.0 PC应用的核心开发流程,涵盖:

  1. 响应式布局:通过GridRow/GridCol实现PC端自适应
  2. 分布式能力:利用DistributedObject实现跨设备协同
  3. 多窗口管理:支持专业级多文档编辑体验
  4. 键鼠交互:完整的快捷键系统提升效率

未来优化方向:

  • 接入AI能力实现智能Markdown补全
  • 支持插件系统扩展编辑器功能
  • 实现WebDAV云同步

HarmonyOS PC生态正处于快速发展期,开发者应抓住窗口期,提前布局PC应用市场。随着2025年鸿蒙PC的全面商用,早期投入将获得显著的先发优势。


参考资源:


转载自:https://blog.ZEEKLOG.net/u014727709/article/details/158931841
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Read more

FPGA实现MIPI协议全解析 + MIPI协议完整时序规范

FPGA实现MIPI协议全解析 + MIPI协议完整时序规范

一、MIPI协议核心基础认知 百度网盘链接:https://pan.baidu.com/s/1rDsLAXGj8WbX82teSkhuIw?pwd=1234 提取码: 1234 包含FPGA系统学习资料,免费分享 1. MIPI协议定义与核心特点 MIPI(Mobile Industry Processor Interface,移动产业处理器接口)是由MIPI联盟制定的高速串行差分接口协议,最初为手机、平板等移动设备设计,目前广泛应用于FPGA/嵌入式的图像采集(摄像头)、显示驱动(液晶屏)、高速数据传输 场景。 核心特点: ✅ 采用差分信号传输,抗干扰能力强、EMI电磁辐射小; ✅ 支持高低速双模切换,兼顾高速大数据传输和低速控制指令传输; ✅ 串行传输,引脚数量极少(对比并行RGB的几十根引脚,MIPI仅需时钟+1~4路数据差分对),硬件设计简洁; ✅ 传输速率高:单lane(数据通道)速率可达1Gbps~

By Ne0inhk

Vivado完整license文件获取与配置指南

本文还有配套的精品资源,点击获取 简介:Vivado是由Xilinx开发的FPGA和SoC设计综合工具,支持Verilog、VHDL等硬件描述语言,提供高级综合、仿真、IP集成等功能。本资源包“Vivado_的license文件.zip”包含用于解锁Vivado完整功能的许可证文件。介绍了许可证服务器配置、.lic文件管理、浮动与固定许可证区别、激活流程、更新与诊断等核心内容。适用于FPGA开发者、嵌入式系统工程师及学习者,帮助其合法配置Vivado环境,提升开发效率和项目执行能力。 1. Vivado工具与FPGA开发环境概述 Xilinx Vivado设计套件是面向FPGA和SoC开发的集成化软件平台,广泛应用于通信、工业控制、人工智能、嵌入式视觉等多个高科技领域。其核心功能包括项目创建、综合、实现、仿真、调试及系统级集成,支持从设计输入到硬件验证的全流程开发。 Vivado不仅提供了图形化界面(GUI)便于初学者快速上手,还支持Tcl脚本自动化操作,满足高级用户的大规模工程管理需求。其模块化架构设计使得开发者可以灵活选择所需功能组件,如HLS(高层次综合)、IP In

By Ne0inhk
图数据库Neo4j和JDK安装与配置教程(超详细)

图数据库Neo4j和JDK安装与配置教程(超详细)

目录 前言 一、Java环境配置 (一)JDK的下载与安装 (二)JDK环境配置 (三)检测JDK17是否配置成功 二、Neo4j的安装与配置 (一)Neo4j的下载与安装 (二)Neo4j环境变量配置 (三)检查Neo4j是否配置完成 Neo4j的使用 一、在前台运行 二、在后台运行 前言 Neo4j作为目前比较流行的图数据库,在知识图谱等领域有较多应用。本文将详细介绍Windows系统下Neo4j图数据库的安装与配置。 Neo4j 是基于Java的图数据库,其运行时需要 Java 运行时环境(JRE)来启动 JVM 进程,而 JDK 包含了 JRE 以及开发工具,因此安装 JDK 是必要的。 一、Java环境配置 (一)JDK的下载与安装 首先,访问Oracle官方JDK下载页面,

By Ne0inhk
1200PLC与爱普生机器人modbus_TCP通讯

1200PLC与爱普生机器人modbus_TCP通讯

1.前言 首先申明一下我的硬件信息 机器人:C4-A601S 控制器:RC700 PLC:西门子S7-1200(CPU:1217C/DC/DC/DC) 2.控制器IP地址查看及修改 在配置控制器相关信息时需要先用网线连接PC与机器人控制器连接,爱普生机器人出厂设定网址为192.168.0.1(我这里是之前修改过了) 若默认没有显示以太网连接,点击右侧的增加,选择“通过以太网连接到控制器”后点击确定 如果控制器网址被修改过了,不知道是多少,可以用一根PC线,一头接在控制器的“开发用PC连接专用USB端口”另一头接在电脑USB口 这时候再在通讯处选择USB连接就可以通上了 现在就可以在“系统配置”处看到控制器的IP地址以及相关信息了,如果有需要也可以直接在这修改IP地址。 3.机器人控制器配置 网线连接好后开始配置通讯相关信息 1.控制设备 控制设备修改为远程I/O 2.现场总线 现场总线类型修改为“Modbus TCP”

By Ne0inhk