视频地址:哔哩哔哩

数据收发功能开发

上一节我们讲解了串口调试助手App 的需求导入,本节我们将讲解 数据收发的开发流程

在工作台的项目列表中找到 串口调试助手 App,点击【项目菜单】->【开发文档】进入到需求文档页面。

编辑页面信息

  • 点击 切换到页面菜单,选中 DataTransfer

  • 点击 编辑 按钮,对页面信息进行修改

功能详情

功能详情填写:

  • 串口选择(仅在桌面端存在,手机端默认连接一个串口设备)
    • 监测当前平台是否为android,如果是 则隐藏串口选择功能并默认连接第一个串口,否则 显示串口选择功能
  • 串口通信设置
    • 基本设置,波特率、停止位、数据位、校验位
    • 通信协议设置,支持拼接帧头、帧尾、校验位、帧长、控制字,用户只需发送数据位
    • 追加符设置,支持追加换行符 0x0D(回车)、 0x0A(换行)
  • 日志管理
    • 自动滚屏,可以进行设置日志是否自动滚屏
    • 字符过滤,在日志中高亮显示内容
    • 时间戳拼接,可以进行设置日志是否拼接时间戳
    • 日志一键清理
    • 日志存储限制,当前窗口显示的数据 不超过 1000条
      • 用户每次打开软件,清空日志文件,一旦有数据接收并将日志追加写入到此文件(该文件保留的是全部日志内容)
    • 日志保存,实际上就是将 本地的日志文件 复制到 用户指定的位置
  • 数据收发
    • 支持Hex和文本两种数据格式发送与显示

参考页面

放入下图:

变量列表

进入变量列表,点击 添加变量:

变量名称

变量类型

是否为数组

默认值

变量描述

cycleBoolfalse循环发送
logsMap日志数据列表
fileLengthInt0选中的文件大小
fileChunkSizeStrString56文件分包大小
fileChunkSizeInt56文件分包大小
chunkIndexInt0当前分块索引
delayedString20延时发送值,毫秒值
listMap日志保存的变量记录
sendDataString发送的数据内容
hexDisplayBooltrue是否16进制显示
buChongString0文件拆包补包
timestampBooltrue是否拼接时间戳
filenameString选中的文件名称
autoscrollBooltrue是否自动滚动
protocolDataMap过滤协议数据
hexSendBooltrue是否16进制发送
newLineBooltrue是否添加新行
baudRateString115200波特率
dataBitsString8数据位
stopBitsString1停止位
serialDataMap串口数据
serialPortVar桌面端串口连接对象
_dropDownValueString选择的串口端口值
availablePortsString可用的串口端口列表

配置流程

在流程设计中,填写以下流程:

  • 页面初始化:开始串口扫描,默认连接索引为0的设备,配置串口数据监听

  • 发送按钮:将输入框中的内容通过串口发送给硬件

点击保存按钮保存数据

页面搭建

1. 点击右上角 跳转到开发页面,选中“DataTransfer”页面,开始页面设计。

2. 点击小部件面板图标进入小部件面板

3.在页面预览区域点击顶部导航栏图标,打开添加顶部导航

4. 拖动文本到导航栏中间,点击选中刚拖动的文本小部件,右侧出现小部件的配置页面,并配置如下内容

  • 文本内容:串口调试助手

  • 字号:18

  • 字体颜色:#FFFFFFFF

5. 拖动按钮到导航栏右侧,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 宽:100

  • 高:40

  • 背景颜色:#00FCFCFC

  • 阴影大小:0

6.拖动文本到按钮中,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 文本内容:串口设置

  • 文本大小:14

  • 字体颜色:#FFFFFFFF

7.拖动容器到上下布局中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 宽:无限

  • 高:460

  • 背景颜色:#00C8C8C8

  • 内边距:10

8.拖动上下布局到容器中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 主轴对齐:两端对齐

  • 次轴对齐:向左对齐

9.拖动左右布局到上下布局中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 主轴对齐:两端对齐

  • 次轴对齐:居中对齐

10.拖动 复选框 + 文本 到左右布局中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 复选框绑定变量:hexDisplay

  • 文本内容:16进制显示

11.拖动 复选框 + 文本 到左右布局中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 复选框绑定变量:timestamp

  • 文本内容:时间戳

12.拖动 复选框 + 文本 到左右布局中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 复选框绑定变量:autoscroll

  • 文本内容:自动滚动

