Voice control for NeoPixel

1 312 Easy

This tutorial should teach you, how easy you can control NeoPixel via the Gravity: AI Offline Language Learning Voice Recognition Sensor and mostly any ESP32 micro controller. The language will be MicroPython.

HARDWARE LIST
1 Gravity: AI Offline Language Learning Voice Recognition Sensor
1 ESP32
1 NeoPixel/WS2812

In the first step you connect all the wires. Depending on the respective model, please note the respective GPIO pins! In my example the following pins are used:

 

ESP32 to NeoPixel:

 

5V to 5V

GND to GND

16 to DIN

 

ESP32 to Sensor:

 

5V to VCC

GND to GND

21 (SDA) to D/T

22 (SCL) to C/R

 

 

Before you supply the ESP with power, make sure that you select the Communication Mode: I2C and SPK1 on the sensor. If you need/want to assign other pins (on the ESP32), adjust them in the MicroPython script.

Now all you need is the MicroPython code and the fun can begin. So that you can reuse the code at any time and keep the whole thing a little clearer, we split it up. In the first step we create the driver for the sensor. I have already converted the existing Raspberry driver for Micropython I2C. You can adapt and expand this code as you wish at any time! Just name this file: DFRobot_DF2301Q_I2C.py.

CODE
from micropython import const
from machine import I2C, Pin
from utime import sleep


DF2301Q_I2C_ADDR = const(0x64)
DF2301Q_I2C_REG_CMDID = const(0x02)
DF2301Q_I2C_REG_PLAY_CMDID = const(0x03)
DF2301Q_I2C_REG_SET_MUTE = const(0x04)
DF2301Q_I2C_REG_SET_VOLUME = const(0x05)
DF2301Q_I2C_REG_WAKE_TIME = const(0x06)
DF2301Q_I2C_8BIT_RANGE = const(0xFF)
DF2301Q_I2C_MSG_TAIL = const(0x5A)
DF2301Q_I2C_GET_CMDID_DURATION = const(0.05)
DF2301Q_I2C_PLAY_CMDID_DURATION = const(1)


class DFRobot_DF2301Q_I2C:
    """
    MicroPython class for communication with the DF2301Q from DFRobot via I2C
    """

    def __init__(self, sda, scl, i2c_addr=DF2301Q_I2C_ADDR, i2c_bus=0):
        """
        Initialize the DF2301Q I2C communication
        :param sda: I2C SDA pin
        :param scl: I2C SCL pin
        :param i2c_addr: I2C address
        :param i2c_bus: I2C bus number
        """
        self._addr = i2c_addr
        self._i2c = I2C(i2c_bus, sda=Pin(sda), scl=Pin(scl))

    def _write_reg(self, reg, data) -> None:
        """
        Writes data to the I2C register
        :param reg: register address
        :param data: data to write
        :return: None
        """
        if isinstance(data, int):
            data = [data]

        self._i2c.writeto_mem(self._addr, reg, bytearray(data))

    def _read_reg(self, reg):
        """
        Reads data from the I2C register
        :param reg: address of device
        :return: bytes
        """
        data = self._i2c.readfrom_mem(self._addr, reg, 1)
        return data[0]

    def get_cmdid(self) -> int:
        """
        Returns the current command id
        :return: int
        """
        sleep(DF2301Q_I2C_GET_CMDID_DURATION)
        return self._read_reg(DF2301Q_I2C_REG_CMDID)

    def get_wake_time(self) -> int:
        """
        Returns the current wake-up duration
        :return: int
        """
        return self._read_reg(DF2301Q_I2C_REG_WAKE_TIME)

    def play_by_cmdid(self, cmdid: int) -> None:
        """
        Play the current command words by command id
        :param cmdid: command words as integer
        :return: None
        """
        self._write_reg(DF2301Q_I2C_REG_PLAY_CMDID, int(cmdid))
        sleep(DF2301Q_I2C_PLAY_CMDID_DURATION)

    def set_wake_time(self, wake_time: int) -> None:
        """
        Set the wake-up duration of the device
        :param wake_time: integer between 0 and 255
        :return: None
        """
        wake_up_time = int(wake_time) & DF2301Q_I2C_8BIT_RANGE
        self._write_reg(DF2301Q_I2C_REG_WAKE_TIME, wake_up_time)

    def set_volume(self, vol: int) -> None:
        """
        Set the volume of the device
        :param vol: integer between 1 and 7
        :return: None
        """
        self._write_reg(DF2301Q_I2C_REG_SET_VOLUME, int(vol))

    def set_mute_mode(self, mode) -> None:
        """
        Set the mute mode of the device
        :param mode: integer 0 for off, 1 for on
        :return: None
        """
        self._write_reg(DF2301Q_I2C_REG_SET_MUTE, int(bool(mode)))

