Android RadioTransport 统一传输层
SDK Version: 1.0.0
Package:com.xtalk.mesh.sdk.transport
RadioTransport 让 Auth、Init 和业务层用同一套接口访问 BLE 或客户 UART。它不打开物理通道;构造前必须先完成 BLE 连接或打开 UART。
1. 模型与枚举
| 类型 | 值/字段 | 用途 |
|---|---|---|
RadioTransportMode | BLE, UART | 当前物理通道 |
RadioSendResult | SEND_OK, SEND_FAIL_NOT_CONNECTED, SEND_FAIL_INTERNAL | RF 发送受理结果 |
AtCommandErrorPolicy | FAIL_FAST, COLLECT_UNTIL_COMPLETE | 遇到错误行立即失败或让自定义完成条件收集完整响应 |
RadioPacketSource | BLE_DIRECT, BLE_PASSTHROUGH_DI, UART_DI, UART_HEX | 入站包来源 |
RadioInboundPacket | payload, source, snr, rssi, receivedAtNanos | RF 入站数据;payload 是业务字节副本 |
RadioCrcErrorEvent | source, slot, snr, rssi, rawLine, receivedAtNanos | 可结构化的 CRC 错误;缺失元数据为 null |
RadioPacketListener | onPacket(packet) | 普通或实时包回调 |
RadioCrcErrorListener | onCrcError(event) | CRC 回调 |
监听器执行线程由具体 transport 决定,默认按高频 I/O 回调处理:不要阻塞,不要直接操作 View,把 UI 更新切到主线程。
2. RadioTransport 每个接口
共同前置条件:AT 和 RF 方法在后台线程调用;Auth/Init/自定义 AT 不可并发。timeoutMs 单位毫秒,超时不代表设备一定未执行命令,因此超时后先查询状态再重试。
| 接口 | 参数/默认值 | 返回、错误与配对操作 |
|---|---|---|
mode, label | 无 | 当前通道枚举与日志标签 |
isOpenOrConnected() | 无 | 物理链路是否打开;不代表 Auth/Init 完成 |
isReadyForSend() | 无 | 是否满足业务发送门槛 |
localDeviceLongId() | 无 | 已认证 DID;未知为 null |
runAtCommand(command, timeoutMs = 3000, log = true, errorPolicy = FAIL_FAST, stopWhen = null) | AT 文本、自定义完成条件 | 返回响应行;未连接、发送失败、设备错误或超时可抛异常 |
runAtCommandDrainingResponseTail(...) | stopWhen 必填 | 匹配业务载荷后仍持有锁到终止 OK/ERROR 或原截止时间,防止迟到 OK 污染下一条命令;不支持的实现抛 UnsupportedOperationException |
sendAtCommand(command) | AT 文本 | 等 SDK 发送结果,成功为 true;不返回响应行 |
sendAtCommandNoWait(command) | AT 文本 | 只报告是否受理;调用方不能据此判断设备执行成功 |
sendRfPayload(payload, timeoutMs = 3000) | 非空业务帧 | RadioSendResult;普通可靠业务首选 |
sendRfPayloadAndWaitSendFinish(payload, timeoutMs = 3000) | 非空业务帧 | 等设备 SEND_FINISH;成功为 true |
sendRfPayloadNoWait(payload) | 非空实时帧 | 低延迟受理结果;无设备完成确认,适合实时语音,调用方负责节流 |
addPacketListener / removePacketListener | 同一个 listener 实例 | 普通 RF 包生命周期配对 |
addRealtimePacketListener / removeRealtimePacketListener | 同一个 listener 实例 | 实时高优先级包生命周期配对 |
addCrcErrorListener / removeCrcErrorListener | 同一个 listener 实例 | CRC 事件配对;具体 bridge 必须有 CRC 解析源 |
setExpectedRealtimeSourceShortId(shortId) | null 清除 | UART/高级实现可用它过滤实时源;BLE 实现可能无操作 |
isRecentLocalRealtimeEcho(payload) | 完整帧 | 是否是近期本机实时回声;不支持的实现返回 false |
setFrequencyHz(freqHz) | 获准频率 Hz | 全通道设置成功为 true;失败后不要发送 |
setRateMode(rateMode) | 设备支持的模式 | 成功为 true |
queryRateMode(timeoutMs = 1500) | 超时 | 当前模式;解析/响应失败为 null |
queryWorkMode(timeoutMs = 1500) | 超时 | 当前模式;解析/响应失败为 null |
restoreTextPttDefaults(defaultRateMode, defaultAddtl = 1, defaultWorkMode = 21) | 三项默认参数 | 全部恢复成功为 true;通话/文件传输结束后调用 |
val lines = withContext(Dispatchers.IO) {
radio.runAtCommand("AT+RATE?", timeoutMs = 1_500L)
}
val sent = withContext(Dispatchers.IO) {
radio.sendRfPayload(payload, timeoutMs = 3_000L)
}
3. BleRadioTransport
val radio: RadioTransport = BleRadioTransport(
eventBridge = customerEventBridge,
clearRealtimeNoWaitMark = { echoTracker.clear() },
markRealtimeNoWaitSend = { bytes -> echoTracker.mark(bytes) },
)
- 前置:
XTalkBleClient.ensureInitialized()成功;发送前 BLE 已连接,Auth/Init 已完成。 eventBridge把底层入站事件映射给 transport listeners;完整 BLE 示例见完整接入示例。- 两个 realtime mark 回调用于客户的本地回声识别。不需要时可传空 lambda。
BleRadioTransport不拥有 BLE 连接;销毁时仍由客户移除 listeners 并调用XTalkBleClient.disconnect()。
4. RadioTransportEventBridge
接口包含三组严格配对方法:普通包、实时包、CRC。实现必须保存 listener → 底层 callback 映射,移除时使用原 callback;不能每次重新创建 callback。普通 BLE 数据桥示例已放在完整接入示例。
5. 客户 UART 适配器
如果客户没有交付的 UART transport,可按接口包装已有驱动。下面只展示契约骨架;响应分行、DI 解码、CRC、互斥锁和超时必须由实现补齐:
class CustomerUartRadioTransport(
private val uart: CustomerUartDriver,
) : RadioTransport {
override val mode = RadioTransportMode.UART
override val label = "customer-uart"
override fun isOpenOrConnected() = uart.isOpen
override fun isReadyForSend() = uart.isOpen && uart.did != null && uart.initialized
override fun localDeviceLongId() = uart.did
override fun runAtCommand(
command: String,
timeoutMs: Long,
log: Boolean,
errorPolicy: AtCommandErrorPolicy,
stopWhen: ((List<String>) -> Boolean)?,
): List<String> = uart.runExclusiveAt(command, timeoutMs, errorPolicy, stopWhen)
override fun sendAtCommand(command: String) = uart.sendAt(command, waitWrite = true)
override fun sendAtCommandNoWait(command: String) = uart.sendAt(command, waitWrite = false)
override fun sendRfPayload(payload: ByteArray, timeoutMs: Long) =
if (uart.sendRf(payload, timeoutMs)) RadioSendResult.SEND_OK
else RadioSendResult.SEND_FAIL_INTERNAL
override fun sendRfPayloadAndWaitSendFinish(payload: ByteArray, timeoutMs: Long) =
uart.sendRfAndAwaitFinish(payload, timeoutMs)
override fun sendRfPayloadNoWait(payload: ByteArray) = uart.sendRfNoWait(payload)
// add/remove listeners、频率/速率查询设置和默认恢复按同名接口委托给客户驱动。
// 不支持的能力必须返回 false/null 或明确抛 UnsupportedOperationException,不能伪造成功。
}
用于 Auth 的最小 UART 只需实现 DeviceAuthenticator.AtCommandTransport;用于 Init 的最小 UART 只需实现 DeviceInitializer.AtCommandTransport。只有业务层需要统一切换时才实现完整 RadioTransport。
6. RadioTransportRouter
val router = RadioTransportRouter(bleTransport, uartTransport, RadioTransportMode.BLE)
router.addPacketListener(packetListener)
router.setMode(RadioTransportMode.UART)
val active = router.currentTransport()
setMode 会把 Router 已保存的三类 listener 从旧 transport 移到新 transport。切换前客户必须保证新物理通道已打开且完成对应 Auth/Init。销毁时仍需逐个 remove;Router 不关闭底层通道。
7. Advanced:入站解析工具
这些是低层 transport 作者使用的公开工具,普通 BLE 接入不需要直接调用:
| API | 契约 |
|---|---|
AtLineBuffer.hasBufferedData() | 是否还有未终止的半行 |
AtLineBuffer.append(bytes, onLine) | 按 CR/LF 组装行;二参数 callback 同时给出组装毫秒数;线程安全但 callback 应快速返回 |
AtResponsePolicy.isOkLine/isErrorLine/isSendFinishLine | 识别兼容终止行 |
AtResponsePolicy.isResponseTailOkLine/isResponseTailErrorLine | 严格 tail-drain 终止判断 |
AtResponsePolicy.isFreqProbeAccepted/freqProbeStatus | 返回 NORMAL_FREQ、NEEDS_VERIFY 或 null |
AtResponseTailDrainTracker.observe | 返回 CONTINUE/COMPLETE/ERROR;payloadMatched 一旦为真不会回退 |
BinaryEscape.escape/unescape | 对 CR、LF、转义字节做可逆封装;不修改输入数组 |
DiFrameParser.parseLine/parseBytes | 解析 +DI/AT+DI;失败为 null,lengthMismatch 必须作为丢包依据 |
DiFrameParser.parseDiMeta | 提取 DiMeta 的计数、长度、slot、SNR/RSSI、TXP;缺字段为 null |
DiMeta.hasInvalidRemoteFeedback() | 远端反馈组合是否无效 |
CrcErrorParser.parseLine/parseBytes | 解析 +CRCERR,可传 AT decoder;失败为 null |
AsciiPrefix.findDiPrefixIndex/findCrcErrPrefixIndex/findAsciiPrefixIndex | 在可能含前导噪声的字节中定位 ASCII 前缀 |
解析器只做边界解析,不负责线程、串口读取、重连或业务重试。