DFRobot Gravity Offline Voice Recognition Module Robot Control

In this project I utilize a DFRobot Gravity Offline Voice Recognition module to control a Adeept Omni-Directional Robot Kit. This robot kit is pretty cool as it has a Banana Pi PicoW S3 with it and the omni-directional capabilities allow you to move in unique ways.


I have used the voice recognition module previously for a few projects:

https://github.com/Cosmic-Bee/df2301q.rs – added rust support for the module

https://www.youtube.com/watch?v=k-IBDOczJ5I - used with stm32duino to control the onboard WS2812 and attached LED strip


You can either use I2C or UART to connect to the module. DFRobot provides a library for Arduino and Python. For the purposes of this guide I've gone ahead and modified the underlying Python library's I2C implementation to work with CircuitPython. I didn't support UART in my fork as I wasn't using it for this project.


If you're interested in 3D printed enclosure for the module: https://www.printables.com/model/788025-dfrobot-offline-voice-recognition-robot-robo-enclo

1 Gravity: Offline Language Learning Voice Recognition Sensor (Affiliate Link)
1 Adeept 4WD Omni-directional Mecanum Wheels Robotic Car Kit for ESP32-S3

I previously received one of these 4WD Omni-Directional mecanum car kit from Adeept and wrote an article (https://www.cranberrygrape.com/mini-projects/adeept-omni-car/) where I went through the lessons from the car itself and discussed the code. It was a nice tutorial series and was helpful for my understanding of servos and motors as I have little experience in that area. That article can be referenced for some information about the robot and its associated functionality.

Prepare the Robot's Prerequisites

For more information about the robot car kit you can check my article on the lessons from the Adeept tutorials with the car. If you're not using the Adeept car I would suggest you become familiar with the model you have on hand and its associated capabilities. As long as it surfaces an I2C connection you should be able to work with the DFRobot module without issue.


This guide assumes the existing control libraries from the Adeept code bundle exist on the CircuitPython drive and that the robot has been properly configured and tested.


In my fork of the DFRobot_DF2301Q I have the CircuitPython library I've put together from the original Python:


I've also included an additional file to make it easier to work with the commands:



You can see from this example below how the library works with CircuitPython:

# -*- coding: utf-8 -*
  @file  i2c.py
  @brief  Control the voice recognition module via I2C
  @n  Get the recognized command ID and play the corresponding reply audio according to the ID;
  @n  Get and set the wake-up state duration, set mute mode, set volume, and enter the wake-up state
  @copyright  Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
  @licence  The MIT License (MIT)
  @author  [qsjhyy](yihuan.huang@dfrobot.com)
  @version  V1.0
  @date  2022-12-30
  @url https://github.com/DFRobot/DFRobot_DF2301Q

import time
import board
import busio
from DFRobot_DF2301Q import DFRobot_DF2301Q_I2C, DF2301Q_I2C_ADDR
from DFRobot_DF2301Q_Commands import CommandWord

# Initialize I2C bus
i2c = busio.I2C(board.GP21, board.GP20)

# Create instance of the I2C device
DF2301Q = DFRobot_DF2301Q_I2C(i2c, i2c_addr=DF2301Q_I2C_ADDR)

def setup():
      @brief Set voice volume
      @param vol - Volume

      @brief Set mute mode
      @param mode - Mute mode; set value 1: mute, 0: unmute

      @brief Set wake-up duration
      @param wakeTime - Wake-up duration(0-255)

      @brief Get the wake-up duration
      @return The current set wake-up period
    print("wake_time = %u\n" % (DF2301Q.get_wake_time()))

      @brief Play the corresponding reply audio according to the command word ID
      @param CMDID - Command word ID
      @note Can enter wake-up state through ID-1 in I2C mode
    # DF2301Q.play_by_CMDID(CommandWord.HelloRobot)   # Wake-up command
    DF2301Q.play_by_CMDID(CommandWord.Retreat)   # Common word ID

def loop():
    CMDID = DF2301Q.get_CMDID()
    if CMDID != 0:
        print("CMDID = %u\n" % CMDID)

if __name__ == "__main__":
    while True:

This example sets the volume of the module, makes sure it is unmuted, and then sets a wake-up duration of 20.


After this it just loops continually checking to see if a command has been sent. If one is sent it prints it. The logic for the car control is very similar in that it's constantly waiting for a subset of the full command list and acting accordingly when it is triggered.

Adjust the BPI_PicoW_S3_Car.py from Adeept's provided code
class LCD1602:
    def __init__(self, i2c):
        self.i2c = i2c
        self.lcd = LCD(I2CPCF8574Interface(self.i2c, 0X27), num_rows=2, num_cols=16)
    def clear(self):

    def print(self, string):

    def cleanup(self):

For this step open the BPI_PicoW_S3_Car.py file provided by Adeept and modify the LCD1602 class to accept i2c as a parameter versus configuring it internally to the code. Note: this will break the other examples from Adeept as they expect the i2c bus to be configured internally during initialization. 

Copy the associated files to the CircuitPython drive

If you were following the Adeept setup instructions most of the underlying library code for controlling the robot will be on your device already. In this step download and store the offline voice recognition module code to the root of your device.


I've linked them here for convenience: 



I've also added them zipped here to the guide.

icon DFRobot_DF2301Q.zip 3KB Download(1)
Update the CircuitPython main.py code for the robot control logic

Finally update the main.py file to include the robot control logic.


I've attached the main.py file I used as a zip here but have it also available as a gist: https://gist.githubusercontent.com/Timo614/ff64dcbae994308eb0a2c67bfcb982e4/raw/16dd6cd65dc7e876fd90ed5336201244f1d408d6/main.py

icon main.zip 2KB Download(0)
# Function to handle voice commands
def handle_voice_command(CMDID):
    global brightness
    command_name = ""

    if CMDID == CommandWord.GoForward:
        motor.move(1, 'forward', speed_low)
        command_name = "Go Forward"
    elif CMDID == CommandWord.Retreat:
        motor.move(1, 'backward', speed_low)
        command_name = "Retreat"
    elif CMDID == CommandWord.ParkACar:
        command_name = "Park a Car"
    elif CMDID == CommandWord.TurnOnTheLight:
        update_rgb_color(255, 255, 255)
        command_name = "Turn On The Light"
    elif CMDID == CommandWord.TurnOffTheLight:
        update_rgb_color(0, 0, 0)
        command_name = "Turn Off The Light"
    elif CMDID == CommandWord.BrightenTheLight:
        if brightness < 1.0:
            brightness += 0.1
        command_name = "Brighten The Light"
    elif CMDID == CommandWord.DimTheLight:
        if brightness > 0.0:
            brightness -= 0.1
        command_name = "Dim The Light"
    elif CMDID == CommandWord.SetToRed:
        update_rgb_color(255, 0, 0)
        command_name = "Set To Red"
    elif CMDID == CommandWord.SetToOrange:
        update_rgb_color(255, 165, 0)
        command_name = "Set To Orange"
    elif CMDID == CommandWord.SetToYellow:
        update_rgb_color(255, 255, 0)
        command_name = "Set To Yellow"
    elif CMDID == CommandWord.SetToGreen:
        update_rgb_color(0, 255, 0)
        command_name = "Set To Green"
    elif CMDID == CommandWord.SetToCyan:
        update_rgb_color(0, 255, 255)
        command_name = "Set To Cyan"
    elif CMDID == CommandWord.SetToBlue:
        update_rgb_color(0, 0, 255)
        command_name = "Set To Blue"
    elif CMDID == CommandWord.SetToPurple:
        update_rgb_color(128, 0, 128)
        command_name = "Set To Purple"
    elif CMDID == CommandWord.SetToWhite:
        update_rgb_color(255, 255, 255)
        command_name = "Set To White"
    elif CMDID == CommandWord.Reset: 
        servo.set_angle(board.GP7, 0)
        command_name = "Set Servo To 0 Degrees"
    elif CMDID == CommandWord.SetServoToTenDegrees: 
        servo.set_angle(board.GP7, 10)
        command_name = "Set Servo To 10 Degrees"
    elif CMDID == CommandWord.SetServoToThirtyDegrees:  
        servo.set_angle(board.GP7, 30)
        command_name = "Set Servo To 30 Degrees"
    elif CMDID == CommandWord.SetServoToFortyFiveDegrees:  
        servo.set_angle(board.GP7, 45)
        command_name = "Set Servo To 45 Degrees"
    elif CMDID == CommandWord.SetServoToSixtyDegrees:  
        servo.set_angle(board.GP7, 60)
        command_name = "Set Servo To 60 Degrees"
    elif CMDID == CommandWord.SetServoToNinetyDegrees:  
        servo.set_angle(board.GP7, 90)
        command_name = "Set Servo To 90 Degrees"
    elif CMDID == CommandWord.TurnOnTheBuzzer:  
        command_name = "Turn On The Buzzer"
    elif CMDID == CommandWord.TurnOffTheBuzzer:  
        command_name = "Turn Off The Buzzer"

    if command_name:

def setup():
    print("wake_time = %u\n" % (DF2301Q.get_wake_time()))

def loop():
    CMDID = DF2301Q.get_CMDID()
    if CMDID != 0:
        print("CMDID = %u\n" % CMDID)

if __name__ == "__main__":
    while True:

I've included the relevant portions of the code above to highlight the inclusion of the DFRobot module. You can see each iteration of the loop it attempts to get the command from the module and if it finds one it proceeds to handle that command.


It's fairly straightforward and a neat way to control the robot. My three year old daughter is actually able to control it as a result. The module does a good job listening for commands and I rarely need to repeat them unless it's a larger one and I don't enunciate the word correctly.



All Rights