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

GDG DevFest Minneapolis - How to write your own custom driver for Android Things

GDG DevFest Minneapolis - How to write your own custom driver for Android Things

Speaker slides of the How to write your own custom driver for Android Things done at the GDG DevFest Minneapolis

James Coggan

February 17, 2018
Tweet

More Decks by James Coggan

Other Decks in Technology

Transcript

  1. James Coggan How to write your own custom driver for

    Android Things https://jamescoggan.com @mad_team
  2. Wireless Remote Control Sockets ! Pros ◦ 433 MHz (~30m

    range) ◦ Cheap (6 for $25) ◦ No need for central hub ! Cons ◦ No security ◦ Not connected (needs remote)
  3. One

  4. Wireless Remote Control Sockets ! Receiver ◦ 5 volts !

    Transmitter ◦ 3 to 12 volts ◦ 20 to 200 meters (depends on voltage)
  5. ! Raspberry PI ◦ Provides 5V ◦ IO pins are

    3.3v ! Receiver signal is 5v instead of 3.3v ◦ We need a voltage divider
  6. Supported I/Os ! General Purpose Input/Output (GPIO): Binary ! Universal

    Asynchronous Receiver Transmitter (UART) ! Inter-Integrated Circuit(I2C) ! Pulse Width Modulation (PWM): Servo motors, DC motors ! Serial Peripheral Interface (SPI)
  7. class RCReceiver(val portName: String) : AutoCloseable{ private val gpio: Gpio

    init { gpio = PeripheralManagerService().openGpio(portName) gpio.setDirection(Gpio.DIRECTION_IN) gpio.setEdgeTriggerType(Gpio.EDGE_BOTH) gpio.setActiveType(Gpio.ACTIVE_HIGH) } override fun close() { gpio.close() } }
  8. private var separationLimit = 4300 // In micros private var

    lastTime = 0L private var startOfData = false private var timings = LongArray(20)
  9. private fun onGpioSwitch() { val time = (System.nanoTime() / 1000)

    // Micros val duration = time - lastTime timings[changeCount++] = duration lastTime = time }
  10. if (duration > separationLimit) { if (startOfData) { val codeInBinaryString

    = processData(changeCount) codeInBinaryString?.let { onResultListener.onResult(codeInBinaryString) } startOfData = false } else { startOfData = true } changeCount = 0 }
  11. private var code = 0L class HighLow(val high: Int, //

    High pulse duration val low: Int. // Low pulse duration )
  12. val one = HighLow(high = 120, low = 40) //

    In Microseconds val zero = HighLow(high = 40, low = 120) // In Microseconds val sync = HighLow(high = 2, low = 240) // In Microseconds
  13. private fun processData(changeCount: Int): String? { for (i in 1

    until changeCount step 2) { code = code shl 1 } }
  14. private fun processData(changeCount: Int): String? { for (i in 1

    until changeCount step 2) { code = code shl 1 // code = 00 } }
  15. private fun processData(changeCount: Int): String? { for (i in 1

    until changeCount step 2) { code = code shl 1 // code = 00 if (diff(timings[i],zero.high) < delayTolerance && diff(timings[i + 1],zero.low) < delayTolerance) { // zero, do nothing } } }
  16. One

  17. private fun processData(changeCount: Int): String? { for (i in 1

    until changeCount step 2) { ... } else if (diff(timings[i],one.high) < delayTolerance && diff(timings[i + 1], one.low) < delayTolerance) { // one } } }
  18. private fun processData(changeCount: Int): String? { for (i in 1

    until changeCount step 2) { ... } else if (diff(timings[i],oneHighDuration) < delayTolerance && diff(timings[i + 1], oneLowDuration) < delayTolerance) { // one code = code or 1 } } }
  19. private fun processData(changeCount: Int): String? { for (i in 1

    until changeCount step 2) { ... } else if (diff(timings[i],oneHighDuration) < delayTolerance && diff(timings[i + 1], oneLowDuration) < delayTolerance) { // one code = code or 1 // code = 01 } } }
  20. private fun processData(changeCount: Int): String? { for (i in 1

    until changeCount step 2) { ... else { return null // Invalid code } } }
  21. private fun processData(changeCount: Int): String? { for (i in 1

    until changeCount step 2) { … else { return null // Invalid code } } return code.asBinaryString() }
  22. fun send(code: String) { (0..repeatTimes - 1).forEach { code.forEach {

    if(it == ‘0'){ transmit(one) } else if(it == ‘1') { transmit(zero) } } } }
  23. fun send(code: String) { (0..repeatTimes - 1).forEach { code.forEach {

    if(it == ‘0’){ transmit(one) } else if(it == ‘1') { transmit(zero) } else throw IllegalArgumentException("Only binary code allowed") } } }
  24. fun send(code: String) { (0..repeatTimes - 1).forEach { code.forEach {

    if(it == ‘0’){ transmit(one) } else if(it == ‘1') { transmit(zero) } else throw IllegalArgumentException("Only binary code allowed") } transmit(sync) } }
  25. fun send(code: String) { (0..repeatTimes - 1).forEach { code.forEach {

    if(it == ‘0’){ transmit(one) } else if(it == ‘1') { transmit(zero) } else throw IllegalArgumentException("Only binary code allowed") } transmit(sync) } digitalWrite(false) }
  26. private fun digitalWrite(value: Boolean) { gpio.value = value } private

    fun busyWaitMicros(micros: Int) { val waitUntil = System.nanoTime() + micros * 1000 while (waitUntil > System.nanoTime()) { } }
  27. NO!

  28. NDK

  29. struct HighLow { uint8_t high; uint8_t low; }; const char

    *GPIO_PORT = "BCM21"; const int repeatTimes = 4; const int LOW = 0; const int HIGH = 1; const HighLow one = {40, 120}; const HighLow zero = {120, 40}; const HighLow sync = {1, 240};
  30. void send(unsigned long code, unsigned int length) { for (int

    repeat = 0; repeat < repeatTimes; repeat++) { for (int i = length - 1; i >= 0; i--) { if (code & (1L << i)) transmit(one); else transmit(zero); } transmit(sync); } digitalWrite(LOW); }
  31. Supported I/Os ! General Purpose Input/Output (GPIO): Binary ! Universal

    Asynchronous Receiver Transmitter (UART) ! Inter-Integrated Circuit(I2C) ! Pulse Width Modulation (PWM): Servo motors, DC motors ! Serial Peripheral Interface (SPI)
  32. private val uartDeviceCallback = object : UartDeviceCallback() { override fun

    onUartDeviceDataAvailable(uart: UartDevice?): Boolean { val buffer = ByteArray(8) while (uart?.read(buffer, buffer.size)!! > 0) { handleData(buffer) } return true } }
  33. Supported I/Os ! General Purpose Input/Output (GPIO): Binary ! Universal

    Asynchronous Receiver Transmitter (UART) ! Inter-Integrated Circuit(I2C) ! Pulse Width Modulation (PWM): Servo motors, DC motors ! Serial Peripheral Interface (SPI)
  34. // Arduino const uint8_t I2C_ADDRESS = 0x42;
 void setup() {

    rcSwitch = RCSwitch(); Wire.begin(I2C_ADDRESS); Wire.onRequest(requestEvent); Wire.onReceive(receiveEvent); }
  35. // Arduino
 void loop() { if (rcSwitch.codeAvailable()) { code =

    rcSwitch.getCode(); bitsize = rcSwitch.getCodeBitSize(); rcSwitch.resetCode(); } }
  36. // Arduino const uint8_t COMMAND_REQUEST_BITSIZE = 0x02; const uint8_t COMMAND_REQUEST_CODE

    = 0x05; void requestEvent() { switch (opcode) { case COMMAND_REQUEST_BITSIZE: Wire.write(bitsize); break; case COMMAND_REQUEST_CODE: Wire.write(code); break; default: Wire.write(0); break; } }
  37. // Arduino
 void receiveEvent(int bytes) { opcode = Wire.read(); if

    (bytes > 1) { if (opcode == CODE_RECEIVED_ACKNOWLEDGE) { Wire.read(); code = ""; bitsize = 0; } } }
  38. companion object { private const val I2C_ADDRESS = 0x42 private

    const val REQUEST_SIZE_IN_BITS = 0x02 private const val REQUEST_CODE = 0x05 private const val CODE_RECEIVED_ACKNOWLEDGE = 0x06 } i2cDevice = PeripheralManagerService().openI2cDevice(portName, I2C_ADDRESS)
  39. fun readCode(): String? { val size = i2CDevice.readRegByte(REQUEST_SIZE_IN_BITS).unsignedToInt() // No

    data if (size == 0) { return null } val byteArray = ByteArray(size) i2CDeviceService.readRegBuffer(REQUEST_CODE, byteArray, size) val codeInBinary = byteArray.asString() return codeInBinary }
  40. // Arduino
 if (opcode == SEND_CODE && bytes > 2)

    { int codeSize = bytes - 1; String codeReceived = String(); while (Wire.available() != 0) { char c = Wire.read(); Serial.print(c); codeReceived += c; c++; }
  41. companion object { private const val SEND_CODE = 0x07 }

    fun sendCode(code: String) { i2cDevice.writeRegBuffer(SEND_CODE, code.toByteArray(), code.length) }
  42. val handler = Handler() val runnable = object : Runnable

    { override fun run() { readCode() handler.postDelayed(this, 1000) } } handler.postDelayed(runnable, 100)
  43. // Arduino
 void loop() { if (rcSwitch.codeAvailable()) { code =

    rcSwitch.getCode(); bitSize = rcSwitch.getCodeBitSize(); rcSwitch.resetCode(); } }
  44. // Arduino
 void loop() { if (rcSwitch.codeAvailable()) { code =

    rcSwitch.getCode(); rcSwitch.resetCode(); digitalWrite(GPIO_PIN, HIGH); } }
  45. // Arduino
 void receiveEvent(int bytes) { opcode = Wire.read(); if

    (bytes > 1) { if (opcode == CODE_RECEIVED_ACKNOWLEDGE) { Wire.read(); code = ""; bitsize = 0; receivedProtocol = 0; receivedPulseLength = 0; } } }
  46. void receiveEvent(int bytes) { opcode = Wire.read(); if (bytes >

    1) { if (opcode == CODE_RECEIVED_ACKNOWLEDGE) { Wire.read(); code = ""; bitsize = 0; receivedProtocol = 0; receivedPulseLength = 0; digitalWrite(GPIO_PIN, LOW); } } }
  47. private val listener = object : GpioCallback() { override fun

    onGpioEdge(gpio: Gpio?): Boolean { readCode() return false } override fun onGpioError(gpio: Gpio?, error: Int) { Log.e("TAG", “GPIO Error!") } }
  48. Supported I/Os ! General Purpose Input/Output (GPIO): Binary ! Inter-Integrated

    Circuit(I2C) ! Universal Asynchronous Receiver Transmitter (UART) ! Pulse Width Modulation (PWM): Servo motors, DC motors ! Serial Peripheral Interface (SPI)
  49. Supported I/Os https://github.com/jamescoggan/remotecontrolarduino ! Library ! Tests ! Example app

    ! Arduino files ! Diagrams (pending) Separate repo: ! GPIO code (the one that didn’t work)