iOS API Reference
SDK Version: 1.0.0
Platform: iOS
Use every @MainActor type, property, and method on MainActor. Default values below match the formal signatures.
XTalkBleClient
@MainActor public final class XTalkBleClient: NSObject, ObservableObject
public override init()
Each client owns one BLE lifecycle and one serialized AT/RF channel. It cannot be reused after teardown().
Published state
| Property | Type | Meaning |
|---|---|---|
connectionState | XTalkConnectionState | Current scan/connection state; read-only and observable as $connectionState |
scanResults | [XTalkScanResult] | Devices in the current scan session |
connectedName | String? | Selected/connected device name |
connectedAddress | String? | Address when the platform supplies it |
connectedPeripheralId | UUID? | Current peripheral identifier |
atReady | Bool | Passthrough write and notification readiness |
deviceControlReady | Bool | Device-control query readiness |
atEncoding | XTalkAtEncoding | Active AT encoding; defaults to .xor |
events | AsyncStream<XTalkBleEvent> | Bounded event stream, finished by teardown() |
onEvent | (@MainActor (XTalkBleEvent) -> Void)? | Optional single event callback |
Connection and lifecycle methods
| Method | Precondition | Result/notes |
|---|---|---|
startScan() | Client not torn down | Clears and updates results; failures publish .failed |
stopScan() | None | Stops scanning; scanning state returns to .idle |
connect(id:) | Bluetooth available, id from current results | Stops scanning and connects asynchronously; state/events publish the outcome |
disconnect() | None | Cancels connection and transactions; client can scan again |
teardown() | None | Ends scan, connection, transactions, and events; idempotent and terminal |
AT and RF methods
public func setAtEncoding(_ encoding: XTalkAtEncoding)
public func runAtCommand(
_ plain: String,
timeoutMs: Int = 3_000,
log: Bool = true,
responseMode: XTalkAtResponseMode = .automatic
) async throws -> [String]
public func sendAtCommandNoWait(_ plain: String) throws
public func withExclusiveAtSession<T>(
_ operation: @MainActor (XTalkBleClient) async throws -> T
) async throws -> T
public func sendRfPayload(_ rawPayload: Data, timeoutMs: Int = 5_000) async throws
public func sendRfPayloadNoWait(_ rawPayload: Data) throws
| Method | Input | Return/error |
|---|---|---|
setAtEncoding | .plain or .xor | Resets the text decode buffer; never switch during a transaction |
runAtCommand | Plain AT text, positive timeout, response mode | Returns response lines; can throw connection, readiness, busy, timeout, write, or cancellation errors |
sendAtCommandNoWait | AT text | Starts a write only; responses use events/observers and isolation can throw .busy |
withExclusiveAtSession | Serialized async closure | Returns closure result and prevents unrelated AT insertion |
sendRfPayload | Raw business payload | Waits for complete send; throws on timeout or link failure |
sendRfPayloadNoWait | Raw business payload | Starts send only; completion uses events and isolation can throw .busy |
Device information queries
public func fetchBatteryLevel(timeoutMs: Int = 2_000) async -> Int?
public func fetchBleFirmwareVersion(timeoutMs: Int = 2_000) async -> String?
public func fetchHardwareVersion(timeoutMs: Int = 2_000) async -> String?
These convenience queries do not throw and return nil on failure. Check deviceControlReady first. Battery is the integer returned by the device; the SDK does not impose the customer's UI range or format. Version strings are trimmed.
Fine-grained observers
public func observeAtLines(_ handler: @escaping @MainActor (String) -> Void) -> UUID
public func removeAtLineObserver(_ id: UUID)
public func observeSendFinishLines(_ handler: @escaping @MainActor (String) -> Void) -> UUID
public func removeSendFinishLineObserver(_ id: UUID)
public func observePassthroughRaw(_ handler: @escaping @MainActor (Data) -> Void) -> UUID
public func removePassthroughRawObserver(_ id: UUID)
Registration returns a token accepted only by the matching remove method. Handlers run on MainActor; do not perform long blocking work in them.
Scan and connection models
XTalkScanResult
public init(id: UUID, name: String, address: String?, rssi: Int)
public var displayName: String { get }
displayName combines name and address when an address exists; otherwise it returns the name. Pass id to connect(id:).
XTalkConnectionState
.idle, .scanning, .connecting, .connected, .disconnected, .failed(XTalkBleError).
XTalkBleError
.bluetoothUnavailable, .permissionDenied, .scanFailed(String), .deviceNotFound(UUID), .deviceMismatch(String), .connectionFailed(String), .serviceMissing, .characteristicMissing, .notConnected, .notReady, .busy, .timeout, .writeFailed(String), .cancelled, .malformedResponse(String), .deviceControlFailed(String).
See Errors and FAQ for recovery guidance.
AT, RF, and event models
XTalkAtEncoding:.plain,.xor.XTalkAtResponseMode:.automaticcompletes by command policy;.collectUntilTimeoutcollects the full timeout window.XTalkDiMeta: optionaldiCnt,len,slot,snr,rssi,localSnr,localRssi,remoteSnr,remoteRssi, andtxp; every initializer argument defaults tonil.XTalkBleEvent.connectionStateChanged(XTalkConnectionState): state transition.XTalkBleEvent.atLine(String): parsed AT line.XTalkBleEvent.sendFinished: send completion.XTalkBleEvent.diPayload(Data, XTalkDiMeta): business payload and radio metadata.XTalkBleEvent.crcError(XTalkDiMeta, String): CRC metadata and raw line.XTalkBleEvent.passthroughRaw(Data): unclassified raw passthrough bytes.
DeviceAuthTransport
@MainActor public protocol DeviceAuthTransport: AnyObject {
var label: String { get }
var atEncoding: DeviceAuthAtEncoding { get }
func setAtEncoding(_ encoding: DeviceAuthAtEncoding)
func runAtCommand(
_ command: String,
timeoutMs: Int,
log: Bool,
responseMode: DeviceAuthResponseMode
) async throws -> [String]
}
DeviceAuthAtEncoding has .plain and .xor. DeviceAuthResponseMode has .automatic, .collectFullWindow, and .recoverableEncodingProbe. The final two preserve full-window semantics. .recoverableEncodingProbe additionally requires a clean channel before the next command after ambiguous probing.
See the Device Authentication SDK for a complete BLE adapter.
XTalkDeviceAuthenticator
@MainActor public final class XTalkDeviceAuthenticator: ObservableObject {
@Published public private(set) var state: XTalkDeviceAuthState
public init(transport: any DeviceAuthTransport)
public func authenticate(
configuration: XTalkDeviceAuthConfiguration = XTalkDeviceAuthConfiguration()
) async throws -> XTalkDeviceAuthResult
public func cancel()
}
authenticate probes encoding, performs Challenge when required, reads identity, and returns the result. One instance allows one active session. Failure publishes .failed(error) and throws the same public error. cancel() requests cancellation and does nothing without an active session.
Authentication configuration, result, and state
XTalkDeviceAuthConfiguration
public init(
probeTimeoutMs: Int = 800,
strictProbeTimeoutMs: Int = 1_200,
challengeTimeoutMs: Int = 4_000,
identityTimeoutMs: Int = 4_000,
maxChallengeAttempts: Int = 3
)
Every value must be greater than zero or authentication throws .invalidConfiguration.
XTalkDeviceAuthResult
Contains identity: XTalkDeviceIdentity, encoding: DeviceAuthAtEncoding, and didRequireChallenge: Bool; its public initializer accepts the same fields.
XTalkDeviceAuthState
.idle, .probing, .challenging(attempt:maxAttempts:), .readingIdentity, .authenticated(XTalkDeviceIdentity), .failed(XTalkDeviceAuthError), .cancelled.
XTalkDeviceAuthError
.busy, .invalidConfiguration, .transportUnavailable, .commandTimedOut(String), .secureRandomUnavailable, .invalidChallenge, .challengeMissing, .malformedChallengeReply, .invalidSignatureLength(Int), .signatureVerificationFailed, .challengeRetryExhausted, .invalidDeviceIdentity, .cancelled.
Device identity and DID utilities
XTalkDeviceIdentity
public init(
deviceID: UInt32,
did: String,
serial: String,
source: XTalkDeviceIdentitySource
)
source is .efuseSn or .sn. Normal authentication consumers should use the returned identity instead of constructing one.
XTalkDeviceIDCodec
| Method | Purpose |
|---|---|
canonicalize(_ raw: UInt32) -> UInt32 | Normalize legacy device identifier encoding |
fromSn(year: Int, week: Int, serial: UInt32) -> UInt32 | Build an identifier from SN fields using protocol bit widths |
parse(_ raw: String) -> UInt32? | Parse decimal or 0x hexadecimal text and canonicalize it |
format(_ raw: UInt32) -> String | Produce canonical 0x plus eight uppercase hexadecimal digits |