Protobuf examples for mobile
Hi! would be really cool to see some examples of using protobuf commands on IOS or Android. Are there any plans to add it? Would be super useful with this new COHN feature
Yeah I do plan on adding Kotlin to the Protobuf tutorial.
An example of how to Get All Available Presets using RequestGetPresetStatus would be great!
Is the confusion here about the additional Open GoPro packet wrapping around Protobuf?
Or simply how to compile / build protobuf messages?
The latter is mostly out of the scope of Open GoPro and covered pretty extensively by Protobuf.dev
@tcamise-gpsw It would be great just to have some Kotlin example in tutorials to perform actions like in this tutorial for Python, not necessarily a complete wrapper
Maybe adding it as an additional file to the Kotlin Example here:
@tcamise-gpsw I've managed to implement COHN provisioning for Kotlin. My Protobuf encoding works fine, but my parsing logic is not generic at all; I've just hardcoded it using regular expressions.
I haven’t worked with Protobuf before, but as I understand it, parsing is just the opposite operation to encoding (packetizing). Before trying to implement the parsing, I would like to clarify—how does the Python SDK differentiate Query Response Types? Specifically, how do we know that the response is a Protobuf? I couldn't understand that from the library in a reasonable amount of time. Are we supposed to know which type of response we are waiting for, considering the command sent?
By the way, here is how my encoding works. FeatureId(0x02) ActionId(0x05) and FieldNumbers (1,2) are hardcoded for simplicity.
suspend fun connectToNetwork(ssid: String, password: String) {
val ssidPayload = serializeRequest(1, ssid)
val passwordPayload = serializeRequest(2, password)
val connectRequestData = mutableListOf<Byte>(0x02, 0x05)
connectRequestData.addAll(ssidPayload.toList().map { it.toByte() })
connectRequestData.addAll(passwordPayload.toList().map { it.toByte() })
println("connectToNetwork request ${connectRequestData.toByteArray().toUByteArray().toHexString()}")
val packetizedData = fragmentedPackets(connectRequestData.toByteArray())
for (packet in packetizedData) {
ble.writeCharacteristic(
connectedGoProBLEMac,
GoProUUID.NETWORK_MANAGEMENT_REQ_UUID.uuid,
packet.toByteArray().toUByteArray()
)
}
}
fun varintEncode(number: Int): ByteArray {
val buffer = mutableListOf<Byte>()
var num = number
while (num >= 128) {
buffer.add((num and 0x7F or 0x80).toByte())
num = num shr 7
}
buffer.add((num and 0x7F).toByte())
return buffer.toByteArray()
}
fun serializeRequest(fieldNumber: Int, value: String? = null, isString: Boolean = true): UByteArray {
if (value == null) {
return ubyteArrayOf()
}
if (isString) {
return lengthDelimitedEncode(fieldNumber, value).toUByteArray()
}
if (value.lowercase() in listOf("true", "1", "yes")) {
val key = varintEncode(fieldNumber shl 3 or 0)
val valueEncoded = varintEncode(1)
return (key + valueEncoded).toUByteArray()
}
return ubyteArrayOf()
}
@OptIn(ExperimentalUnsignedTypes::class)
fun fragmentedPackets(payload: ByteArray): List<UByteArray> {
val length = payload.size
val CONTINUATION_HEADER: UByteArray = ubyteArrayOf(0x80u)
val MAX_PACKET_SIZE = 20
var isFirstPacket = true
val header: UByteArray = when {
length < (1 shl 13) - 1 -> ((length or 0x2000).toShort()).toUByteArray()
length < (1 shl 16) - 1 -> ((length or 0x6400).toShort()).toUByteArray()
else -> throw IllegalArgumentException("Data length $length is too big for this protocol.")
}
val packets = mutableListOf<UByteArray>()
var byteIndex = 0
while (length - byteIndex > 0) {
var packet = if (isFirstPacket) header.copyOf() else CONTINUATION_HEADER.copyOf()
isFirstPacket = false
val packetSize = min(MAX_PACKET_SIZE - packet.size, length - byteIndex)
packet += payload.sliceArray(byteIndex until byteIndex + packetSize).toUByteArray()
packets.add(packet)
byteIndex += packetSize
}
return packets
}
fun lengthDelimitedEncode(fieldNumber: Int, value: String): ByteArray {
val key = varintEncode(fieldNumber shl 3 or 2)
val encodedValue = value.toByteArray(Charsets.UTF_8)
val length = varintEncode(encodedValue.size)
return key + length + encodedValue
}
fun Short.toUByteArray(): UByteArray {
return ubyteArrayOf(
(this.toInt() shr 8).toUByte(),
(this.toInt() and 0xFF).toUByte()
)
}
Nice! I'm about to start working on this ticket so I'll take a look at your code above when I get around to it.
Yes your understanding of general protobuf parsing sounds correct. Regarding the ID parsing...it is unfortunately extremely contrived but it is documented as well as described in a tutorial (for Python)
FYI I'm working on a Kotling KMP SDK which will be similar in scope to the current Python SDK. I'm not sure if the tutorials as currently constructed are going to be worthwhile once we have both of these SDK's. Ideally tutorials instead could use the various SDK's to focus on higher level concept such as streaming, presets, etc.
Any opinion here?
FWIW the Kotlin SDK now makes heavy use of the protobuf messages.
I'll leave this open until a decision has been made on whether the Kotlin Protobuf tutorials will be updated.