Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Droidcon Berlin - Hardware Development With KMP...

Avatar for Enrique Ramirez Enrique Ramirez
July 04, 2024
46

Droidcon Berlin - Hardware Development With KMP and Compose Multiplatform

Avatar for Enrique Ramirez

Enrique Ramirez

July 04, 2024
Tweet

Transcript

  1. Requirements Matrix8 • Switch multiple guit a r ped a

    ls a nd its order inst a ntly • Displ a y which ped a ls a re selected a nd switched on • Wireless communic a tion for a comp a nion a pp
  2. I/O Boards • Mono a udio I/O bo a rd

    • JST connectors • 6.35mm, 1/4’’ j a ck connectors
  3. 3D Printing • Helps keep your prototype tidy. • Help

    you visu a lise your project a s a whole. • Helps f inding issues a nd iter a ting on them. • You c a n use online services. • It h a s a le a rning curve.
  4. A Swiss Army knife The Raspberry PI • Sm a

    ll form f a ctor computer • Con f igur a ble I/O • Integr a ted wireless comms * • OS Customis a tion • Good Community support • Displ a y c a p a bilities • Open Source friendly • Wide r a nge of v a ri a nts
  5. Compose Multiplatform KMP Applic a tion JVM Desktop T a

    rget SKIKO SKIA Compose for Desktop
  6. I/O Java Library for the Pi Pi4J Project • Comp

    a tible with multiple bo a rds • Very f lexible • Support for I²C, SPI, GPIO, UART, PWM • Kotlin DSL
  7. Inter-Integrated Circuit I²C • 2 wires required for multiple devices

    in the bus • 7 bit a ddressing, up to 128 t a rget devices • Bidirection a l communic a tion • Multiple speed modes: 100kbps, 400kbps, 1Mbps, 1.7Mbps, 3.4Mbps, 5Mbps
  8. Bus I²C M a in MCU T a rget Device

    1 SDA SCL T a rget Device 2 T a rget Device N
  9. Example ON/OFF | AX3 | AX2 | AX1 | AX0

    | AY2 | AY1 | AY0 8Bit Word ON X2 -> Y4 1 0 0 1 0 1 0 0 ON 2 4
  10. override suspend fun sendData(commandValue: List<Byte>) { pi4j { i2c(1, ADG2188_DEVICE_ADDRESS)

    {
 id("ADG2188")
 linuxFsI2CProvider() }.use {
 commandValue.forEach {
 adg2188.write(it)
 }
 } }
 }
 

  11. private data class AddressByte(
 val enabled: Boolean,
 val x: Int,


    val y: Int ) {
 fun toByte(): Byte {
 val enableBit = if (enabled) 1 else 0
 return (((enableBit shl 7) or (x.xAddressOffset() shl 3)) or y).toByte()
 }
 
 private fun Int.xAddressOffset(): Int {
 return if (this < 6) return this else (this + 2)
 }
 }

  12. Current St a te Di ff St a te Δ

    New st a te Di ff St a te Δ List of I2c comm a nds
  13. private fun getMatrix(pedals: List<Pedal>): Array<BooleanArray> { val enabledPedals = pedals.filter

    { it.enabled } val matrix = Array(8) { BooleanArray(8) } if (enabledPedals.isEmpty()) { matrix[0][0] = true return matrix } enabledPedals.forEachIndexed { index, pedal -> when (index) { 0 -> { matrix[0][pedal.ioChannel] = true // If only 1 pedal in the list, output index should be 0 (output) val outputIndex = enabledPedals.getOrNull(1)?.ioChannel ?: 0 matrix[pedal.ioChannel][outputIndex] = true } enabledPedals.lastIndex -> matrix[pedal.ioChannel][0] = true else -> matrix[pedal.ioChannel][enabledPedals[index + 1].ioChannel] = true } } return matrix }
  14. private fun getAddressByteList( oldMatrix: Array<BooleanArray>, newMatrix: Array<BooleanArray> ): List<AddressByte> {

    val addressesPairList = mutableListOf<AddressByte>() for (x in oldMatrix.indices) { for (y in oldMatrix[x].indices) { if (oldMatrix[x][y] != newMatrix[x][y]) { addressesPairList.add( AddressByte( enabled = newMatrix[x][y], x = x, y = y, ) ) } } } return addressesPairList.toList() }
  15. Options Companion app communication • TCP/IP b a sed •

    Wi f i Direct • HTTP Server • Websockets Server • MQTT • Bluetooth • Bluetooth Cl a ssic • Bluetooth low energy
  16. BLE Communication Advertise BLE Service Re a d Publish Ch

    a r a cteristic Sc a n Connect H a ndle connections Re a d Write Subscribe ID ID
  17. Java Library BLE • Simple interf a ce • Service

    a nd ch a r a cteristic cre a tion c a p a bilities • Not m a int a ined (l a st ch a nge 6 ye a rs a go) • E a sy to modify to extend c a p a bilities • No m a ven repository (Requires checkout a nd j a r gener tion) https://github.com/tongo/ble-j a v a
  18. class Matrix8Characteristic( bleService: BleService ) : BleCharacteristic(bleService), GattCharacteristic1 { companion

    object { private const val UUID = "718b1158-3736-4620-98d6-e57321d01a70" private const val PATH = "/matrix8/s/c" } var matrix8State = byteArrayOf() set(value) { field = value sendNotification() } var matrix8Callback: ((ByteArray) -> Unit)? = null init { val flags = arrayListOf( CharacteristicFlag.READ, CharacteristicFlag.WRITE, CharacteristicFlag.NOTIFY ) setFlags(flags) path = PATH uuid = UUID listener = object : BleCharacteristicListener { override fun getValue(): ByteArray { return matrix8State } override fun setValue(value: ByteArray) { matrix8State = value matrix8Callback?.invoke(value) } } } }
  19. companion object { private const val UUID = "740b93ce-c198-455a-9102-43edd3f59f6c" private

    const val PATH = "/matrix8" private const val DEVICE_ALIAS = "Matrix8" private const val DEVICE_NAME = "Matrix8" } private val bleApp = BleApplication(PATH, appListener, DEVICE_NAME) private val bleService = BleService("$PATH/s", UUID, true) private val matrix8Characteristic = Matrix8Characteristic(bleService) init { bleService.addCharacteristic(matrix8Characteristic) bleApp.addService(bleService) bleApp.setAdapterAlias(DEVICE_ALIAS) }
  20. override suspend fun startService() { bleApp.start() CoroutineScope(Dispatchers.IO).launch { pedalStateFlow.collectLatest {

    setCharacteristicValue(it) } } matrix8Characteristic.matrix8Callback = { pedalsByteArray -> pedalStateFlow.value = pedalsByteArray.toPedalList() } } override suspend fun stopService() { matrix8Characteristic.matrix8Callback = null bleApp.stop() }
  21. /** * Characteristic format, byteArray * ChannelNumber[0],Enabled[0], ChannelNumber[1],Enabled[1]....... */ private

    fun setCharacteristicValue(pedals: List<Pedal>) { matrix8Characteristic.matrix8State = pedals.map { val channel = it.ioChannel.toByte() val enabled: Byte = if (it.enabled) 1 else 0 listOf(channel, enabled) }.flatten().toByteArray() }
  22. Architecture Present a tion UI UseC a ses I²C G

    a tew a y BLE Service Switcher St a te M a trix8ViewModel M a trix8Screen GetM a trix8UseC a se SwitchPed a lUseC a se St a teFlow<List<Ped a l> M a trix8 St a te Ch a r a cteristic ADG2188 Driver
  23. General Purpose Input/Output GPIO • Dedic a ted logic pins

    • Con f igur a ble to re a d or write. • Ide a l for simple a pplic a tions. (Button re a d, LED switching, …)
  24. companion object { private const val DIGITAL_INPUT_PIN_SOUND_A: Int = 7

    // GPIO 4 private const val DIGITAL_INPUT_PIN_SOUND_B: Int = 29 // GPIO 5 } fun setButtonListener(onButtonPressed: (matrixButton: MatrixButton) -> Unit) { pi4jAsync { matrix8Button(DIGITAL_INPUT_PIN_SOUND_A) { onButtonPressed(MatrixButton.SOUND_A) } matrix8Button(DIGITAL_INPUT_PIN_SOUND_B) { onButtonPressed(MatrixButton.SOUND_B) } } } private fun Context.matrix8Button( pin: Int onButtonPressed: () -> Unit, ) { digitalInput(pin) { pull(PullResistance.PULL_DOWN) debounce(1000L) piGpioProvider() }.onLow { onButtonPressed() } }
  25. companion object { private const val DIGITAL_OUTPUT_PIN_SOUND_A: Int = 26

    // GPIO 6 private const val DIGITAL_OUTPUT_PIN_SOUND_B: Int = 31 // GPIO 7 } fun setLED(button: MatrixButton, on: Boolean) { pi4j { val pin = when (button) { MatrixButton.SOUND_A -> DIGITAL_OUTPUT_PIN_SOUND_A MatrixButton.SOUND_B -> DIGITAL_OUTPUT_PIN_SOUND_B } val digitalState = if(on) DigitalState.HIGH else DigitalState.LOW digitalOutput(pin) { initial(digitalState) shutdown(digitalState) piGpioProvider() } } }
  26. AKA Wi fi P2P Wi- f i Direct • Useful

    for device setup • No need of network to oper a te • Android friendly • No direct w a y to setup vi a Kotlin 😢
  27. fun enableWifiDirect() { executeCommand("python3 pi_server/p2p_connector/wifi_direct_connetor.py -—target matrix8”) } fun disableWifiDirect()

    { executeCommand("python3 pi_server/src/wifi_direct_connetor.py --remove-group") } private fun executeCommand(command: String) { val processBuilder = ProcessBuilder() processBuilder.command("bash", "-c", command).start() }
  28. Ktor Http Server • Kotlin libr a ry • O

    ff ici a lly supported • Multiple engines to choose from: Jetty, Netty, Tomc t, CIO, ServletApplic tionEngine • WebSocket c a p a bilities
  29. fun Application.configureRouting() { routing { staticResources("static", "static") route("/matrix8") { get(“/pedals”)

    { call.respond(pedals) } post("/pedals") { try { val pedals = call.receive<List<Pedal>>() setPedals(pedals) call.respond(HttpStatusCode.NoContent) } catch (ex: IllegalStateException) { call.respond(HttpStatusCode.BadRequest) } catch (ex: JsonConvertException) { call.respond(HttpStatusCode.BadRequest) } } } } }
  30. Future of Matrix8 • KMP Comp a nion a pp

    (desktop/mobile) • Edit mode • MIDI port • Use the a udio input of the Pi (e.g. Guit a r Tuner) • CI/CD with distribut a ble upd a tes