在鸿蒙(HarmonyOS)工业场景下的串口通信开发中,系统提供了完善的 serialManager 模块(USB Serial Communication Management)来支持主机与串口设备(如 RS-232、RS-485 等)之间的数据传输。

一、 基础通信流程:权限、打开与数据读写

在工业场景中,标准的串口通信需要严格遵循“获取设备列表 -> 申请权限 -> 打开串口 -> 数据读写”的流程。系统同时提供了异步(Promise)和同步两种读写方式。

核心代码示例:

import { serialManager } from '@kit.BasicServicesKit';
import { buffer } from '@kit.ArkTS';

async function startSerialCommunication() {
  // 1. 获取主机连接的 USB 串口设备列表
  let portList: serialManager.SerialPort[] = serialManager.getPortList();
  if (portList === undefined || portList.length === 0) {
    console.error('未检测到串口设备');
    return;
  }
  
  let portId: number = portList[0].portId;

  // 2. 检查并请求串口设备的访问权限
  if (!serialManager.hasSerialRight(portId)) {
    let result = await serialManager.requestSerialRight(portId);
    if (!result) {
      console.error('用户拒绝授权串口访问');
      return;
    }
  }

  // 3. 打开串口设备
  try {
    serialManager.open(portId);
    console.info(`串口打开成功,端口ID: ${portId}`);
  } catch (error) {
    console.error(`串口打开失败: ${error}`);
    return;
  }

  // 4. 异步读取数据
  let readBuffer: Uint8Array = new Uint8Array(64);
  serialManager.read(portId, readBuffer, 2000).then((size: number) => {
    console.info(`成功读取 ${size} 字节数据`);
  }).catch((error: Error) => {
    console.error(`读取数据异常: ${error}`);
  });

  // 5. 异步写入数据(向工业设备发送指令)
  let writeBuffer: Uint8Array = new Uint8Array(buffer.from('Hello Industrial Device', 'utf-8').buffer);
  serialManager.write(portId, writeBuffer, 2000).then((size: number) => {
    console.info(`成功写入 ${size} 字节数据`);
  }).catch((error: Error) => {
    console.error(`写入数据异常: ${error}`);
  });
}

二、 协议配置:波特率与数据帧格式

工业现场设备(如传感器、PLC、读卡器)对通信协议参数有严格要求。开发者可通过 getAttribute 和 setAttribute 接口动态配置波特率、数据位、校验位和停止位。

核心代码示例:

// 获取当前串口配置
let attribute: serialManager.SerialAttribute = serialManager.getAttribute(portId);

// 修改配置:例如设置为 115200 波特率,8数据位,无校验,1停止位
attribute.baudRate = 115200;
attribute.dataBits = 8;
attribute.parity = serialManager.ParityType.PARITY_NONE;
attribute.stopBits = 1;

// 应用新配置
try {
  serialManager.setAttribute(portId, attribute);
  console.info('串口参数配置成功');
} catch (error) {
  console.error('串口参数配置失败:', error);
}

三、 进阶架构:非标设备的扩展驱动开发(USB Serial DDK)

在工业场景中,常会遇到一些老旧或定制的非标串口设备(如特殊温湿度计、定制身份读卡器)。如果系统没有适配驱动,开发者可以使用 USB Serial DDK(Driver Develop Kit) 在应用层开发专属的 USB 串口驱动。

架构原理:
非标外设应用通过扩展外设管理服务获取设备 ID,通过 RPC 将操作下发给 USB 串口驱动应用(DriverExtensionAbility)。驱动应用调用 DDK 接口设置串口属性并进行读写,DDK 再通过 HDI 服务将指令下发至内核驱动与设备通信。

Native 层核心接口(C++):

// 1. 初始化 USB Serial DDK
OH_UsbSerial_Init();

// 2. 打开指定的 USB 串口设备
UsbSerial_Device *dev = nullptr;
OH_UsbSerial_Open(deviceId, interfaceIndex, &dev);

