private suspend fun sendConfigAndStart(configData: ByteArray) { // 0x01, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x13, // 0x01, 0x00, 0x00, 0x00, 0x3f, 0xf5, 0x03, 0x00, 0xb8, // 0x0b, 0x00, // 0x00, 0x01, 0x09, // 0x09, 0x00, // 0x00, // 0x4c, 0x91, // 0x01, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x13, // configLen - 15 // 0x01, 0x00, 0x00, 0x00, 0x3f, 0xf5, 0x03, 0x00, 0xb8, // 0x0b, 0x00, // Preamble - 25 // 0x00, 0x01, 0x09, // 0x09, 0x00, // Channel - 30 // 0x01, // 0x7c, 0xd2, // Dest Addr - 33 val configLen = configData[15].toInt() var channel = configData[30] var preamble = configData[25] var destAddr = byteArrayOf(1, 0) if (configData.size == 35) { channel = configData[30] preamble = configData[25] destAddr = byteArrayOf(configData[33], configData[34]) } else { log("Bad UWB Config size") } // iOS timings // val packetPayload = byteArrayOf( // 0x01, 0x00, 0x00, 0x00, 0x17, 0x45, 0x55, // 0x64.toByte(), 0x8b.toByte(), 0x00, 0x00, // SessionID - 7 // 0x0b, // preamble - 11 // 0x09, // channel - 12 // 0x06, 0x00, // num slots - 13 // 0x10, 0x0e, // slot duration - 15 // 0xb4.toByte(), 0x00, // block duration - 17 // 0x03, // 0x35.toByte(), 0xfd.toByte(), 0x52.toByte(), 0xf3.toByte(), 0xe0.toByte(), 0xc9.toByte(), // STS IV - 20 // 0x46, 0x59.toByte(), // DST_ADDR - 26 // ) // Android timings val packetPayload = byteArrayOf( 0x01, 0x00, 0x00, 0x00, 0x17, 0x45, 0x55, 0x11.toByte(), 0x11.toByte(), 0x11, 0x011, // SessionID - 7 0x0b, // preamble - 11 0x09, // channel - 12 0x06, 0x00, // num slots - 13 0x60, 0x09, // slot duration - 15 0xf0.toByte(), 0x00, // block duration - 17 0x03, 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), // STS IV - 20 0x46, 0x59.toByte(), // SRC_ADDR - 26 ) val sessionId = 0x00000001 // SessionID - 7 ByteBuffer.wrap(packetPayload, 7, 4).putInt(sessionId) log("UWB Session id: ${"%8x".format(sessionId)}") // Channel packetPayload[12] = channel // Preamble packetPayload[11] = preamble // SRC_ADDR packetPayload[26] = clientSession.localAddress.address[0] packetPayload[27] = clientSession.localAddress.address[1] log("Config UWB channel $channel preamble $preamble destAddr ${"%2x".format(destAddr[0])}:${"%2x".format(destAddr[1])} srcAddr ${clientSession.localAddress}") // TODO - STS_IV byte order looks strange on iOS val vendorId = byteArrayOf(0x4c, 0x00) val stsIV = byteArrayOf(0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte()) // STS IV - 20 ByteBuffer.wrap(packetPayload, 20, 6).put(stsIV) val newConfig = byteArrayOf(configureAndStartAndroid.toByte()) + packetPayload uartManager.send(newConfig) lifecycleScope.launch { startRanging( destAddr, channel.toInt(), preamble.toInt(), sessionId, vendorId + stsIV ) } } private fun processUwbConfigData(configData: ByteArray) { // The message structure is the following: // ------------------------ // majorVersion -- the major version from section 3.3 of the Nearby Interaction Accessory Protocol Specification, Developer Preview, Release 1. // minorVersion -- the minor version from section 3.3 of the Nearby Interaction Accessory Protocol Specification, Developer Preview, Release 1. // preferredUpdateRate -- a selection of one of the options from table 3-3 in the Nearby Interaction Accessory Protocol Specification, Developer Preview, Release 1. // rfu[10] -- reserved for future use. // uwbConfigDataLength -- the length of the UWB config data as provided by the UWB middleware. // uwbConfigData -- the UWB config data as provided by the UWB middleware, according to section 4.4.2 on the UWB Interoperability Specification, Developer Preview, Release 1. // #define MAX_UWB_CONFIG_SIZE (64) // #define ACCESSORY_CONFIGURATION_DATA_FIX_LEN (16) // ///// Section 3.4.3.1 in Nearby Interaction Accessory Protocol Specification, Developer Preview. // enum PreferredUpdateRate { // PreferredUpdateRate_Automatic = 0, // iOS will choose a value // PreferredUpdateRate_Infrequent = 10, // ~1.3Hz // PreferredUpdateRate_UserInteractive = 20, // ~5.5Hz // }; // // struct AccessoryConfigurationData { // uint16_t majorVersion; // NI Accessory Protocol major version // uint16_t minorVersion; // NI Accessory Protocol minor version // uint8_t preferredUpdateRate; // uint8_t rfu[10]; // uint8_t uwbConfigDataLength; // uint8_t uwbConfigData[MAX_UWB_CONFIG_SIZE]; // } __attribute__((packed)); val config = ByteBuffer.wrap(configData) val majorVersion = config.short.toUShort() // NI Accessory Protocol major version val minorVersion = config.short.toUShort() // NI Accessory Protocol minor version val preferredUpdateRate = config.get().toUByte() val rfu = ByteArray(10) config.get(rfu) val uwbConfigDataLength = config.get().toUByte() log("Got UWB Config $majorVersion:$minorVersion update rate: $preferredUpdateRate UwbConfigLen $uwbConfigDataLength") runBlocking(Dispatchers.IO) { sendConfigAndStart(configData) } } // A code snippet that initiates uwb ranging for a controlee. private suspend fun startRanging(destAddr: ByteArray, uwbChannelNum: Int, preamble: Int, sessionId: Int, sessionKey: ByteArray) { // Get the ranging parameter of a partnering controller using an OOB mechanism of choice... hard coded in our case val uwbDevice = UwbDevice.createForAddress(destAddr) val uwbChannel = UwbComplexChannel(uwbChannelNum, preamble) // Create the ranging parameters. // val rangingParameters = RangingParameters( // uwbConfigType = UWB_CONFIG_ID_1, // sessionId = sessionId, // sessionKeyInfo = sessionKey, // complexChannel = uwbChannel, // peerDevices = listOf(uwbDevice), // updateRateType = RangingParameters.RANGING_UPDATE_RATE_AUTOMATIC // ) // Create the ranging parameters. val rangingParameters = RangingParameters( uwbConfigType = UWB_CONFIG_ID_1, // SessionKeyInfo is used to encrypt the ranging session. sessionId = sessionId, sessionKeyInfo = sessionKey, complexChannel = uwbChannel, peerDevices = listOf(uwbDevice), updateRateType = RangingParameters.RANGING_UPDATE_RATE_FREQUENT ) log("Start ranging UWB channel $uwbChannelNum preamble $preamble destAddr ${uwbDevice.address}") val sessionFlow = clientSession.prepareSession(rangingParameters) // Start a coroutine scope that initiates ranging. CoroutineScope(Dispatchers.IO).launch { sessionFlow.collect { when (it) { is RangingResult.RangingResultPosition -> processRanging(it.position) is RangingResult.RangingResultPeerDisconnected -> log("UWB Peer disconnected: $it") } } } }