Hackster is hosting Hackster Holidays, Finale: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Tuesday!Stream Hackster Holidays, Finale on Tuesday!
Gautier Mechling
Published © Apache-2.0

Room Automation for Kids

A Google Assistant compatible box for kids (from 2 years old) to control lights and other devices in their room.

IntermediateFull instructions provided4 hours15,837
Room Automation for Kids

Things used in this project

Hardware components

PICO-IMX7
TechNexion PICO-IMX7
NXP i.MX7D
×1
Adafruit Mini USB Microphone
×1
RC522 RFID Module
×1
Arduino Pro Mini 328 - 3.3V/8MHz
SparkFun Arduino Pro Mini 328 - 3.3V/8MHz
Acts as a proxy between the RC522 and the Android Things board
×1
Big Red Dome Button
SparkFun Big Red Dome Button
Red, Green, Blue, Yellow, White, Black
×6
Resistor 221 ohm
Resistor 221 ohm
Pull-up resistors
×5
Resistor 1k ohm
Resistor 1k ohm
Pull-up resistor for the last button, connected to the 1.8V GPIO
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Breadboard (generic)
Breadboard (generic)
×1
Audio Speakers 3.5mm jack USB powered
Or any compatible audio speaker
×1

Software apps and online services

Android Things
Google Android Things
Assistant SDK
Google Assistant SDK

Story

Read more

Schematics

NXP Pico i.MX7D I/O Pinout

Schematics

Code

Rc522LiveData

Java
A LiveData wrapper that emits RFID ids received from the Arduino UART interface
class Rc522LiveData : LiveData<String>() {

    companion object {
        private const val UART_NAME = "UART6"
        private const val UART_BUFFER_SIZE = 512
    }

    private var handler: Handler? = null
    private var handlerThread: HandlerThread? = null

    private var uartDevice: UartDevice? = null
    private var pendingUartData = ""

    private val callback = object : UartDeviceCallback() {
        // This method is called when the Arduino write data to the Serial interface. We will get data and emit a LiveData value
        override fun onUartDeviceDataAvailable(uart: UartDevice): Boolean {
            val buffer = ByteArray(UART_BUFFER_SIZE)
            val read = uart.read(buffer, UART_BUFFER_SIZE)

            if (read > 0) {
                postValue(buffer)
            }

            return true
        }

        override fun onUartDeviceError(uart: UartDevice, error: Int) {
            Log.e(TAG, "UART device error ($error)")
        }
    }

    override fun onActive() {
        openUart()
    }

    override fun onInactive() {
        closeUart()
    }

    // Open the UART connection
    private fun openUart() {
        handlerThread = HandlerThread(TAG).also { handlerThread ->
            handlerThread.start()
            handler = Handler(handlerThread.looper)
        }

        uartDevice = PeripheralManagerService().openUartDevice(UART_NAME).apply {
            setBaudrate(9600)
            setDataSize(8)
            setParity(UartDevice.PARITY_NONE)
            setStopBits(1)

            registerUartDeviceCallback(callback, handler)
        }
    }

    // Close the UART connection
    private fun closeUart() {
        handler = null
        handlerThread?.quitSafely().also { handlerThread = null }

        uartDevice?.let {
            it.unregisterUartDeviceCallback(callback)
            it.close()
        }.also { uartDevice = null }
    }
}

Arduino Sketch

Arduino
#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN 9
#define SDA_PIN 10

MFRC522 mfrc522;

void setup() {
  Serial.begin(9600);
  while (!Serial);

  SPI.begin();
  mfrc522.PCD_Init(SDA_PIN, RST_PIN);
}

void loop() {
  // Forward Uid to the Serial interface
  if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
    writeUidToSerial(mfrc522.uid.uidByte, mfrc522.uid.size);
    mfrc522.PICC_HaltA();
  }
}

void writeUidToSerial(byte *uid, byte size) {
  for (byte i = 0; i < size; i++) {
    if (uid[i] < 0x10) {
      Serial.print("0");
    }
    Serial.print(uid[i], HEX);
  }
  Serial.println();
}

ButtonsLiveData

Java
class ButtonsLiveData : LiveData<Pair<DataLayerButton, Boolean>>() {

    companion object {
        private const val BUTTON_DEBOUNCE_DELAY_MS = 20L
    }

    private val DataLayerButton.gpio: String
        get() = when (this) {
            DataLayerButton.RED -> "GPIO_37"
            DataLayerButton.GREEN -> "GPIO_32"
            DataLayerButton.BLUE -> "GPIO_39"
            DataLayerButton.YELLOW -> "GPIO_34"
            DataLayerButton.WHITE -> "GPIO_33"
            DataLayerButton.BLACK -> "GPIO_174"
        }

    // This map is helpful to know which button is pressed
    private var buttons: Map<DriverLayerButton, DataLayerButton>? = null

    // When a button event is received, we emit the button and its state (pressed=true, release=false)
    private val listener = OnButtonEventListener { button: DriverLayerButton, pressed: Boolean ->
        buttons?.get(button)?.let {
            value = it to pressed
        }
    }

    // Here, we open and setup all buttons according to their GPIO names
    override fun onActive() {
        buttons = DataLayerButton.values()
                .map {
                    DriverLayerButton(it.gpio, DriverLayerButton.LogicState.PRESSED_WHEN_LOW).apply {
                        setDebounceDelay(BUTTON_DEBOUNCE_DELAY_MS)
                        setOnButtonEventListener(listener)
                    } to it
                }
                .toMap()
    }

    // We close buttons when the LiveData is inactive
    override fun onInactive() {
        buttons?.let { buttons ->
            buttons.forEach { (button, _) ->
                button.setOnButtonEventListener(null)
                button.close()
            }
        }.also {
            buttons = null
        }
    }
}

Full project on GitHub

See the README.md file for build information

Credits

Gautier Mechling
2 projects • 13 followers
Software Craftsman

Comments