// 3. 设置波特率等属性
OH_UsbSerial_SetBaudRate(dev, 9600);

// 4. 读写数据
uint8_t buff[128];
uint32_t bytesRead = 0;
OH_UsbSerial_Read(dev, buff, sizeof(buff), &bytesRead);

// 5. 使用完毕后关闭设备,防止内存泄漏
OH_UsbSerial_Close(&dev);
OH_UsbSerial_Release();
  1. 生命周期管理:在使用 DDK 接口时,务必在 DriverExtensionAbility 的生命周期内调用,并在设备使用完后严格调用 close 接口,否则会造成底层内存泄漏。
  2. 权限声明:使用 USB Serial DDK 开放 API 开发扩展驱动时,必须在 module.json5 中声明匹配的 ACL 权限,例如 ohos.permission.ACCESS_DDK_USB_SERIAL
  3. 默认参数注意:在未显式配置串口参数时,系统默认的波特率为 9600bps、8位数据位、无校验位和1位停止位。对接工业设备前务必确认参数匹配,否则会导致数据解析乱码。
  4. 同步与异步选择:在工业高频数据采集场景中,若需要保证数据帧的绝对顺序且不阻塞主线程,建议结合鸿蒙的 FFRT(Function Flow Runtime)串行队列进行异步读写任务的调度。

四、 跨平台架构:Flutter 鸿蒙串口通信桥接

对于使用 Flutter 构建鸿蒙工业 HMI 的团队,Dart 层无法直接访问底层硬件。必须借助鸿蒙的 Platform Channel 机制,在 ArkTS/Native 层拦截权限并完成串口操作,再通过事件通道(EventChannel)将数据流推送到 Dart 层。

核心代码示例(ArkTS 侧):

// 1. 拦截并授予串口权限(解决鸿蒙 Webview/Flutter 容器的安全限制)
methodChannel.on('requestSerialPort', async () => {
  let portId = portList[0].portId;
  if (!serialManager.hasSerialRight(portId)) {
    await serialManager.requestSerialRight(portId);
  }
  methodChannel.send('serialGranted', true);
});

// 2. 建立事件通道,将底层硬件数据主动推送到 Flutter UI 层
eventChannel.onListen(() => {
  // 监听串口数据接收
  serialManager.read(portId, readBuffer, 2000).then((size: number) => {
    // 将原生的 ArrayBuffer 转换为 Hex 字符串或 Base64 回传给 Dart 端
    const strData = bufferToHex(readBuffer);
    eventChannel.send(strData);
  });
});

五、 工业协议集成:Modbus RTU/TCP 的鸿蒙化适配

在工业场景中,串口通常仅作为物理传输层,上层往往运行着 Modbus 等标准协议。开发者可以通过引入纯 Dart 逻辑库(如 modbus_client),结合鸿蒙原生串口驱动,快速构建工业网关。

核心代码示例(Dart 侧):

import 'package:modbus_client/modbus_client.dart';

// 定义工业数据模型(将寄存器直接映射为类型安全的对象)
final temperatureSensor = ModbusNumericElement(
  name: 'MachineTemp',
  address: 0x0001,       // Modbus 寄存器地址
  type: NumericType.uint16,
  scaleFactor: 0.1       // 缩放因子:原始值 / 10 = 实际温度
);

// 在鸿蒙端发起 Modbus RTU 读取请求
Future<void> readIndustrialData() async {
  // 结合鸿蒙底层串口构建 Modbus RTU 客户端
  final client = ModbusClientRtu(serialPort: ohosSerialPortInstance);
  final result = await client.read(temperatureSensor);
  print('当前设备温度: ${result?.value} ℃');
}

六、 性能优化:高频数据流的非阻塞与降采样

工业传感器(如高频振动传感器)的回传数据量极大。如果将全量数据直接透传给 UI 层,会导致严重的内存抖动和界面卡顿。必须在原生层进行“边缘计算”或“降采样”。

