二进制组件使用指导

← 返回二进制组件参考 · 二进制数据专题 · 全部使用指导

二进制组件使用指导

二进制组件帮你把「帧头、命令、温度、校验」这些内容和一串字节互相转换。它不收发数据, 收发交给蓝牙、Socket、WebSocket、MQTT 组件(见二进制数据专题)。 所有方法直接返回结果,不需要事件。

前四个案例不需要任何硬件,放一个按钮、一个标签就能在手机上看到结果,和文中写的对一下就知道做对没有。

先懂三件事

1. 字节列表:一串字节就是由 0~255 的数字组成的普通列表,[165, 1, 255]。 列表积木都能用:求长度、取第几个(从 1 数起)、追加。

2. 十六进制:协议文档里的 A5 01 FF 和 [165, 1, 255] 是同一串字节,两位十六进制是一个字节。 「十六进制转字节」「字节转十六进制」负责互转,调试时把收到的内容显示成十六进制最直观。

3. 多字节数字的顺序和符号:一个字节只能放 0~255,温度 23.5 这种要乘 10 变成 235 再放进 2 个字节。

数字 高位优先(默认) 低位优先 说明
1500 05 DC DC 05 协议文档会写明「高字节在前」还是「低字节在前」
-20 FF EC EC FF 负数按补码;读回时「有符号」要选真,否则得到 65516

单片机直接发内存里的数字(ESP32、STM32 常见)多是低字节在前,把高位优先(HighByteFirst)设为假。

快速上手:十六进制与字节互转

拖一个二进制组件、一个文本输入框、一个按钮和一个标签。在输入框里敲十六进制,点按钮看它有几个字节、第 1 个字节的十进制是多少:

when 按钮1.Click() {
  var 字节 = 二进制1.FromHex(文本输入框1.Text)
  if (length(字节) > 0) {
    标签1.Text = join(length(字节), " 个字节,第 1 个是 ", 字节[1], ",规整后 ", 二进制1.ToHex(字节))
  } else {
    标签1.Text = "请输入至少一个有效字节"
  }
}

跑起来看:输入 a5,01,ff,标签显示「3 个字节,第 1 个是 165,规整后 A5 01 FF」。输入 A5 1(单数位)会弹出错误提示「十六进制位数是单数」。

案例一:拼一帧并加上校验

协议:帧头 A5 + 命令 03 + 目标温度(2 字节,乘 10,有符号)+ CRC16-MODBUS 校验。设定温度 -2.5 ℃:

when 按钮1.Click() {
  var 帧 = 二进制1.Build([165, 3, 二进制1.NumberToBytes(-25, 2)])
  标签1.Text = 二进制1.ToHex(二进制1.AppendChecksum(帧, "CRC16-MODBUS"))
}

跑起来看:标签显示 A5 03 FF E7 D3 52。FF E7 是 -25 的补码,最后两个字节是 CRC(Modbus 规定低字节在前,已自动处理)。

「拼接字节」一块积木放下整帧的各部分:数字是一个字节,列表整段接上,文本按编码转字节。 不用它的话要连续「追加列表」好几次,还容易漏掉一个字节。

案例二:解析设备回复的一帧

设备回复:A5 83 + 温度(2 字节,乘 10,有符号)+ 湿度(1 字节)+ 状态(1 字节)+ 累加校验。 先把回复写死在十六进制文本里练习解析,接到真设备时把十六进制文本换成收到的字节列表即可:

when 按钮1.Click() {
  var 帧 = 二进制1.FromHex("A5 83 00 EB 2D 05 45")
  if (二进制1.VerifyChecksum(帧, "SUM8")) {
    var 温度 = 二进制1.BytesToNumber(帧, 3, 2, true) / 10
    var 湿度 = 帧[5]
    var 报警 = 二进制1.GetBit(帧[6], 0)
    标签1.Text = join("温度 ", 温度, " ℃,湿度 ", 湿度, " %,报警 ", 报警)
  } else {
    标签1.Text = "校验不对,丢弃这一帧"
  }
}