13.拖动左右布局到上下布局中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 主轴对齐:左侧对齐

  • 次轴对齐:居中对齐

14.拖动 按钮 + 文本 到左右布局中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 宽:100

  • 高:40

  • 背景颜色:#FF2B66F4

  • 阴影大小:0

  • 外边距:右 20

  • 文本内容:清空日志

  • 文本大小:14

  • 字体颜色:#FFFFFFFF

15.拖动 按钮 + 文本 到左右布局中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 宽:100

  • 高:40

  • 背景颜色:#FF2B66F4

  • 阴影大小:0

  • 文本内容:数据波形

  • 文本大小:14

  • 字体颜色:#FFFFFFFF

16.拖动 自动扩展 + 容器 到上下布局中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 宽:无限

  • 高:

  • 圆角:10

  • 边框:1 #FF000000

  • 内边距:4

  • 背景颜色:#00C8C8C8

17.拖动 滚动条 到容器中,然后拖动列表布局到到滚动条中,在右侧的属性编辑器中配置属性,属性信息如下:

  • 数据绑定:logs

  • 宽:无限

  • 高:无限

 

18.拖动 上下布局 到列表布局中,拖动 条件生成器到上下布局中, 然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 条件内容:timestamp = true

19.拖动 文本 到条件内容1中,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 文本内容:${logs[index]['time']}>

  • 文本大小:12

  • 字体颜色:#FF9C9C9C

20.拖动 条件生成器到上下布局中, 然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 条件内容1:logs[index]['type'] == 'common'

  • 条件内容2:logs[index]['type'] == 'send'

21.拖动 文本 到条件内容1中,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 文本内容:${logs[index]['content']}

  • 文本大小:12

22.拖动 文本 到条件内容2中,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 文本内容:[${logs[index]['uuid']}] 写入: ${convertData(logs[index]['value'])}

  • 文本大小:12

  • 字体颜色:#FF00CD00

23.拖动 文本 到条件内容3中,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 文本内容:[${logs[index]['uuid']}] 接收: ${convertData(logs[index]['value'])}

  • 文本大小:12

  • 字体颜色:#FF0070E6

24.拖动 容器 到上下布局中,然后在右侧的属性编辑器中配置属性,属性信息如下:
  • 宽:无限

  • 高:320

  • 背景颜色:#00FFFFFF

  • 内边距:10

25.拖动 上下布局 到容器中,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 主轴对齐:两端对齐

  • 次轴对齐:左侧对齐

26.拖动 文本 到上下布局中,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 文本内容:单项发送

  • 文本大小:14

  • 字体粗细:700

27.拖动 文本输入框 到上下布局中,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 数据绑定:sendData

  • 宽:无限

  • 高:50

  • 提示文本内容:数据格式:${hexSend? 'aa00bb11cc22': '任意字符' }

  • 提示字体颜色:#FF9E9E9E

28.拖动 左右布局 到上下布局中,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 主轴对齐:两端对齐

  • 次轴对齐:居中对齐

29.拖动 上下布局 到左右布局中,然后在右侧的属性编辑器中配置属性,属性信息如下:

  • 主轴对齐:两端对齐

  • 次轴对齐:居中对齐

30.拖动 复选框 + 文本 + 文本输入框 至 上下布局中,

  • 复选框绑定变量:cycle

  • 文本内容:循环发送 延时(ms):

  • 文本输入框宽:80

  • 文本输入框高:40

  • 边框:1 #FF000000

  • 文本输入框绑定变量:delayed

31.拖动 复选框 + 文本 + 复选框 + 文本 至 上下布局中,

  • 复选框1绑定变量:hexSend

  • 文本内容1:16进制发送

  • 复选框2绑定变量:newLine

  • 文本内容2:添加换行

32.拖动 按钮 + 文本 至 上下布局中,

  • 宽:100

  • 高:40

  • 背景颜色:#FF2B66F4

  • 阴影大小:4

  • 圆角:10

  • 文本内容:设置协议

  • 文本大小:14

  • 字体颜色:#FFFFFFFF

33.拖动 上下布局 至 左右布局中

  • 主轴对齐:向上对齐

  • 次轴对齐:居中对齐

34.拖动 按钮 + 文本 至 上下布局中,

  • 宽:100

  • 高:40

  • 背景颜色:#FF2B66F4

  • 阴影大小:4

  • 圆角:10

  • 文本内容:发送

  • 文本大小:14

  • 字体颜色:#FFFFFFFF