核心架构建议:

  1. Dart Stream 异步消费:在 Dart 侧使用 port.readable.stream.listen() 进行非阻塞式的高频数据交互,避免同步读取阻塞主线程。
  2. 原生层数据聚合:在 ArkTS 或 C++ 层设置数据缓冲区,例如每累积 100 个采样点,计算出一个平均值或峰值后,再触发一次跨线程通信传递给 Flutter 绘制波形图。

七、 稳定性保障:物理链路的断连重连机制

鸿蒙手持设备在工业现场移动时,USB 连接器极易因震动发生物理断开。应用必须具备静默恢复通信的能力。

核心代码示例:

// 实现心跳包与重连轮询机制
async function maintainSerialConnection() {
  while (isAppRunning) {
    try {
      // 1. 轮询检查已授权的端口是否依然有效
      let currentPorts = serialManager.getPortList();
      let isConnected = currentPorts.some(p => p.portId === targetPortId);
      
      if (!isConnected) {
        console.warn('检测到串口物理断开,正在尝试重新连接...');
        // 2. 触发重连逻辑并重新配置波特率等参数
        await reconnectAndConfigure(); 
      }
    } catch (error) {
      console.error('心跳检测异常:', error);
    }
    // 3. 设置合理的轮询间隔(如 1000ms),避免过度消耗 CPU
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
}
  1. WebView 权限拦截(Critical):在鸿蒙系统中,默认的 Webview 容器出于安全考虑会禁用 serial API。开发者必须在鸿蒙 Native 层拦截 onPermissionRequest 事件,并显式授予 ohos.permission.SERIAL_PORT 权限,否则串口请求将静默失效。
  2. 读写超时控制:工业现场电磁环境复杂,极易发生丢包。在调用 serialManager.read 时,务必设置合理的超时时间(如 2000ms),并在 catch 块中做好超时重试机制,防止进程无限挂起。
  3. 资源释放:在组件销毁(aboutToDisappear)或应用退至后台时,必须严格调用 serialManager.close(portId) 关闭串口,释放底层硬件文件描述符,防止下次启动时提示“设备被占用”。
  4. 跨平台一致性:在 Flutter 鸿蒙混合开发中,遵循“让鸿蒙做硬事(底层串口驱动、权限管理),让 Flutter 做软事(UI 渲染、Modbus 业务逻辑)”的核心心法,能最大化发挥鸿蒙分布式硬件的优势。
1、 WebView 权限拦截(Critical):显式授予串口权限

在鸿蒙的 ArkWeb 容器中,出于安全沙箱限制,Web 页面发起的硬件请求默认会被拦截。必须在 ArkTS 侧拦截 onPermissionRequest 事件,并显式授予 ohos.permission.SERIAL_PORT 权限。

核心代码示例:

import { webview } from '@kit.ArkWeb';

// 在 Web 组件的控制器中拦截权限请求
let webController = new webview.WebviewController();

// 监听 Web 页面发起的权限请求
webController.on('onPermissionRequest', (event: webview.PermissionRequestEvent) => {
  // 检查请求的权限是否为串口访问权限
  if (event.permissionList.includes('ohos.permission.SERIAL_PORT')) {
    // 显式授予串口权限
    event.grant(event.permissionList); 
    console.info('已显式授予 Web 组件串口访问权限');
  } else {
    // 拒绝其他未授权的权限
    event.reject(); 
  }
});
2、 读写超时控制:异步读取与异常重试机制

工业现场电磁干扰严重,极易发生数据丢包或响应延迟。必须为 serialManager.read 设置合理的超时时间,并在 catch 块中捕获超时错误码(如 31400006),触发重试机制。

核心代码示例:

import { serialManager } from '@kit.BasicServicesKit';

async function safeReadWithRetry(portId: number, maxRetries: number = 3): Promise<Uint8Array | null> {
  let buffer = new Uint8Array(64);
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      // 设置 2000ms 超时时间,防止底层 I/O 无限挂起
      let size = await serialManager.read(portId, buffer, 2000);
      if (size > 0) {
        return buffer.subarray(0, size); // 返回有效数据切片
      }
    } catch (error) {
      // 捕获超时错误码 (31400006) 或 I/O 异常 (31400007)
      if (error.code === 31400006 || error.code === 31400007) {
        console.warn(`第 ${attempt} 次读取超时/异常,准备重试...`);
        if (attempt === maxRetries) {
          console.error('达到最大重试次数,放弃本次读取');
          return null;
        }
        // 简单的退避延迟,避免连续重试压垮底层驱动
        await new Promise(resolve => setTimeout(resolve, 500)); 
      } else {
        throw error; // 抛出非超时类的致命错误
      }
    }
  }
  return null;
}
3、 资源释放:生命周期绑定与防占用机制

