Skip to main content

Android RadioTransport Abstraction

SDK Version: 1.0.0
Package: com.xtalk.mesh.sdk.transport

RadioTransport lets Auth, Init, and business code access BLE or customer UART through one contract. It does not open the physical link. Connect BLE or open UART before constructing/using it.

1. Models and enums

TypeValues/fieldsPurpose
RadioTransportModeBLE, UARTSelected physical channel
RadioSendResultSEND_OK, SEND_FAIL_NOT_CONNECTED, SEND_FAIL_INTERNALRF submission result
AtCommandErrorPolicyFAIL_FAST, COLLECT_UNTIL_COMPLETEFail on an error line or let a custom completion condition collect a full response
RadioPacketSourceBLE_DIRECT, BLE_PASSTHROUGH_DI, UART_DI, UART_HEXInbound packet origin
RadioInboundPacketpayload, source, snr, rssi, receivedAtNanosInbound RF data; payload is the business byte array
RadioCrcErrorEventsource, slot, snr, rssi, rawLine, receivedAtNanosStructured CRC error; unavailable metadata is null
RadioPacketListeneronPacket(packet)Normal or realtime packet callback
RadioCrcErrorListeneronCrcError(event)CRC callback

Listener threads are transport-defined high-frequency I/O paths. Return quickly and dispatch UI updates to the main thread.

2. Every RadioTransport member

Common contract: invoke AT and RF operations off the main thread. Do not overlap Auth, Init, or custom AT transactions. timeoutMs is milliseconds. A timeout does not prove that the device did not execute the command; query state before retrying.

MemberParameters/defaultsReturn, errors, and paired operation
mode, labelNoneCurrent channel and log label
isOpenOrConnected()NonePhysical link state; not Auth/Init readiness
isReadyForSend()NoneBusiness-send gate
localDeviceLongId()NoneAuthenticated DID or null
runAtCommand(command, timeoutMs = 3000, log = true, errorPolicy = FAIL_FAST, stopWhen = null)AT text and optional completion predicateResponse lines; disconnected, send, device error, and timeout paths may throw
runAtCommandDrainingResponseTail(...)stopWhen requiredKeeps listener/lock through terminal OK/ERROR or deadline after payload match; unsupported transports throw UnsupportedOperationException
sendAtCommand(command)AT texttrue when submitted; no response lines
sendAtCommandNoWait(command)AT textSubmission only; does not prove device execution
sendRfPayload(payload, timeoutMs = 3000)Non-empty frameRadioSendResult; recommended for normal reliable traffic
sendRfPayloadAndWaitSendFinish(payload, timeoutMs = 3000)Non-empty frameWaits for device SEND_FINISH; true on success
sendRfPayloadNoWait(payload)Non-empty realtime frameLow-latency submission without completion; caller rate-limits
addPacketListener / removePacketListenerSame listener instanceNormal packet lifecycle pair
addRealtimePacketListener / removeRealtimePacketListenerSame listener instanceRealtime packet lifecycle pair
addCrcErrorListener / removeCrcErrorListenerSame listener instanceCRC lifecycle pair; bridge needs a CRC parser source
setExpectedRealtimeSourceShortId(shortId)null clearsUART/advanced source filter; BLE may be a no-op
isRecentLocalRealtimeEcho(payload)Complete frameLocal realtime echo check; unsupported implementations return false
setFrequencyHz(freqHz)Approved Hz valuetrue only when all channel values were applied
setRateMode(rateMode)Device-supported modetrue on success
queryRateMode(timeoutMs = 1500)TimeoutCurrent value or null on response/parse failure
queryWorkMode(timeoutMs = 1500)TimeoutCurrent value or null on response/parse failure
restoreTextPttDefaults(defaultRateMode, defaultAddtl = 1, defaultWorkMode = 21)Three settingstrue only if all restore operations succeed; call after call/file modes
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) },
)

Initialize XTalkBleClient first. Connect BLE before transport operations and complete Auth/Init before business sends. eventBridge maps lower-level inbound events; see End-to-End Integration. The realtime callbacks may be empty when local-echo tracking is not needed. The transport does not own BLE; customers still remove listeners and disconnect.

4. RadioTransportEventBridge

The interface has three strict add/remove pairs: normal packet, realtime packet, and CRC error. Keep a listener → underlying callback map and remove the original callback instance. The complete BLE mapping example is in End-to-End Integration.

5. Customer UART adapter

Wrap an existing driver when a ready-made UART transport is not included in the delivery. This is the contract skeleton only; the implementation must add response framing, DI decoding, CRC handling, exclusive locking, and deadlines:

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)?,
) = 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)

// Delegate listener pairs, radio settings/queries, and defaults to the customer driver.
// Return false/null or throw UnsupportedOperationException for unsupported operations.
}

For Auth only, implement DeviceAuthenticator.AtCommandTransport. For Init only, implement DeviceInitializer.AtCommandTransport. Implement full RadioTransport only when unified business switching is required.

6. RadioTransportRouter

val router = RadioTransportRouter(bleTransport, uartTransport, RadioTransportMode.BLE)
router.addPacketListener(packetListener)
router.setMode(RadioTransportMode.UART)
val active = router.currentTransport()

setMode moves the Router's three listener sets from the old transport to the new one. Open and Auth/Init the new physical channel before switching. Remove listeners during cleanup; the Router does not close transports.

7. Advanced inbound parsers

These public low-level tools are for transport authors; ordinary BLE customers do not call them directly:

APIContract
AtLineBuffer.hasBufferedData()Whether an unterminated partial line remains
AtLineBuffer.append(bytes, onLine)CR/LF framing; the two-argument callback also receives assembly milliseconds
AtResponsePolicy.isOkLine/isErrorLine/isSendFinishLineCompatible terminal recognition
AtResponsePolicy.isResponseTailOkLine/isResponseTailErrorLineStrict tail-drain terminal recognition
AtResponsePolicy.isFreqProbeAccepted/freqProbeStatusNORMAL_FREQ, NEEDS_VERIFY, or null
AtResponseTailDrainTracker.observeCONTINUE/COMPLETE/ERROR; payloadMatched is sticky
BinaryEscape.escape/unescapeReversible CR/LF/escape-byte framing without mutating input
DiFrameParser.parseLine/parseBytesParse +DI/AT+DI; null on failure; drop frames with lengthMismatch
DiFrameParser.parseDiMetaExtract count, length, slot, SNR/RSSI, and TXP; missing fields are null
DiMeta.hasInvalidRemoteFeedback()Detect invalid remote feedback combination
CrcErrorParser.parseLine/parseBytesParse +CRCERR, optionally through an AT decoder; null on failure
AsciiPrefix.findDiPrefixIndex/findCrcErrPrefixIndex/findAsciiPrefixIndexFind an ASCII prefix after possible leading noise

Parsers do not own serial reads, threads, reconnection, or business retries.

Integration Overview · API Coverage Matrix