35.拖动 按钮 + 文本 至 上下布局中,

  • 宽:100

  • 高:40

  • 背景颜色:#FF2B66F4

  • 阴影大小:4

  • 圆角:10

  • 文本内容:快捷发送

  • 文本大小:14

  • 字体颜色:#FFFFFFFF

配置逻辑(动作流程图)

进入页面动作流程图

1.点击右上角 页面设置 图标进入到页面设置中

2.打开动作 → 点击 编辑流程图

页面初始化

1. 点击页面初始化下面的加号添加事件

2.点击开始下方的加号,添加动作

3.鼠标选中动作,右侧出现动作配置并配置如下内容

  • 动作类型:硬件动作

  • 硬件类型:串口

  • 操作类型:扫描

4.添加新的动作,右侧出现动作配置并配置如下内容

  • 动作类型:硬件动作

  • 硬件类型:串口

  • 操作类型:连接

  • 连接方式:数组索引

  • 设备索引:0

5.添加新的动作,右侧出现动作配置并配置如下内容

  • 动作类型:硬件动作

  • 硬件类型:串口

  • 操作类型:接收数据

  • 点击新增自定义代码块

    • 代码块名称:数据处理

    • 代码块编辑区

      processData(strList) {
         addReceiveLog(strList.map((e) => int.parse(e, radix: 16)).toList());
      }
  • 选中这个代码块

  • 点击新增自定义代码块

    • 添加以下代码块,用来方便其它函数调用处理

    • // Custom Code: 日志记录
        addLog(type, {content, value}) async {
          if (logs.length > 1000) {
            logs.removeRange(0, logs.length - 1000);
          }
      
          var map = {
            'time': DateTime.now().toString().substring(0, 23),
            'type': type,
            'content': content,
            'value': value
          };
      
          logs.add(map);
      // list.add(map);
      
          if (currentFile == null) {
            Directory directory = await getTemporaryDirectory();
            currentFile =
                File('${directory.path}${Platform.pathSeparator}serialtool.log');
      
            currentFile?.deleteSync();
          }
      
          try {
            IOSink sink = currentFile!.openWrite(mode: FileMode.append);
            sink.writeln(
                "${map['time']}, ${map['type'] == 'common' ? map['content'] : map['value']}  ");
            await sink.flush();
            await sink.close();
          } catch (e) {
            print('写入异常----> $e');
          }
      
          setState(() {});
        }
      
        // Custom Code: 普通消息记录
        addCommonLog(content) {
          addLog('common', content: content);
        }
      
        // Custom Code: 发送消息记录
        void addSendLog(value) {
          addLog('send', value: value);
        }
      
        // Custom Code: 接收消息记录
        void addReceiveLog(value) {
          addLog('receive', value: value);
        }
      
      // Custom Code: 数据格式转换
        convertData(data) {
          if (hexDisplay) {
            return (data.cast<int>())
                .map((e) => e.toRadixString(16).padLeft(2, '0'))
                .join(' ');
          }
      
          return String.fromCharCodes(data.cast<int>());
        }
清空日志按钮动作流程图

1.点击按钮小部件进入到小部件配置中

2.打开动作 → 点击 编辑流程图

单击事件

1. 点击页面初始化下面的加号添加事件

2.点击开始下方的加号,添加动作

3.点击添加按钮,选择添加动作,并配置动作属性

  • 动作类型:变量更新

  • 选择变量:logs

  • 数组操作类型:清空数组

数据波形按钮动作流程图

1.点击按钮小部件进入到小部件配置中

2.打开动作 → 点击 编辑流程图

单击事件

1. 点击页面初始化下面的加号添加事件

2.点击开始下方的加号,添加动作

3.点击添加按钮,选择添加动作,并配置动作属性

  • 动作类型:导航

  • 导航到:DataWaveform

发送按钮动作流程图

1.点击按钮小部件进入到小部件配置中

2.打开动作 → 点击 编辑流程图

单击事件

1. 点击页面初始化下面的加号添加事件

2.点击开始下方的加号,添加动作