跑起来看:标签显示「温度 23.5 ℃,湿度 45 %,报警 true」。把十六进制最后的 45 改成 46 再点,显示「校验不对,丢弃这一帧」。

帧[5] 就是列表的「选择列表项」,第 5 个字节;状态字节 05 的二进制是 0000 0101,第 0 位是 1,所以报警为真。

案例三:小数与 4 字节 float 互转

传感器常把小数按 IEEE754 单精度(4 字节)发送。把 23.5 转成字节再转回来:

when 按钮1.Click() {
  var 大端 = 二进制1.FloatToBytes(23.5, 4)
  二进制1.HighByteFirst = false
  var 小端 = 二进制1.FloatToBytes(23.5, 4)
  var 还原 = 二进制1.BytesToFloat(小端, 1, 4)
  二进制1.HighByteFirst = true
  标签1.Text = join("大端 ", 二进制1.ToHex(大端), " / 小端 ", 二进制1.ToHex(小端), " / 还原 ", 还原)
}

跑起来看:标签显示「大端 41 BC 00 00 / 小端 00 00 BC 41 / 还原 23.5」。 和单片机对不上时,先看它发来的是哪一种顺序。

案例四:文本命令与中文

有的设备协议里夹着文本命令,比如 A5 + "LED=1" + 换行;串口屏显示中文多用 GBK 编码:

when 按钮1.Click() {
  var 命令 = 二进制1.Build([165, "LED=1", 10])
  二进制1.TextEncoding = "GBK"
  var 中文 = 二进制1.FromText("你好")
  二进制1.TextEncoding = "UTF-8"
  标签1.Text = join(二进制1.ToHex(命令), " / ", 二进制1.ToHex(中文))
}

跑起来看:标签显示「A5 4C 45 44 3D 31 0A / C4 E3 BA C3」。同样的「你好」按 UTF-8 是 6 个字节 E4 BD A0 E5 A5 BD, 所以设备显示乱码时,先确认两边用的是同一种编码。

案例五:和电脑上的网络调试助手联调

没有设备也能把收发跑通:电脑上的网络调试助手(NetAssist、SSCOM 等,网上免费下载)开一个 TCP 服务器扮演设备。

  1. 电脑和手机连同一个 Wi-Fi,查到电脑的局域网 IP(如 192.168.1.10);
  2. 调试助手选「TCP Server」,端口 8888,勾选「十六进制显示」「十六进制发送」,点「打开」;
  3. App 里拖一个 Socket客户端,服务器地址填电脑 IP、端口 8888,再放一个按钮、两个标签:
when Screen1.Initialize() {
  Socket客户端1.Connect()
}

when 按钮1.Click() {
  var 帧 = 二进制1.AppendChecksum(二进制1.Build([165, 1, 二进制1.NumberToBytes(1500, 2)]), "SUM8")
  Socket客户端1.SendBytes(帧)
  标签1.Text = join("已发送 ", 二进制1.ToHex(帧))
}

when Socket客户端1.BytesReceived(字节列表) {
  标签2.Text = join("收到 ", 二进制1.ToHex(字节列表))
}

跑起来看:

  • 点按钮,调试助手窗口里出现 A5 01 05 DC 87,和标签1 显示的一致;
  • 在调试助手的发送框里填 A5 83 00 EB 2D 05 45 点发送,标签2 显示「收到 A5 83 00 EB 2D 05 45」。 把案例二的解析逻辑搬进「收到字节时」,就成了一个完整的上位机。

调试助手一次发出的内容,App 在局域网里通常一次收到。真实设备连续发送时可能拆开或粘在一起, 可靠的写法见专题页的拆帧。

案例六:收到的字节存成文件,再读回来分包发送