硬件文件描述符(FD)是极其宝贵的系统资源。必须在 UI 组件销毁(aboutToDisappear)或应用退至后台时,严格调用 close 接口,防止设备被永久锁死。

核心代码示例:

import { serialManager } from '@kit.BasicServicesKit';

@Entry
@Component
struct SerialMonitorPage {
  private portId: number = -1;

  aboutToAppear() {
    // 初始化并打开串口
    this.initSerialPort();
  }

  // 核心:在组件销毁时强制释放硬件资源
  aboutToDisappear() {
    if (this.portId !== -1) {
      try {
        serialManager.close(this.portId);
        console.info(`串口 ${this.portId} 已安全关闭,释放底层文件描述符`);
      } catch (error) {
        console.error(`关闭串口异常: ${error}`);
      }
    }
  }

  private async initSerialPort() {
    let portList = serialManager.getPortList();
    if (portList.length > 0) {
      this.portId = portList[0].portId;
      if (!serialManager.hasSerialRight(this.portId)) {
        await serialManager.requestSerialRight(this.portId);
      }
      serialManager.open(this.portId);
    }
  }

  build() {
    // UI 布局...
    Column() {}
  }
}
4、 跨平台一致性:Flutter 与鸿蒙的软硬分工架构

在 Flutter for OpenHarmony 架构下,遵循“鸿蒙做硬事,Flutter做软事”的原则。ArkTS/Native 层负责底层驱动和权限,Flutter 层专注 Modbus 协议解析和 UI 渲染。

核心代码示例(Dart 侧):

import 'package:flutter/services.dart';
import 'package:modbus_client/modbus_client.dart';

class OhosIndustrialBridge {
  static const _channel = MethodChannel('com.example/serial_bridge');
  
  // 1. 通过 Channel 通知鸿蒙 Native 层初始化并打开串口
  static Future<void> initHardware() async {
    await _channel.invokeMethod('initSerialPort');
  }

  // 2. 监听底层推上来的原始数据流(EventChannel)
  static Stream<Uint8List> get rawSerialStream {
    const eventChannel = EventChannel('com.example/serial_stream');
    return eventChannel.receiveBroadcastStream().map((data) => Uint8List.fromList(data));
  }

  // 3. 在 Dart 层处理 Modbus 协议(软事)
  static void processModbusData(Uint8List rawData) {
    // 将底层透传的字节流交给 Modbus 解析器
    // 例如:ModbusClientRtu.parse(rawData);
    print('收到工业数据帧,长度: ${rawData.length}');
  }
}

核心代码示例(ArkTS 侧 - 对应上述 Dart 的硬件事件):

// 在鸿蒙侧的 MethodChannel 处理中
methodChannel.on('initSerialPort', async () => {
  let portList = serialManager.getPortList();
  if (portList.length > 0) {
    let portId = portList[0].portId;
    if (!serialManager.hasSerialRight(portId)) {
      await serialManager.requestSerialRight(portId);
    }
    serialManager.open(portId);
    // 启动后台轮询,将数据通过 EventChannel 推送给 Flutter
    startBackgroundReadLoop(portId); 
  }
});
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