<\/figure>\n\n\n\nA special END byte delimits single messages<\/p>\n\n\n\n
This algorithm is an easy and fast-to-implement way of separating our CoAP messages in a stream of bytes.<\/p>\n\n\n\n
fun encode(data: ByteArray): ByteArray {\n\n val encoded = ByteArrayOutputStream()\n\n \n\n data.forEach { byte ->\n\n when (byte.toInt()) {\n\n 0xC0 -> {\n\n encoded.write(0xDB)\n\n encoded.write(0xDC)\n\n }\n\n 0xDB -> {\n\n encoded.write(0xDB)\n\n encoded.write(0xDD)\n\n }\n\n else -> encoded.write(byte.toInt())\n\n }\n\n }\n\n \n\n encoded.write(0xC0)\n\n return encoded.toByteArray()\n\n}<\/code><\/pre>\n\n\n\nPutting it together<\/h2>\n\n\n\n Let’s take a look at how the described steps and pieces fall into place.<\/p>\n\n\n\n <\/figure>\n\n\n\nCoAP over Bluetooth Low Energy, separated into a CoAP layer and a BLE layer<\/p>\n\n\n\n
First off, we decided to use a UART service implementation to send and receive bytes between the two devices. This circumvents the question of how to map CoAP endpoints to specific BLE characteristics and allows us to send CoAP messages of arbitrary size. In order to delimit single CoAP messages in the UART stream, we apply the SLIP algorithm which appends a special END byte to each message.<\/p>\n\n\n\n
From the perspective of the Android app, each message thus goes through the same steps: First, a CoAP request message is constructed with the required headers, options, payload, and token. The token is saved in conjunction with a RequestType<\/code> in order to know how to parse the payload of incoming CoAP messages.<\/p>\n\n\n\n\/** CoAP layer - construct token and CoAP message **\/\nfun requestTemperature() {\n val token = generateToken()\n sendCoapRequest(\n coapMessageFactory.createGetRequest(\"device\/temperature\", token)\n )\n pendingRequests[token] = RequestType.TEMPERATURE\n}\n<\/code><\/pre>\n\n\n\nThe CoAP message object is then converted to a Byte Array, which is encoded by the SLIP algorithm. The resulting Byte Array is written to the UART Rx characteristic of the remote device. Android takes care of splitting the message into smaller chunks.<\/p>\n\n\n\n
\/** BLE layer - encode and write message **\/\nfun sendCoapRequest(coapMessage: CoapMessage) {\n coapMessage.toByteArray()?.let { rawData->\n val encodedMessage = slipAdapter.encode(rawData)\n writeCharacteristic(uartRxCharacteristic, encodedMessage)\n }\n}\n<\/code><\/pre>\n\n\n\nWhen receiving new data, small chunks of bytes arrive in our app at the subscriber for the Tx characteristic of the remote device (in onReceive()<\/code>). The new data is appended to an internal buffer. With each new update, the SLIP algorithm searches for the occurrence of the END byte in this buffer and splits the stream into complete messages. As a last step, we clear the completed messages from the buffer and keep only the remaining, still incomplete bytes.<\/p>\n\n\n\n\/** BLE layer - receive and decode bytes **\/\n\nvar buffer = ByteArray(0)\n\nfun onReceive(newData: ByteArray?) {\n\n newData?.let { data ->\n\n buffer += data\n\n val decoded: DecodeResult = slipAdapter.decode(buffer)\n\n scope.launch {\n\n decoded.messages.forEach { m ->\n\n responseChannel.send(m)\n\n }\n\n }\n\n buffer = buffer.copyOfRange(result.endIndex + 1, buffer.size)\n\n }\n\n}<\/code><\/pre>\n\n\n\nEach complete message is parsed as a CoAP message. The app then checks the received CoAP token to determine if the received message is an expected response. If this check succeeds, the payload is further processed by the business components of the app (in handleExpectedMessage()<\/code>).<\/p>\n\n\n\n\/** CoAP layer - parse and handle CoAP message **\/\nfun handleMessage(data: ByteArray) {\n val coapMessage = coapMessageFactory.fromByteArray(data)\n coapMessage?.let { message ->\n val token = message.token\n val requestType = pendingRequests[token]\n return if (requestType == null) {\n handleUnexpectedMessage(message)\n } else {\n handleExpectedMessage(requestType, message, token)\n }\n } ?: logger.error(\"Unable to parse CoAP message\")\n}\n<\/code><\/pre>\n\n\n\nSince our initial implementation of CoAP over BLE a few months ago, the solution has proven to be easy to maintain and extend.<\/p>\n\n\n\n
Although developers initially need to learn how to work with CoAP, this is quickly accomplished due to the similarity to REST over HTTP. In my opinion, relying on an established standard protocol such as CoAP has many advantages over rolling your own BLE communication protocol.<\/p>\n\n\n\n
Have you worked with CoAP? What are your thoughts and what were your challenges?<\/p>\n\n\n\n
References<\/h2>\n\n\n\n [1]<\/a> https:\/\/tools.ietf.org\/html\/rfc7252#section-3<\/a><\/p>\n\n\n\n[2]<\/a> https:\/\/tools.ietf.org\/html\/rfc1055<\/a><\/p>\n\n\n\n<\/div>\n\n\n\n
\n\n\n\n
<\/div>\n\n\n\n
About the author<\/h3>\n\n\n\n\n
\n
<\/figure>\n\n\n\nMatthias Nefzger<\/h4>\n\n\n\n Senior Lead IT Architect <\/p>\n<\/div>\n\n\n\n
<\/p>\n<\/div>\n\n\n\n
<\/p>\n<\/div>\n<\/div>