3.点击添加按钮,选择添加动作,并配置动作属性

  • 动作类型:自定义代码块

  • 点击新增自定义代码块

    • 代码块名称:串口数据发送

    • 设置入参:String

    • 代码块编辑区

         writeData(String data) async {
          while (true) {
            print("object==== 发送数据 $data");
            // 相关参数
            List<int> paramBytesList = [];
            if (hexSend || canSend) {
              for (int i = 0; i < data.length; i += 2) {
                int v = int.parse("${data[i]}${data[i + 1]}", radix: 16);
                paramBytesList.add(v);
              }
            } else {
              paramBytesList = List.from(data.codeUnits);
            }
      
            if (!canSend) {
              String checkBitType = protocolData['checkBitType'] ?? '累加';
      
              bool isFixedBegin = protocolData['isFixedBegin'] ?? false;
      
              bool isLength = protocolData['isLength'] ?? false;
      
              bool isControlWord = protocolData['isControlWord'] ?? false;
      
              bool isCheckBit = protocolData['isCheckBit'] ?? false;
      
              bool isFixedEnd = protocolData['isFixedEnd'] ?? false;
      
              String fixedBegin = protocolData['fixedBegin'] ?? 'AA';
      
              String length = protocolData['length'] ?? '0';
      
              String controlWord = protocolData['controlWord'] ?? '00';
      
              String checkBitRangeStart = protocolData['checkBitRangeStart'] ?? '1';
              String checkBitRangeEnd = protocolData['checkBitRangeEnd'] ?? '2';
              String fixedEnd = protocolData['fixedEnd'] ?? '55';
      
              List<int> resolved(String source) {
                List<int> res = [];
                for (int i = 0; i < source.length; i = i + 2) {
                  res.add(int.parse(source[i] + source[i + 1], radix: 16));
                }
                return res;
              }
      
              final dataLength = paramBytesList.length;
              int frontLength = 0;
              int totalLength = paramBytesList.length;
      
              if (isFixedBegin) {
                List<int> res = resolved(fixedBegin);
                paramBytesList.insertAll(0, res);
                totalLength += res.length;
                frontLength += res.length;
              }
      
              if (isLength) {
                paramBytesList.insert(
                    frontLength, dataLength + int.parse(length, radix: 16));
                totalLength += 1;
                frontLength += 1;
              }
      
              if (isControlWord) {
                List<int> res = resolved(controlWord);
                paramBytesList.insertAll(frontLength, res);
                totalLength += res.length;
                frontLength += res.length;
              }
      
              if (isCheckBit) {
                List<int> res = [];
                totalLength += 1;
                if (isFixedEnd) {
                  res = resolved(fixedEnd);
                  totalLength += res.length;
                }
      
                int checksum = 0;
                for (int i = int.parse(checkBitRangeStart);
                    i < totalLength - int.parse(checkBitRangeEnd);
                    i++) {
                  if (checkBitType == '累加') {
                    checksum = (checksum + paramBytesList[i]) & 0xFF;
                  }
      
                  if (checkBitType == '异或') {
                    checksum = (checksum ^ paramBytesList[i]) & 0xFF;
                  }
                }
                paramBytesList.add(checksum);
                if (res.isNotEmpty) {
                  paramBytesList.addAll(res);
                }
              } else {
                if (isFixedEnd) {
                  List<int> res = resolved(fixedEnd);
                  paramBytesList.addAll(res);
                }
              }
            }
      
            if (!canSend && newLine) {
              paramBytesList.addAll([0x0D, 0x0A]);
            }
      
            await Future.delayed(Duration(milliseconds: int.parse(delayed)), () {});
            addSendLog(paramBytesList);
            sendDataSerialByIntArr(paramBytesList);
            if (!cycle) {
              break;
            }
          }
        }
      
      
  • 选中这个代码块,并填写入参 sendData

快捷发送按钮动作流程图

1.点击按钮小部件进入到小部件配置中

2.打开动作 → 点击 编辑流程图

    单击事件

    1. 点击页面初始化下面的加号添加事件

    2.点击开始下方的加号,添加动作

    3.点击添加按钮,选择添加动作,并配置动作属性

    • 导航到CustomData

    • 动作类型:导航

    4.添加一个新的动作,并配置动作属性

      • 操作类型:数据读取

      • 接收数据的变量:sendData

      • 读取本地数据的键名:data

      • 动作类型:本地存储

    5.添加一个新的动作,并配置动作属性

      • 选中 串口数据发送

        •    输入参数 sendData

      • 动作类型:自定义代码块

    至此,页面及逻辑全部设置完成。

    Logo

    DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

    更多推荐