蓝牙、串口收到的整段数据(日志、图片、固件)要留底,或者要把一个文件分成小包发给单片机,用「保存字节到文件」和「读取文件字节」。 下面先把一段字节存成 log.bin,再读回来,按每包 4 个字节切开:

when 按钮1.Click() {
  var 数据 = 二进制1.FromHex("A5 01 05 DC 87 00 FF 10 20 30")
  var 路径 = 二进制1.SaveBytesToFile(数据, "log.bin")
  var 读回 = 二进制1.ReadFileBytes("log.bin")
  var 分包 = ""
  for 起点 = 1 to length(读回) step 4 {
    // 最后一包可能不足 4 个:截取长度不能超过剩下的字节数,否则「截取字节」会报错
    分包 = join(分包, "[", 二进制1.ToHex(二进制1.SubBytes(读回, 起点, min(4, length(读回) - 起点 + 1))), "]")
  }
  标签1.Text = join("读回 ", length(读回), " 字节,和写入相同:", 读回 == 数据)
  标签2.Text = join("分包:", 分包)
  标签3.Text = 路径
}

跑起来看:标签1 显示「读回 10 字节,和写入相同:true」,标签2 显示「分包:[A5 01 05 DC][87 00 FF 10][20 30]」——三包分别是 4、4、2 个字节,最后一包按剩下的字节数截取;标签3 显示文件的完整路径,以 /log.bin 结尾。 这个路径可以直接交给 Web 客户端的「执行POST表单文件请求」上传,或交给分享组件发出去。

要点:

  • 只写文件名时,文件存在本应用自己的目录,不需要存储权限,卸载应用时一起删除;同名文件会被覆盖;
  • 「读取文件字节」只写文件名时,先找本应用目录里存过的文件,没有再找项目素材,所以也能直接读打包进项目的 .bin 素材;
  • 读写都限 1MB:字节列表里每个字节是一个列表项,太大的文件会把内存撑满。分包发送时每包大小按设备的接收能力定(低功耗蓝牙常见 20 字节)。

常见问题

现象 原因 解决
温度读成 65516 这种大数 负数按无符号读了 「字节转整数」的「有符号」选真
数字差了很多倍、对不上 字节顺序和设备相反 改「高位优先」;先用「字节转十六进制」看原始字节
校验总是不对 算法、初值或覆盖范围和设备不一致 用参考页 123456789 的检验值核对设备端;确认校验包含哪几个字节
报错「超出一个字节的范围」 列表里放了大于 255 的数 多字节数字先用「整数转字节」
拼出来多了几个字节 文本框的 "12" 放在顶层按文本转成了两个字节 要当一个字节时放进列表,或先做一次数学运算
中文乱码 编码不一致 「文本编码」和设备保持一致(国内设备多为 GBK)
「读取文件字节」报文件超过 1MB 字节列表不适合太大的文件 大文件交给文件类组件或 Web 上传,不要整个转成字节列表
只写文件名读到的不是刚存的文件 应用目录里没有这个名字,读到了同名素材 用「保存字节到文件」返回的完整路径去读

平台说明

安卓与鸿蒙行为一致,结果逐字节相同,本页积木两边写法相同。

完整 .aia 源码与操作指导

下载 .aia 源码

不需要网络或硬件即可解析真实硬件协议格式。初始小端、UTF-8、空格分隔;默认帧是 Modbus 读取10个保持寄存器请求。字节下标从1开始,位下标从0开始。要连接硬件,将拼接结果传给通信组件的字节发送入口。

导入 BinaryToolsAllFeatures.aia,连接最新伴侣,或编译独立安装包。真机/伴侣运行尚未实测。

