数熵 ED-ble
工控调试数熵 ED-N2 二次开发文档:开发者无需关注底层硬件电路、接口驱动、供电适配、蓝牙硬件协议栈等硬件研发工作,仅基于设备开放的软件接口、通讯协议、参数配置规则,即可快速完成手机端、物联网上位机、嵌入式应用、数据采集系统、设备管控平台等软件功能的开发与落地。
数熵 ED-N2 二次开发文档面向设备厂商开发者:无需关注底层硬件电路、接口驱动与蓝牙协议栈,基于开放的 BLE 特征值、RS485 串口参数与数据透传协议,即可完成微信小程序、物联网上位机等软件功能开发,附完整代码示例与参数速查表。
数熵 ED-N2 二次开发文档讲了什么?
面向开发者的 ED-N2 二次开发说明:适用设备为 ED-N2-RS485 串口调试器,目标是让开发者不碰硬件电路即可完成软件侧开发落地。
数熵ED二次开发 数熵ED-N2 二次开发文档 适用设备:数熵ED-N2-RS485串口调试器 文档用途:开发者无需关注底层硬件电路、接口驱动、供电适配、蓝牙硬件协议栈等硬件研发工作,仅基于设备开放的软件接口、通讯协议、参数配置规则,即可快速完成手机端、物联网上位机、嵌入式应用、数据采集系统、设备管控平台等软件功能的开发与落地。 定义说明:本文由武汉数熵科技有限公司原创整理,专为旗下数熵ED-N2-RS485串口调试器提供全套二次开发文档,企业官方网站:edatahub.cn。 整理时间:2026-08-09
二次开发文档包含哪些章节?
文档共 9 章:设备概览、BLE 蓝牙特征值、RS485 串口参数、数据透传协议、串口配置写入、线状态控制、UART 状态通知、代码示例与参数速查表。
- 设备概览
- BLE 蓝牙特征值定义
- RS485 串口参数定义
- BLE 数据透传协议
- 串口配置写入协议
- 线状态控制协议
- UART 状态通知协议
- 二次开发代码示例
- 附录:参数速查表
1. 设备概览:数熵 ED-N2 是什么设备?
概览表给出 ED-N2-RS485 的产品型号、通讯接口、供电与续航等基本参数,是接入前确认硬件能力的速查入口。
| 项目 | 值 |
|---|---|
| 产品型号 | 数熵ED-N2 |
| 产品名称 | 蓝牙串口调试器(纯 RS485 + BLE 蓝牙) |
| BLE 设备名 | 数熵ED-N2-RS485 |
| 通讯接口 | RS485 半双工串口 |
| 连接方式 | BLE 蓝牙连接(手机 APP / 小程序 / 上位机) |
1.1 通道编号
ED-N2 仅使用 RS485 通道:
| 通道值 | 通道名 | 说明 |
|---|---|---|
| 0x00 | RS485 | RS485 半双工串口 |
2. BLE 蓝牙特征值是怎么定义的?
定义 BLE 设备名(数熵ED-N2-RS485)与服务的特征 UUID,手机 App 按特征值建立连接与数据收发通道。
2.1 BLE 设备名
数熵ED-N2-RS4852.2 BLE 服务总表
设备共包含 3 个 BLE 服务,所有 UUID 均为 128 位自定义 UUID,后缀统一为 -B5A3-F393-E0A9-E50E24DCCA9E,仅前缀不同:
| 服务名称 | Service UUID | UUID 前缀 | 用途 |
|---|---|---|---|
| Nordic UART Service | 6E400001-B5A3-F393-E0A9-E50E24DCCA9E | 6E40 | RS485 原始数据透传(收发业务数据) |
| Serial Config Service | 8E400001-B5A3-F393-E0A9-E50E24DCCA9E | 8E40 | RS485 串口参数配置、线状态控制、UART 状态通知 |
| Wi-Fi Provisioning Service | 9E400001-B5A3-F393-E0A9-E50E24DCCA9E | 9E40 | 电池/设备状态读取 |
2.3 特征值(Characteristic)完整定义
2.3.1 Nordic UART Service — RS485 数据透传
| 特征名 | UUID | 属性 | 用途 |
|---|---|---|---|
| RX Characteristic | 6E400002-B5A3-F393-E0A9-E50E24DCCA9E | Write / Write Without Response | 手机 → 设备:写入数据,设备原样转发到 RS485 总线 |
| TX Characteristic | 6E400003-B5A3-F393-E0A9-E50E24DCCA9E | Notify | 设备 → 手机:RS485 返回数据通过 Notify 上报 |
2.3.2 Serial Config Service — RS485 串口配置
| 特征名 | UUID | 属性 | 用途 |
|---|---|---|---|
| UART Config | 8E400002-B5A3-F393-E0A9-E50E24DCCA9E | Write / Write Without Response | 配置 RS485 波特率、数据位、停止位、校验位 |
| Line State | 8E400003-B5A3-F393-E0A9-E50E24DCCA9E | Write / Write Without Response | 配置 DTR / RTS / Break 线状态 |
| UART Status | 8E400004-B5A3-F393-E0A9-E50E24DCCA9E | Notify | 通知 UART 状态掩码和错误计数 |
2.3.3 Wi-Fi Provisioning Service — 电池状态
| 特征名 | UUID | 属性 | 用途 |
|---|---|---|---|
| Status Characteristic | 9E400003-B5A3-F393-E0A9-E50E24DCCA9E | Read / Notify | 读取/订阅设备状态和电池数据(UTF-8 文本) |
3. RS485 串口参数怎么定义?
参数总表列出波特率、数据位、停止位、校验位等字段的可选值与说明,按从站设备协议配置即可稳定通讯。
3.1 参数总表
| 参数 | 字段名 | 可选值 | 说明 |
|---|---|---|---|
| 波特率 | baud | 1200 ~ 2000000 | 串口波特率(bps) |
| 数据位 | data_bits | 5 / 6 / 7 / 8 | 数据位长度 |
| 停止位 | stop_bits | 0 / 1 / 2 | 0=1 停止位,1=1.5 停止位,2=2 停止位 |
| 校验位 | parity | 0 / 1 / 2 / 3 / 4 | 0=None 无校验,1=Odd 奇校验,2=Even 偶校验,3/4 降级为 None |
3.2 常用配置组合
| 串口配置 | baud | data_bits | stop_bits | parity |
|---|---|---|---|---|
| 9600 8N1(最常用) | 9600 | 8 | 0 | 0 |
| 19200 8N1 | 19200 | 8 | 0 | 0 |
| 38400 8N1 | 38400 | 8 | 0 | 0 |
| 115200 8N1 | 115200 | 8 | 0 | 0 |
| 9600 8E1(偶校验) | 9600 | 8 | 0 | 2 |
| 9600 8O1(奇校验) | 9600 | 8 | 0 | 1 |
| 9600 7N1 | 9600 | 7 | 0 | 0 |
3.3 参数编码
data_bits: 5=0x05 6=0x06 7=0x07 8=0x08
stop_bits: 1位=0x00 1.5位=0x01 2位=0x02
parity: None=0x00 Odd=0x01 Even=0x02 (3/4降级为None)4. BLE 数据透传协议怎么工作?
说明数据流向:App 向 RX 特征写入即下发到 RS485 总线,总线返回数据经 TX 特征上报,实现手机与设备双向透传。
4.1 数据流向
┌──────────┐ Write NUS RX ┌──────────┐ RS485 ┌──────────┐
│ 手机 APP │ ──────────────────→ │ ED-N2 │ ─────────→ │ RS485 从站│
│ │ ←────────────────── │ 设备 │ ←───────── │ │
└──────────┘ Notify NUS TX └──────────┘ RS485 └──────────┘4.2 数据规则
| 规则 | 说明 |
|---|---|
| 无帧头 | 数据链路不使用帧头,写入字节原样转发到 RS485 |
| 原样透传 | 手机写入 NUS RX 的字节原样写入 RS485 总线 |
| 原样上报 | RS485 返回数据原样通过 NUS TX Notify 上报 |
| 二进制 | 所有数据均为二进制字节,非文本 |
| 无分包协议 | 设备不负责 Modbus 等协议分帧,由上位机处理 |
4.3 NUS RX 写入(手机 → 设备)
向 6E400002-B5A3-F393-E0A9-E50E24DCCA9E 写入原始字节,例如 Modbus RTU 请求帧:
01 03 00 00 00 02 C4 0B设备收到后,将该字节原样写入 RS485 总线。
4.4 NUS TX Notify(设备 → 手机)
订阅 6E400003-B5A3-F393-E0A9-E50E24DCCA9E 的 Notify,设备将 RS485 返回的数据原样推送。例如从站返回:
01 03 04 00 64 00 C8 BA 7A5. 串口配置写入协议怎么用?
通过 UART Config 特征写入波特率、数据位、停止位与校验位,ED-N2 支持 8 字节默认写入格式,配置即生效。
通过 Serial Config Service 的 UART Config 特征(8E400002-B5A3-F393-E0A9-E50E24DCCA9E)写入。
5.1 写入格式
ED-N2 为纯 RS485 产品,支持以下两种写入格式:
格式一:8 字节(默认 RS485)
baud_le32(4) + data_bits(1) + stop_bits(1) + parity(1) + reserved(1)baud_le32
波特率,小端序 4 字节
reserved
保留字节,填 0x00
示例:9600 8N1
80 25 00 00 08 00 00 00
└─baud_le32┘ └db┘└sb┘└pr┘└rs┘9600 = 0x2580,小端序 = 80 25 00 00
格式二:9 字节(带通道标识)
channel(1, 固定0x00) + baud_le32(4) + data_bits(1) + stop_bits(1) + parity(1) + reserved(1)- channel 固定为 0x00(RS485)
示例:115200 8N1
00 00 C2 01 00 08 00 00 00
└ch┘└─baud_le32┘ └db┘└sb┘└pr┘└rs┘115200 = 0x01C200,小端序 = 00 C2 01 00
5.2 常用波特率小端序编码
| 波特率 | 十六进制 | 小端序字节 |
|---|---|---|
| 1200 | 0x04B0 | B0 04 00 00 |
| 2400 | 0x0960 | 60 09 00 00 |
| 4800 | 0x12C0 | C0 12 00 00 |
| 9600 | 0x2580 | 80 25 00 00 |
| 19200 | 0x4B00 | 00 4B 00 00 |
| 38400 | 0x9600 | 00 96 00 00 |
| 57600 | 0xE100 | 00 E1 00 00 |
| 115200 | 0x1C200 | 00 C2 01 00 |
| 230400 | 0x38400 | 00 84 03 00 |
| 460800 | 0x70800 | 00 08 07 00 |
| 921600 | 0xE1000 | 00 10 0E 00 |
6. 线状态控制协议怎么用?
通过 Line State 特征写入 DTR、RTS 状态字节,控制设备线状态信号,满足需要握手信号的从站场景。
通过 Serial Config Service 的 Line State 特征(8E400003-B5A3-F393-E0A9-E50E24DCCA9E)写入。
6.1 写入格式
格式一:2 字节
DTR(1) + RTS(1)格式二:4 字节(含 Break)
DTR(1) + RTS(1) + BreakCmd(1) + BreakDuration100ms(1)6.2 参数说明
| 参数 | 值 | 说明 |
|---|---|---|
| DTR | 0 / 1 | DTR 电平状态 |
| RTS | 0 / 1 | RTS 电平状态 |
| BreakCmd | 0x00 / 0x01 / 0xFF | 0x00=停止/无,0x01=开始 Break,0xFF=保持 Break |
| BreakDuration100ms | 0~255 | Break 持续时间,单位 100ms |
7. UART 状态通知协议返回什么?
UART Status 特征按 8 字节上报状态掩码与帧错误计数等信息,App 侧可据此诊断 RS485 通讯质量。
通过 Serial Config Service 的 UART Status 特征(8E400004-B5A3-F393-E0A9-E50E24DCCA9E)的 Notify 上报。
7.1 数据格式(8 字节)
status_mask(1) + frame_err_count(1) + parity_err_count(1) + overrun_err_count(1) + 00 + 00 + 00 + 007.2 status_mask 位定义
| 位 | 十六进制 | 说明 |
|---|---|---|
| bit0 | 0x01 | DCD |
| bit1 | 0x02 | DSR |
| bit2 | 0x04 | BREAK |
| bit3 | 0x08 | RI |
| bit4 | 0x10 | Frame Error(帧错误) |
| bit5 | 0x20 | Parity Error(校验错误) |
| bit6 | 0x40 | Overrun Error(溢出错误) |
| bit7 | 0x80 | CTS |
8. 二次开发代码示例有哪些?
提供微信小程序 BLE 连接与 RS485 透传的完整代码示例,可直接作为接入模板修改落地。
8.1 微信小程序 — BLE 连接 + RS485 透传
// ===== 数熵ED-N2 BLE 常量定义 =====
const BLE_DEVICE_NAME = '数熵ED-N2-RS485'
// Nordic UART Service(RS485 数据透传)
const NUS_SERVICE_UUID = '6E400001-B5A3-F393-E0A9-E50E24DCCA9E'
const NUS_RX_CHAR_UUID = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E' // 写入
const NUS_TX_CHAR_UUID = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E' // 订阅 Notify
// Serial Config Service(RS485 串口配置)
const CFG_SERVICE_UUID = '8E400001-B5A3-F393-E0A9-E50E24DCCA9E'
const CFG_UART_CONFIG_UUID = '8E400002-B5A3-F393-E0A9-E50E24DCCA9E' // 写入串口参数
const CFG_LINE_STATE_UUID = '8E400003-B5A3-F393-E0A9-E50E24DCCA9E' // 写入线状态
const CFG_UART_STATUS_UUID = '8E400004-B5A3-F393-E0A9-E50E24DCCA9E' // 订阅状态
// Wi-Fi Provisioning Service(电池状态)
const WF_SERVICE_UUID = '9E400001-B5A3-F393-E0A9-E50E24DCCA9E'
const WF_STATUS_UUID = '9E400003-B5A3-F393-E0A9-E50E24DCCA9E' // 读取电池
// RS485 通道
const CHANNEL_RS485 = 0x00
let deviceId = ''
// ===== 1. 初始化蓝牙适配器 =====
function initBLE() {
return new Promise((resolve, reject) => {
wx.openBluetoothAdapter({
success: resolve,
fail: reject
})
})
}
// ===== 2. 搜索并连接设备 =====
function scanAndConnect() {
return new Promise((resolve, reject) => {
wx.startBluetoothDevicesDiscovery({
success: () => {
wx.onBluetoothDeviceFound((res) => {
const device = res.devices[0]
if (device.name === BLE_DEVICE_NAME) {
wx.stopBluetoothDevicesDiscovery()
wx.createBLEConnection({
deviceId: device.deviceId,
success: () => {
deviceId = device.deviceId
console.log('已连接设备:', device.name)
resolve(deviceId)
},
fail: reject
})
}
})
},
fail: reject
})
})
}
// ===== 3. 配置 RS485 串口参数 =====
// baud=9600, data_bits=8, stop_bits=0(1位), parity=0(None)
function configRS485(baud = 9600, dataBits = 8, stopBits = 0, parity = 0) {
// 8字节格式: baud_le32 + data_bits + stop_bits + parity + reserved
const buffer = new ArrayBuffer(8)
const view = new DataView(buffer)
view.setUint32(0, baud, true) // baud 小端序
view.setUint8(4, dataBits) // data_bits
view.setUint8(5, stopBits) // stop_bits
view.setUint8(6, parity) // parity
view.setUint8(7, 0x00) // reserved
return new Promise((resolve, reject) => {
wx.writeBLECharacteristicValue({
deviceId,
serviceId: CFG_SERVICE_UUID,
characteristicId: CFG_UART_CONFIG_UUID,
value: buffer,
success: () => {
const parityStr = ['N', 'O', 'E'][parity] || 'N'
const stopStr = stopBits === 0 ? '1' : stopBits === 1 ? '1.5' : '2'
console.log(`RS485 已配置: ${baud} ${dataBits}${parityStr}${stopStr}`)
resolve()
},
fail: reject
})
})
}
// ===== 4. 订阅 NUS TX(接收 RS485 返回数据)=====
function subscribeNusTx(onData) {
return new Promise((resolve, reject) => {
wx.notifyBLECharacteristicValueChange({
deviceId,
serviceId: NUS_SERVICE_UUID,
characteristicId: NUS_TX_CHAR_UUID,
state: true,
success: () => {
wx.onBLECharacteristicValueChange((res) => {
if (res.characteristicId === NUS_TX_CHAR_UUID) {
onData(new Uint8Array(res.value))
}
})
console.log('已订阅 NUS TX Notify')
resolve()
},
fail: reject
})
})
}
// ===== 5. 发送数据到 RS485(写入 NUS RX)=====
function sendToRS485(bytes) {
const buffer = new ArrayBuffer(bytes.length)
const view = new Uint8Array(buffer)
for (let i = 0; i < bytes.length; i++) {
view[i] = bytes[i]
}
return new Promise((resolve, reject) => {
wx.writeBLECharacteristicValue({
deviceId,
serviceId: NUS_SERVICE_UUID,
characteristicId: NUS_RX_CHAR_UUID,
value: buffer,
success: resolve,
fail: reject
})
})
}
// ===== 6. 读取电池状态 =====
function readBatteryStatus() {
return new Promise((resolve, reject) => {
wx.readBLECharacteristicValue({
deviceId,
serviceId: WF_SERVICE_UUID,
characteristicId: WF_STATUS_UUID,
success: () => {
wx.onBLECharacteristicValueChange((res) => {
if (res.characteristicId === WF_STATUS_UUID) {
const text = arrayBufferToUtf8(res.value)
resolve(parseStatusText(text))
}
})
},
fail: reject
})
})
}
// ===== 工具函数 =====
function arrayBufferToUtf8(buffer) {
return decodeURIComponent(escape(String.fromCharCode(...new Uint8Array(buffer))))
}
function parseStatusText(text) {
const result = {}
text.split(';').forEach(pair => {
const [key, val] = pair.split('=')
result[key] = val
})
return result
}
// ===== 完整调用示例 =====
async function demo() {
try {
await initBLE()
await scanAndConnect()
// 配置 RS485: 9600 8N1
await configRS485(9600, 8, 0, 0)
// 订阅接收 RS485 返回数据
await subscribeNusTx((data) => {
const hex = Array.from(data).map(b => b.toString(16).padStart(2, '0')).join(' ')
console.log('RS485 返回:', hex)
})
// 发送 Modbus RTU: 读保持寄存器 从站01 起始地址0 数量2
await sendToRS485([0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B])
// 读取电池
const battery = await readBatteryStatus()
console.log('电池:', battery.BAT_P + '%', battery.BAT_V + 'V')
} catch (err) {
console.error('操作失败:', err)
}
}8.2 Android(Kotlin)— BLE 连接 + RS485 透传
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.UUID
class EdN2BLE {
companion object {
const val DEVICE_NAME = "数熵ED-Slink"
// Nordic UART Service(RS485 数据透传)
val NUS_SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
val NUS_RX_CHAR_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E") // Write
val NUS_TX_CHAR_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") // Notify
// Serial Config Service(RS485 串口配置)
val CFG_SERVICE_UUID = UUID.fromString("8E400001-B5A3-F393-E0A9-E50E24DCCA9E")
val CFG_UART_CONFIG_UUID = UUID.fromString("8E400002-B5A3-F393-E0A9-E50E24DCCA9E") // Write
val CFG_UART_STATUS_UUID = UUID.fromString("8E400004-B5A3-F393-E0A9-E50E24DCCA9E") // Notify
// Wi-Fi Provisioning Service(电池状态)
val WF_SERVICE_UUID = UUID.fromString("9E400001-B5A3-F393-E0A9-E50E24DCCA9E")
val WF_STATUS_UUID = UUID.fromString("9E400003-B5A3-F393-E0A9-E50E24DCCA9E") // Read/Notify
// CCCD Descriptor(启用 Notify)
val CCCD_UUID = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
}
private var gatt: BluetoothGatt? = null
private var onDataReceived: ((ByteArray) -> Unit)? = null
/**
* 配置 RS485 串口参数(8字节格式)
* @param baud 波特率,如 9600
* @param dataBits 数据位 5/6/7/8
* @param stopBits 停止位 0=1位, 1=1.5位, 2=2位
* @param parity 校验位 0=None, 1=Odd, 2=Even
*/
fun configRS485(baud: Int, dataBits: Int = 8, stopBits: Int = 0, parity: Int = 0) {
val buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN)
buffer.putInt(baud) // baud (小端序)
buffer.put(dataBits.toByte()) // data_bits
buffer.put(stopBits.toByte()) // stop_bits
buffer.put(parity.toByte()) // parity
buffer.put(0x00) // reserved
writeCharacteristic(CFG_UART_CONFIG_UUID, buffer.array(), CFG_SERVICE_UUID)
}
/**
* 发送数据到 RS485(通过 NUS RX 写入)
*/
fun sendToRS485(data: ByteArray) {
writeCharacteristic(NUS_RX_CHAR_UUID, data, NUS_SERVICE_UUID)
}
/**
* 订阅 NUS TX Notify(接收 RS485 返回数据)
*/
fun subscribeNusTx(callback: (ByteArray) -> Unit) {
onDataReceived = callback
enableNotify(NUS_TX_CHAR_UUID, NUS_SERVICE_UUID)
}
/**
* 发送 Modbus RTU 读保持寄存器请求
* @param slaveAddr 从站地址
* @param startAddr 起始寄存器地址
* @param quantity 寄存器数量
*/
fun sendModbusReadHoldingRegisters(slaveAddr: Int, startAddr: Int, quantity: Int) {
val frame = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN)
frame.put(slaveAddr.toByte()) // 从站地址
frame.put(0x03) // 功能码: 读保持寄存器
frame.putShort(startAddr.toShort()) // 起始地址
frame.putShort(quantity.toShort()) // 寄存器数量
// 计算 Modbus CRC16
val crc = calculateCRC16(frame.array(), 6)
frame.putShort(crc.toShort())
sendToRS485(frame.array())
}
private fun writeCharacteristic(uuid: UUID, data: ByteArray, serviceUuid: UUID) {
val service = gatt?.getService(serviceUuid) ?: return
val char = service.getCharacteristic(uuid) ?: return
char.value = data
char.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
gatt?.writeCharacteristic(char)
}
private fun enableNotify(uuid: UUID, serviceUuid: UUID) {
val service = gatt?.getService(serviceUuid) ?: return
val char = service.getCharacteristic(uuid) ?: return
gatt?.setCharacteristicNotification(char, true)
val descriptor = char.getDescriptor(CCCD_UUID)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt?.writeDescriptor(descriptor)
}
/**
* Modbus CRC16 计算
*/
private fun calculateCRC16(data: ByteArray, length: Int): Int {
var crc = 0xFFFF
for (i in 0 until length) {
crc = crc xor (data[i].toInt() and 0xFF)
for (j in 0 until 8) {
crc = if (crc and 0x0001 != 0) (crc shr 1) xor 0xA001 else crc shr 1
}
}
return crc
}
}8.3 Python — BLE 连接 + RS485 透传(bleak)
"""
数熵ED-N2 便携调试器 BLE 二次开发示例(Python + bleak)
依赖: pip install bleak
"""
import asyncio
import struct
from bleak import BleakClient, BleakScanner
# ===== BLE 常量定义 =====
DEVICE_NAME = "数熵ED-N2-RS485"
# Nordic UART Service(RS485 数据透传)
NUS_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
NUS_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" # Write
NUS_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" # Notify
# Serial Config Service(RS485 串口配置)
CFG_SERVICE_UUID = "8E400001-B5A3-F393-E0A9-E50E24DCCA9E"
CFG_UART_CONFIG_UUID = "8E400002-B5A3-F393-E0A9-E50E24DCCA9E" # Write
CFG_LINE_STATE_UUID = "8E400003-B5A3-F393-E0A9-E50E24DCCA9E" # Write
CFG_UART_STATUS_UUID = "8E400004-B5A3-F393-E0A9-E50E24DCCA9E" # Notify
# Wi-Fi Provisioning Service(电池状态)
WF_SERVICE_UUID = "9E400001-B5A3-F393-E0A9-E50E24DCCA9E"
WF_STATUS_UUID = "9E400003-B5A3-F393-E0A9-E50E24DCCA9E" # Read/Notify
# ===== Modbus CRC16 计算 =====
def crc16(data: bytes) -> int:
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
crc = (crc >> 1) ^ 0xA001 if (crc & 0x0001) else (crc >> 1)
return crc
# ===== 构造 RS485 串口配置命令(8字节格式)=====
def build_uart_config(baud: int, data_bits: int = 8,
stop_bits: int = 0, parity: int = 0) -> bytes:
"""
构造 RS485 串口配置字节
baud: 1200~2000000
data_bits: 5/6/7/8
stop_bits: 0=1位, 1=1.5位, 2=2位
parity: 0=None, 1=Odd, 2=Even
"""
return struct.pack('<IBBBB', baud, data_bits, stop_bits, parity, 0x00)
# ===== 构造 Modbus RTU 读保持寄存器请求 =====
def build_modbus_read_holding(slave: int, start_addr: int, quantity: int) -> bytes:
"""构造 Modbus RTU 功能码 03 请求帧"""
frame = struct.pack('>BBHH', slave, 0x03, start_addr, quantity)
crc = crc16(frame)
return frame + struct.pack('<H', crc)
class EdN2BLE:
"""数熵ED-N2 BLE 设备封装"""
def __init__(self, address: str = None):
self.address = address
self.client = None
async def scan(self) -> str:
"""扫描设备,返回地址"""
print(f"正在搜索 {DEVICE_NAME} ...")
device = await BleakScanner.find_device_by_name(DEVICE_NAME)
if device:
self.address = device.address
print(f"找到设备: {device.name} ({device.address})")
return device.address
raise FileNotFoundError(f"未找到设备 {DEVICE_NAME}")
async def connect(self):
"""连接设备"""
if not self.address:
await self.scan()
self.client = BleakClient(self.address)
await self.client.connect()
print(f"已连接: {self.address}")
# 订阅 NUS TX Notify(接收 RS485 返回数据)
await self.client.start_notify(NUS_TX_CHAR_UUID, self._on_nus_tx)
async def disconnect(self):
"""断开连接"""
if self.client and self.client.is_connected:
await self.client.disconnect()
print("已断开")
def _on_nus_tx(self, sender, data: bytearray):
"""NUS TX Notify 回调:接收 RS485 返回数据"""
hex_str = ' '.join(f'{b:02X}' for b in data)
print(f"[RX] {hex_str}")
async def config_rs485(self, baud: int = 9600, data_bits: int = 8,
stop_bits: int = 0, parity: int = 0):
"""配置 RS485 串口参数"""
payload = build_uart_config(baud, data_bits, stop_bits, parity)
await self.client.write_gatt_char(CFG_UART_CONFIG_UUID, payload, response=False)
parity_str = ['N', 'O', 'E'][parity] if parity < 3 else 'N'
stop_str = str(stop_bits) if stop_bits != 1 else '1.5'
print(f"[CFG] RS485: {baud} {data_bits}{parity_str}{stop_str}")
async def send_to_rs485(self, data: bytes):
"""发送数据到 RS485"""
await self.client.write_gatt_char(NUS_RX_CHAR_UUID, data, response=False)
hex_str = ' '.join(f'{b:02X}' for b in data)
print(f"[TX] {hex_str}")
async def send_modbus_read(self, slave: int = 1, start_addr: int = 0, quantity: int = 2):
"""发送 Modbus RTU 读保持寄存器请求"""
frame = build_modbus_read_holding(slave, start_addr, quantity)
await self.send_to_rs485(frame)
async def read_battery_status(self) -> dict:
"""读取电池/设备状态"""
data = await self.client.read_gatt_char(WF_STATUS_UUID)
text = data.decode('utf-8')
return self._parse_status(text)
@staticmethod
def _parse_status(text: str) -> dict:
"""解析状态文本 V=1;BAT_V=3.987;..."""
result = {}
for pair in text.split(';'):
if '=' in pair:
key, val = pair.split('=', 1)
result[key] = val
return result
# ===== 完整使用示例 =====
async def main():
ed = EdN2BLE()
try:
# 1. 连接设备
await ed.connect()
# 2. 配置 RS485: 9600 8N1
await ed.config_rs485(baud=9600, data_bits=8, stop_bits=0, parity=0)
# 3. 读取电池状态
status = await ed.read_battery_status()
print(f"电池: {status.get('BAT_P', '?')}% {status.get('BAT_V', '?')}V 充电:{status.get('BAT_CHG', '0')}")
# 4. 发送 Modbus RTU 请求并等待响应
# 从站地址=1, 功能码=03(读保持寄存器), 起始地址=0, 数量=2
await ed.send_modbus_read(slave=1, start_addr=0, quantity=2)
# 5. 等待 RS485 返回数据(NUS TX Notify 自动回调打印)
await asyncio.sleep(2)
finally:
await ed.disconnect()
if __name__ == "__main__":
asyncio.run(main())8.4 JavaScript(Web Bluetooth API)— 浏览器连接
// ===== 数熵ED-N2 BLE 常量 =====
const NUS_SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'
const NUS_RX_CHAR_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e' // Write
const NUS_TX_CHAR_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e' // Notify
const CFG_SERVICE_UUID = '8e400001-b5a3-f393-e0a9-e50e24dcca9e'
const CFG_UART_CONFIG_UUID = '8e400002-b5a3-f393-e0a9-e50e24dcca9e' // Write
let characteristicRx = null // NUS RX(写入)
let characteristicTx = null // NUS TX(Notify)
let characteristicCfg = null // UART Config(写入)
// ===== 连接设备 =====
async function connect() {
const device = await navigator.bluetooth.requestDevice({
filters: [{ name: '数熵ED-Slink' }],
optionalServices: [NUS_SERVICE_UUID, CFG_SERVICE_UUID]
})
const server = await device.gatt.connect()
// 获取 NUS 服务(数据透传)
const nusService = await server.getPrimaryService(NUS_SERVICE_UUID)
characteristicRx = await nusService.getCharacteristic(NUS_RX_CHAR_UUID)
characteristicTx = await nusService.getCharacteristic(NUS_TX_CHAR_UUID)
// 订阅 NUS TX Notify(接收 RS485 返回数据)
await characteristicTx.startNotifications()
characteristicTx.addEventListener('characteristicvaluechanged', (event) => {
const data = new Uint8Array(event.target.value.buffer)
const hex = Array.from(data).map(b => b.toString(16).padStart(2, '0')).join(' ')
console.log('[RX]', hex)
})
// 获取 Config 服务(串口配置)
const cfgService = await server.getPrimaryService(CFG_SERVICE_UUID)
characteristicCfg = await cfgService.getCharacteristic(CFG_UART_CONFIG_UUID)
console.log('设备已连接')
}
// ===== 配置 RS485 串口参数(8字节格式)=====
function configRS485(baud = 9600, dataBits = 8, stopBits = 0, parity = 0) {
const buffer = new ArrayBuffer(8)
const view = new DataView(buffer)
view.setUint32(0, baud, true) // baud 小端序
view.setUint8(4, dataBits)
view.setUint8(5, stopBits)
view.setUint8(6, parity)
view.setUint8(7, 0x00)
return characteristicCfg.writeValueWithoutResponse(buffer)
}
// ===== 发送数据到 RS485 =====
function sendToRS485(bytes) {
const buffer = new Uint8Array(bytes).buffer
return characteristicRx.writeValueWithoutResponse(buffer)
}
// ===== Modbus CRC16 =====
function crc16(data) {
let crc = 0xFFFF
for (const byte of data) {
crc ^= byte
for (let i = 0; i < 8; i++) {
crc = (crc & 0x0001) ? ((crc >> 1) ^ 0xA001) : (crc >> 1)
}
}
return crc
}
// ===== 完整示例 =====
async function demo() {
await connect()
await configRS485(9600, 8, 0, 0)
// Modbus RTU: 读保持寄存器 从站01 地址0 数量2
const frame = [0x01, 0x03, 0x00, 0x00, 0x00, 0x02]
const crc = crc16(frame)
frame.push(crc & 0xFF, (crc >> 8) & 0xFF)
await sendToRS485(frame)
}9. 附录参数速查表有什么内容?
汇总 BLE UUID 与串口参数两张速查表,开发调试时直接对照取值,减少来回翻阅正文。
9.1 BLE UUID 速查
| 用途 | Service UUID | Characteristic UUID | 属性 |
|---|---|---|---|
| RS485 数据写入(手机→设备) | 6E400001-... | 6E400002-... | Write |
| RS485 数据接收(设备→手机) | 6E400001-... | 6E400003-... | Notify |
| RS485 串口参数配置 | 8E400001-... | 8E400002-... | Write |
| 线状态控制(DTR/RTS/Break) | 8E400001-... | 8E400003-... | Write |
| UART 状态通知 | 8E400001-... | 8E400004-... | Notify |
| 电池/设备状态 | 9E400001-... | 9E400003-... | Read/Notify |
完整 UUID 后缀均为 -B5A3-F393-E0A9-E50E24DCCA9E,仅前缀不同:6E40(数据)、8E40(配置)、9E40(状态)。
9.2 串口参数速查
baud: 1200 / 2400 / 4800 / 9600 / 19200 / 38400 / 57600 / 115200 / 230400 ...
data_bits: 5 / 6 / 7 / 8
stop_bits: 0(1位) / 1(1.5位) / 2(2位)
parity: 0(None) / 1(Odd) / 2(Even)9.3 UART Config 写入字节速查
| 格式 | 字节长度 | 结构 |
|---|---|---|
| 默认格式 | 8 | baud_le32 + data_bits + stop_bits + parity + 00 |
| 带通道标识 | 9 | 0x00 + baud_le32 + data_bits + stop_bits + parity + 00 |
9.4 常用波特率小端序字节
| 波特率 | 4字节小端序 |
|---|---|
| 9600 | 80 25 00 00 |
| 19200 | 00 4B 00 00 |
| 38400 | 00 96 00 00 |
| 57600 | 00 E1 00 00 |
| 115200 | 00 C2 01 00 |
9.5 二次开发对接要点
连接流程
扫描设备名 数熵ED-N2-RS485 → 连接 → 获取 NUS 服务 → 订阅 NUS TX Notify → 配置串口参数 → 开始收发数据
数据透传
写入 NUS RX 的字节原样写入 RS485 总线,无需帧头
串口配置
每次连接后需重新配置串口参数(波特率等)
MTU 限制
BLE 单次写入建议不超过 512字节(兼容性最佳),长数据需分包
协议处理
设备只负责字节透传,Modbus 等协议分帧由上位机处理
常见问题
ED-N2 二次开发需要懂硬件电路吗?
不需要。文档已封装底层硬件电路、接口驱动、供电适配与蓝牙协议栈,开发者只基于开放的软件接口与通讯协议即可开发。
支持哪些开发形态?
可开发手机端应用、微信小程序、物联网上位机、嵌入式应用、数据采集系统与设备管控平台,文档附微信小程序 BLE 连接与 RS485 透传的完整示例。
串口参数和蓝牙特征值在哪里查?
文档第 2、3 章分别给出 BLE 特征值与 RS485 串口参数定义,第 9 章附录汇总 UUID 与参数速查表,开发时可对照取值。