Now let's get to the actual script named: main.py. The respective constants are specified here (for example: the pins used, number of NeoPixels, etc.) as well as the program logic to switch the light on/off and change the color. You can also adapt and expand this example at any time.

CODE
from lib.DFRobot_DF2301Q_I2C import DFRobot_DF2301Q_I2C
from machine import Pin
from micropython import const
from utime import sleep
from neopixel import NeoPixel


SDA_PIN = const(21)
SCL_PIN = const(22)

NEOPIXEL_PIN = const(16)
NEOPIXEL_NUMBER = const(5)

SLEEP_SECONDS = const(1)
TURN_ON_COMMAND = const(103)
TURN_OFF_COMMAND = const(104)
COLOR_COMMANDS = {
    116: ("red", (250, 0, 0)),
    117: ("orange", (250, 165, 0)),
    118: ("yellow", (250, 255, 204)),
    119: ("green", (0, 250, 0)),
    120: ("cyan", (0, 255, 255)),
    121: ("blue", (0, 0, 250)),
    122: ("purple", (128, 0, 128)),
    123: ("white", (250, 250, 250))
}


def setup(sensor) -> None:
    """
    Set up the DFRobot DF2301Q sensor
    :param sensor: instance of DFRobot_DF2301Q_I2C
    :return: None
    """
    sensor.set_volume(5)
    sensor.set_mute_mode(0)
    sensor.set_wake_time(20)


def get_cmd_id(sensor) -> int:
    """
    Get the command id back from the DF2301Q sensor
    :param sensor: instance of DFRobot_DF2301Q_I2C
    :return: int
    """
    command_id = sensor.get_cmdid()

    if command_id != 0:
        return int(command_id)


def set_color(neopixel, rgb) -> None:
    """
    Set the color of neopixel
    :param neopixel: instance of neopixel
    :param rgb: rgb color as tuple
    :return: None
    """
    neopixel.fill(rgb)
    neopixel.write()


if __name__ == "__main__":
    nps = NeoPixel(Pin(NEOPIXEL_PIN), NEOPIXEL_NUMBER)
    voice_sensor = DFRobot_DF2301Q_I2C(sda=SDA_PIN, scl=SCL_PIN)
    setup(sensor=voice_sensor)

    last_color = None
    light_on = False

    print('Speak your commands:')

    while True:
        cmd_id = get_cmd_id(sensor=voice_sensor)

        if isinstance(cmd_id, int):
            print(f'COMMAND ID: {cmd_id}')

            command_info = COLOR_COMMANDS.get(cmd_id)

            # turn on the light
            if cmd_id == TURN_ON_COMMAND:
                light_on = True

                if not last_color:
                    set_color(neopixel=nps, rgb=(100, 100, 100))
                else:
                    set_color(neopixel=nps, rgb=last_color)

            # turn off the light
            if cmd_id == TURN_OFF_COMMAND:
                light_on = False
                set_color(neopixel=nps, rgb=(0, 0, 0))

            # set to [color name]
            if command_info is not None and light_on:
                name, color = command_info
                last_color = color
                set_color(neopixel=nps, rgb=color)

        sleep(SLEEP_SECONDS)

We are already done! Now connect your computer to the ESP32 and load the two MicroPython scripts onto the ESP32 microcontroller. Here is an example with RSHELL:

$ rshell -p /dev/cu.usbserial-0001 cp main.py /pyboard/main.py

$ rshell -p /dev/cu.usbserial-0001 cp lib/DFRobot_DF2301Q_I2C.py /pyboard/lib/DFRobot_DF2301Q_I2C.py

 

Notice!!!! The path and the name of the device (/dev/cu.usbserial-0001) may be different for you! Please adapt for your needs.

 

If you have successfully uploaded the two files to the ESP32, you can start the REPL and test everything.

 

$ rshell -p /dev/cu.usbserial-0001 repl

 

Press CTRL + d keys for a soft reset. In the terminal you should read "Speak your commands:". Say the wakeup word and then “Turn on the light”. The NeoPixels should start to glow. For example, you can change the color with “Set to blue”. With “Turn off the light” you turn the NeoPixels off again.

 

 

That's it already! If you like you can find the code and the example here.

License
All Rights
Reserved
licensBg
1