Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protobuf examples for mobile #518

Open
pavlosharhan2 opened this issue Apr 12, 2024 · 7 comments
Open

Protobuf examples for mobile #518

pavlosharhan2 opened this issue Apr 12, 2024 · 7 comments
Assignees
Labels
enhancement New feature or request tutorials Relating to tutorials

Comments

@pavlosharhan2
Copy link

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

@pavlosharhan2 pavlosharhan2 added the enhancement New feature or request label Apr 12, 2024
@github-actions github-actions bot added the triage Needs to be reviewed and assigned label Apr 12, 2024
@tcamise-gpsw
Copy link
Collaborator

Yeah I do plan on adding Kotlin to the Protobuf tutorial.

@tcamise-gpsw tcamise-gpsw removed the triage Needs to be reviewed and assigned label Apr 12, 2024
@pokmis
Copy link

pokmis commented Apr 17, 2024

An example of how to Get All Available Presets using RequestGetPresetStatus would be great!

@tcamise-gpsw
Copy link
Collaborator

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

@pavlosharhan2
Copy link
Author

pavlosharhan2 commented Apr 23, 2024

@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:
image

@tcamise-gpsw tcamise-gpsw added the tutorials Relating to tutorials label Apr 23, 2024
@tcamise-gpsw tcamise-gpsw self-assigned this Jul 17, 2024
@pavlosharhan2
Copy link
Author

@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()
    )
}

@tcamise-gpsw
Copy link
Collaborator

tcamise-gpsw commented Jul 17, 2024

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)

@tcamise-gpsw
Copy link
Collaborator

tcamise-gpsw commented Aug 30, 2024

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request tutorials Relating to tutorials
Projects
None yet
Development

No branches or pull requests

3 participants