API / 功能 操作入口 预期结果
HighByteFirst / TextEncoding / HexSeparator 读取与设置 编码选择、大小端开关、分隔符输入、应用设置 属性区读取实际值;分隔符可空,选择 GBK 再试中文
FromHex / ToHex / Checksum / AppendChecksum / VerifyChecksum 默认输入点校验 MODBUS 完整帧为 01 03 00 00 00 0A C5 CD;校验值 52677;核对为真
VerifyChecksum / SubBytes / Build 点损坏校验 最后字节变为 CE,校验为假,原帧为真;原始列表不变
FromText / ToText / BytesToBase64 / Base64ToBytes 点文本转换 字节、Base64 与还原文字同时展示;ASCII 使用英文
NumberToBytes / BytesToNumber 1500、小端点数值 DC 05 还原 1500;负数可观察有符号和无符号差异
FloatToBytes / BytesToFloat 23.5、小端点数值 00 00 BC 41 还原 23.5;其他小数允许浮点舍入
Build / SubBytes / IndexOf 点拼接 小端为 A5 01 02 DC 05;负载起点 4;不存在返回 0
SetBit / GetBit 点位操作 第3位设后为 8/真,清后为 0/假;位编号从 0 开始
SaveBytesToFile / ReadFileBytes 先点校验,再点存文件 路径以 /frame.bin 结尾;读回 01 03 00 00 00 0A C5 CD;与原帧相同为真
空、越界、非法输入 空十六进制、非数字或点非法 有明确提示,修正后可继续

所有返回值均用于界面反馈,错误由底部错误区展示。设计器采用明确非默认配置,初始化与界面同步。API 覆盖检查按最终 AIA 检查方法、事件、属性读取与设置及设计器配置,不能替代真实设备验证。

二进制硬件报文

导入方法:项目 → 导入项目(.aia)→ 选择下载的 .aia 源码文件。 不需要网络或硬件即可解析真实硬件协议格式。初始小端、UTF-8、空格分隔;默认帧是 Modbus 读取10个保持寄存器请求。字节下标从1开始,位下标从0开始。要连接硬件,将拼接结果传给通信组件的字节发送入口。 覆盖检查 29/29。首次载入及保存重开均0个错误,真实后台导入、安装包编译通过;真机/伴侣/硬件联调尚未实测。全部属性读写、方法与事件的操作入口和预期结果见组件指导。

用 Python 独立核对默认报文

电脑安装 Python 3 后,把下面代码保存为 check_binary.py,执行 python3 check_binary.py。不需要第三方库或连接设备;输出可与手机案例逐项对比。

import struct
import base64

# Modbus RTU 的 CRC16 校验按低字节在前追加,与数值字节序开关无关。
def modbus_crc(data):
    value = 0xFFFF
    for byte in data:
        value ^= byte
        for _ in range(8):
            value = (value >> 1) ^ (0xA001 if value & 1 else 0)
    return value

request = bytes.fromhex("01 03 00 00 00 0A")
crc = modbus_crc(request)
frame = request + struct.pack("<H", crc)
assert crc == 52677
assert frame.hex(" ").upper() == "01 03 00 00 00 0A C5 CD"
assert struct.pack("<h", 1500).hex(" ").upper() == "DC 05"
assert struct.pack("<f", 23.5).hex(" ").upper() == "00 00 BC 41"
text_bytes = "设备就绪".encode("utf-8")
assert base64.b64decode(base64.b64encode(text_bytes)).decode("utf-8") == "设备就绪"
print("完整帧:", frame.hex(" ").upper())
print("校验值:", crc)
print("整数1500:", struct.pack("<h", 1500).hex(" ").upper())
print("浮点23.5:", struct.pack("<f", 23.5).hex(" ").upper())
print("文本Base64:", base64.b64encode(text_bytes).decode("ascii"))

手机字节列表下标从1开始,Python字节索引从0开始。例如手机从第4字节截取2字节,对应 Python frame[3:5]。发送 Modbus 帧前,还需按设备手册配置站号、寄存器范围和通信连接;本页核对编码与校验,不声称连接了实际设备。

文档反馈