top of page

Using Arduino Controllers for Sensor and Animatronic Applications

Updated: Sep 28, 2022


Introduction to Arduino


Years ago, it would have been difficult to impossible for the average person to implement and use animatronics, sensors, and other kinds of devices. Not only would it have required an electrical engineering degree, but the time and cost involved would have been ridiculously prohibitive. Arduino changed all that. Arduino is a company that creates and promotes open standard micro-controllers. You can find a lot of information about Arduino (and Arduino-like) micro-controllers online. If you’ve never read anything about them, micro-controllers are simple low-power chips (usually less than 9V DC) that have a number of input and output pins that can be easily programmed using a C++/C-like language to accept input from sensors and drive external devices.


People are using these controllers to drive all kinds of devices (robots, RC cars, drones) and do real-world measurement (temperature, moisture, speed, position, etc). Micro-controllers come in all kinds of different form factors, with varying numbers of pins, power requirements, and some (like my current favorite -- the ESP32 controller) even have integrated on-board Wifi, Bluetooth, and cellular communication components. Arduino has also standardized the programming environment for micro-controllers to use a simple C++/C-like language.


Here's a very simple Arduino program. When the Arduino chip starts up, it calls the setup() function. The primary thing that you need to do here is identify which pin numbers you be using and whether they are for INPUT (e.g. reading from a sensor) or OUTPUT (controlling something that’s attached to it). After it has been initialized, the Arduino chip will call the loop() function repeatedly until the chip is powered down or restarted (at which point the whole process of setup() and then loop() will be repeated). Every Arduino micro-controller runs at a particular clock speed (the rate at which it executes code). Some micro-controllers can run at different (settable) clock rates. Using higher clock rates invariably consumes more power. Choosing a clock speed is a tradeoff between how fast you need to process data and how much power you are allowed to consume. Serial.begin(115200) tells Arduino to establish a console connection at 115200bps.


void setup()

{

Serial.begin(115200); // initialize the debugging serial console

pinMode(11,INPUT); // Pin 11 is for input (a sensor)

pinMode(12, OUTPUT); // Pin 22 is for output (LED)


}


void loop()

{

if (digitalRead(11) != 0) { // if the sensor detects something…

digitalWrite(12, HIGH); // turn on the LED

} else {

digitalWrite(12, LOW); // turn off the LED

}

}


You can download the Arduino IDE (Integrated Development Environment) here: https://www.arduino.cc/en/software. This IDE is available on Windows, Mac OS, and Linux. Before you run the IDE for the first time, you will need to connect your Arduino to your computer via a USB cable. This cable provides power to the Arduino controller and allows the IDE to send commands to the Arduino controller. It also uploads your code to the Arduino for it to execute. Before you can compile and run code, you will need to tell the IDE (via the Tools menu) which brand and model of Board (aka Arduino controller) you are using and other parameters (upload speed, clock speed, etc). Once this Is set up properly, you can create a new “sketch” (aka program) and start writing code (like the code above). Occasionally, you will want to run your code via Sketch | Verify/Compile and Sketch | Upload.


Assuming that your code compiles and uploads successfully, it will start executing on the connected Arduino controller. Great, right? Well, not entirely. The Arduino IDE is very basic, so you’re not going to be able to set breakpoints or step through your code line-by-line. You will need to sprinkle Serial.println(“something happened”) debugging statements in your code to get a better idea of what’s happening—and why. One more thing to get you rolling: There are lots of so-called Include Libraries available from Arduino and third-parties to control sensors and external devices that you can use via Sketch | Include Library. My advice is to read the documentation for these libraries carefully. Quite often, they make very explicit assumptions about which input and output pins that they will use; so, if you wire your input and output pins differently from their expectations, they won’t work.


This article has been a jumpstart into programming Arduinos. We've only dipped our toe in the pool of Arduino concepts, though. There's. a lot more. to learn. If you are interested in building Halloween-related animatronics, lighting displays, and other kinds of special effects, Arduino micro-controllers are perfect for this purpose. I used an Arduino Mega for my Beetlejuice Graveyard Sign project. You can read about it here.



The range of IoT devices in sensors, robots, drones, and other sorts of automation devices is expanding rapidly. You can find sensors which measure pretty much anything you can imagine: Temperature, position (accelerometer, gyroscope, GPS), humidity, water-level, motion/proximity, visible light, ultraviolet emissions, pressure, radio spectrum, touch, etc. Which allows you to build almost anything. I bought an Elegoo 37-in-1 Sensor Kit that contains a good set of sensors (including the Linear Hall Sensor used in this project).





Check the specs for the sensors that you intend to hook up to an Arduino. Many of them are designed to consume 3VDC of power. That’s convenient, because Arduino pins tend to output 3VDC. If you need to reduce the power, use Ohm’s law (V = IR) to calculate a resistor suitable for lowering the voltage supplied to the sensor. If the external device requires more power than the Arduino can supply, you’re going to need a separate power supply and control the power supply to the sensor with a Relay.


Introducing the ESP32


One of my favorite Arduino controllers is the ESP32. The ESP32 is a fantastic little IoT chip to build your projects around. It’s a dual-core 32-bit processor (pretty powerful) with 520KB RAM and runs at a range of clock speeds. The form factor is tiny. It has 32 programmable GPIO ports and 3 UARTs (hardware serial devices). One of the strongest selling points, though, is its onboard WiFi and Bluetooth components (unlike Arduinos which require additional shield boards to get the same functionality). It also has 4MB of IDF flash memory with a FAT filesystem structure and supports up to 10K writes per sector. You do not want to use IDF flash for telemetry or high volume data. ESP32 has SDIO support for adding external SD cards.






ESP32 has the ability to run in low-power configurations which allow you to suspend device usage and run for months on battery. It can run on as little as 3.3V but, if you use WiFi and Bluetooth, you will want 5V 1A power (which you can get from its micro-USB). ESP32 is also found in specialized configurations such as ESP32-CAM which incorporates a 1920x1080 camera. Imagine getting this kind of functionality for $4-7 per chip. Seriously. It’s amazing ROI.


Here's a really useful ESP32 Breakout Board that you can use for prototyping. You mount the ESP32 on the lefthand side of the board (NOTE: you really need to make sure you get the right ESP32 chip for this board so that the pin configuration matches), and then simply wire connections via the convenient screw terminal block on the right side. Very convenient.



Garage Door Sensor Project


Here’s a little project that I did recently for my father. He occasionally forgets to close his garage door and wanted to be alerted. Since he has poor vision and doesn’t have the ability to use a cell phone, the alert needs to be audible. I should point out that you can buy this sort of thing commercially. I wanted to learn more about ESP32 micro-controllers, though, and since the whole project cost less than $20, I decided to move forward with it. The basic theory of the project is to use two micro-controllers: One (the “Server”) in the garage senses the position of the garage door with a magnetic Hall Sensor, and the other (the “Client”) in my father’s room sends occasional Web requests to the “Server” to ask its current state) and provides an audible cue when the door is open.



Here's what the completed project looks like:



The Server


I decided to use a Linear Hall Sensor to detect when the garage door goes up and down. The idea behind this sensor is simple. It contains a wire coil at its tip. When the coil encounters a magnetic field, the sensor will go HIGH (value = 1); when there is no magnetic field detected, the sensor will go LOW (value = 0). Here’s a closer look at the Linear Hall Sensor. It has 4 pins (Analog, GND, 3V3+, and Digital). I did not use the Analog pin because we are not interested in measuring the strength of the magnetic field. The Digital pin (as the name implies) supplies a value of 0 (no magnetic field) or 1 (magnetic field detected).









I used a 4-wire connector to connect from the D0 (Digital) sensor to Pin 23 of the ESP32. I could have used other pins. Pin 23 was chosen to keep the wires on the same side of the controller. I connected GND and 3V3 from the sensor to corresponding pins on the ESP32. Finally, I attached a small magnet to the leading track connector on the garage door, and glued the Linear Hall Sensor so that the magnet was in range of the sensor’s wire coil when the door was fully up. So, the sensor trips when the door is up, and goes low when the door is down. Pretty simple, really. I also added a added a LED (with a protective resistor since the LED is only rated for 1.5V) at Pin 18 (again, for convenience).



Now, for the software. One of the more interesting aspects of this project is that I’m implementing a basic Web server on the ESP32. I setup the Linear Hall Sensor on pin 23. I use the WifiServer library to connect to Wifi at the specified ssid and password, and implement the Web server on port 80. The loop just waits for requests and, if the client requests GET /garage.json, it reads the Hall Sensor, and it uses the ArduinoJson library to format and return a JSON blob { “garageState” : “Up:” } or { “garageState” : “Down” }. If the client browses to any other Uri, the code just returns a Web page that displays the garage door’s current state.


Here’s an important detail. I decided to reserve an IP address for this ESP32 Server on the Wifi router. You simply login to your WiFi router, and reserve a particular IP address for the device in the DHCP settings. This was not strictly necessary, but it reduces the complexity of the Client-side device because it knows that the Server has a reserved IP address and, therefore, it doesn’t need to search for it.


Server.ino


#include <WiFi.h>

#include <ArduinoJson.h>

#include "HallSensor.h"


const char *ssid = "YourWifiSSID";

const char *password = "YourWifiPassword";


int hallSensorPin = 23;


// Set web server port number to 80

WiFiServer server(80);


HallSensor sensor(hallSensorPin);


// Variable to store the HTTP request

String header;


unsigned long currentTime = millis();

unsigned long previousTime = 0;

const long timeoutTime = 5000;


#define LED 18


void setup()

{

Serial.begin(115200);


sensor.setup();


pinMode(LED, OUTPUT);


// Connect to Wi-Fi network with SSID and password

Serial.print("Connecting to ");

Serial.println(ssid);


WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED)

{

delay(500);

Serial.print(".");

}


// Print local IP address and start web server

Serial.println("");

Serial.println("WiFi connected.");

Serial.println("IP address: ");

Serial.println(WiFi.localIP());


digitalWrite(LED, HIGH);

server.begin();

}


void loop()

{

WiFiClient client = server.available(); // Listen for incoming clients

bool handledRequest = false;


if (client) // If a new client connects,

{

currentTime = millis();

previousTime = currentTime;

String currentLine = ""; // make a String to hold incoming data from the client

while (client.connected() && currentTime - previousTime <= timeoutTime) // loop while the client's connected

{

currentTime = millis();

if (client.available()) // if there's bytes to read from the client,

{

handledRequest = true;

char c = client.read(); // read a byte, then

header += c;

if (c == '\n') // if the byte is a newline character

{

// if the current line is blank, you got two newline characters in a row.

// that's the end of the client HTTP request, so send a response:

if (currentLine.length() == 0)

{

bool htmlResponseB = true;

Serial.println("Begin reponse");


if (header.indexOf("GET /garage.json") >= 0)

{

Serial.println("GET /garage.json");

htmlResponseB = false;

}


// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)

// and a content-type so the client knows what's coming, then a blank line:

client.println("HTTP/1.1 200 OK");


if (htmlResponseB)

{

client.println("Content-type:text/html");

}

else

{

client.println("Content-type:application/json");

}


client.println("Connection: close");

client.println();


int state = sensor.readDigital();

String upDown = (state ? "Up" : "Down");


if (htmlResponseB)

{

// Display the HTML web page

client.println("<!DOCTYPE html><html>");

client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");

client.println("<head><meta http-equiv=\"refresh\" content=\"5\"></head>");

client.println("<link rel=\"icon\" href=\"data:,\">");


// CSS to style the HTML

// Feel free to change the background-color and font-size attributes to fit your preferences


client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");

client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");

client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");

client.println(".button2 {background-color: #555555;}</style></head>");


// Web Page Heading

client.println("<body><h1>Dad's Garage Door Server</h1>");

client.println("<p>Garage Door State: " + upDown + "</p>");

client.println("</body></html>");

}

else

{

DynamicJsonDocument doc(512);

doc["garageState"] = upDown;

serializeJson(doc, client);

}


// The HTTP response ends with another blank line

client.println();

// Break out of the while loop

break;

}

else // if you got a newline, then clear currentLine

{

currentLine = "";

}

}

else if (c != '\r') // if you got anything else but a carriage return character,

{

currentLine += c; // add it to the end of the currentLine

}

}

}

header = "";

if (handledRequest)

{

Serial.println("End response");

}

client.stop();

}

}



Here’s the Hall Sensor code.


HallSensor.h


#ifndef __HALLSENSOR_H__

#define __HALLSENSOR_H__


#include "Arduino.h"


class HallSensor

{

public:

HallSensor(int digitalPin, int analogPin = -1);


void setup();

int readDigital();

int readAnalog();


private:

int _digitalPin;

int _analogPin;

};


#endif //__HALLSENSOR_H__



HallSensor.ino


#include "HallSensor.h"

HallSensor::HallSensor(int digitalPin, int analogPin) :

_digitalPin(digitalPin), _analogPin(analogPin)

{

}


void HallSensor::setup()

{

if (_digitalPin != -1) {

pinMode(_digitalPin, INPUT);

}

if (_analogPin != -1) {

pinMode(_analogPin, INPUT);

}

}


int HallSensor::readDigital()

{

return (_digitalPin != -1) ? digitalRead(_digitalPin) : -1;

}


int HallSensor::readAnalog()

{

return (_analogPin != -1) ? analogRead(_analogPin) : -1;

}



The Client


Now that the Server is installed and working in the garage, I need a Client device that will query it periodically. Since I want to play a voice recording to inform my father when the door is up, I need a little digital MP3 decoder with a built-in 2W amplifier and a speaker. You can find these components on Amazon for a negligible amount: DFPlayer and Speaker.



Here’s the wiring diagram.




Several things bear mentioning about the MP3 decoder. The documentation for this device is a little weak. I started with the DFPlayer library. It didn’t work right away so I made some modifications to debug what was going on. I lost track of the edits that I made, so I’m including the DFPlayer code here. The DFPlayer device has built-in hardware UART serial ports which you need to use to communicate with the device. Note that you need to cross the ESP32 RX2 -> DFPlayer TX and ESP32TX2 -> DFPlayer RX. You also need pulldown resistors because the voltage coming from the ESP32 is too high and will damage the DFPlayer.


Even though the ESP32 is made by Espressif Systems, there are a number of implementers who create chip mounts with widely varying pinouts (WROOM, WROVER) and firmware versions. I had trouble with a particular ESP32 maker’s chip because the UARTs (hardware serial ports) used to transmit packets back and forth to the MP3 decoder were faulty. It took me several hours to debug this nonsense. The larger point is to examine hardware specs carefully.


In order to get the MP3 files on the DFPlayer,you need to connect it via a USB cable to your computer. The DFPlayer will mount like a flash drive. The filenames need to be specifically named 0001.mp3, 0002.mp3, 0003.mp3, etc. The filenames need to be 4 digits.


Another issue that you might run into is power problems. I originally wanted to supply power to the ESP32 via the built-in micro-USB connector, and then supply power from the ESP32 to the MP3 decoder via GND and 3V3; however, after you turn on Wifi on the ESP32, there just wasn’t enough power to drive both Wifi and the MP3 decoder. The Wifi and amplifier behaved erratically. Sometimes working, sometimes not at all. You can trim power consumption by lowering the clock speed of the ESP32 from 120MHz to 80MHz (or lower). Just remember that when you do this, you’re reducing the rate at which the ESP32 is able to process the code loop() function. You might need to find the right balance. I decided to power both the ESP32 and the MP3 decoder with a USB-A cable.


Here’s the code for the Client. It turns on Wifi and initializes the hardware serial ports on pins 16 and 17. Client attempts to connect to the MP3 decoder via the serial port, and attempts to reach the Server via TCP/IP on port 80; if it fails, it plays a “the server isn’t responding” voice alert. Otherwise, it deserializes the JSON data returned from Server and checks to see whether {“garageState” : “Up”}; if the garage door is up, it plays a “the garage door is open. Please shut it now” voice alert.


Client.ino

#include <Arduino.h>

#include <WiFi.h>

#include <ArduinoJson.h>

#include <HardwareSerial.h>

#include "DFPlayer.h"


HardwareSerial hardwareSerial(1);

DFPlayer mediaPlayer;


const char *ssid = "YourWifiSSID";

const char *password = "YourWifiPassword";


const char* host = "192.168.1.166";


#define ERR_GARAGE_DOOR_OPEN 1

#define ERR_CANNOT_CONNECT_INTERNET 2

#define ERR_SERVER_RETURNED_GARBAGE 3

#define ERR_SERVER_NOT_RESPONDING 4


#define LED_GREEN 32

#define LED_RED 33


void setup()

{

btStop(); //turn off bluetooth

// We start by connecting to a WiFi network

Serial.println();

Serial.print("Connecting to ");

Serial.println(ssid);


pinMode(LED_GREEN, OUTPUT);

pinMode(LED_RED, OUTPUT);


digitalWrite(LED_RED, LOW);

digitalWrite(LED_GREEN, HIGH);


WiFi.begin(ssid, password);


while (WiFi.status() != WL_CONNECTED) {

delay(500);

Serial.print(".");

}


Serial.println("");

Serial.println("WiFi connected");

Serial.println("IP address: ");

Serial.println(WiFi.localIP().toString());


hardwareSerial.begin(9600, SERIAL_8N1, 16/*RX*/, 17/*TX*/);

Serial.begin(115200);


Serial.println();

Serial.println("Initializing DFPlayer ... (May take 3~5 seconds)");

if (!mediaPlayer.begin(hardwareSerial)) {

digitalWrite(LED_RED, HIGH);

digitalWrite(LED_GREEN, LOW);

Serial.println(F("Unable to begin:"));

Serial.println(F("1.Please recheck the connection!"));

Serial.println(F("2.Please insert the SD card!"));

while(true){

delay(0); // Code to compatible with ESP8266 watch dog.

}

}


mediaPlayer.reset();

mediaPlayer.outputDevice(DFPLAYER_DEVICE_SD);

mediaPlayer.setTimeOut(500);

mediaPlayer.disableDAC();

mediaPlayer.EQ(DFPLAYER_EQ_ROCK);

mediaPlayer.volume(25);

Serial.println(" files found on SD card");

Serial.println(F("DFPlayer ready"));

Serial.println();

}


void loop()

{

delay(60000); //wait a minute

Serial.print("Connecting to ");

Serial.println(host);


// Use WiFiClient class to create TCP connections

WiFiClient client;

const int httpPort = 80;

if (!client.connect(host, httpPort)) {

mediaPlayer.playFolder(1, ERR_SERVER_NOT_RESPONDING);

Serial.println("Connection failed");

digitalWrite(LED_RED, HIGH);

digitalWrite(LED_GREEN, LOW);

return;

}


// We now create a URI for the request

String url = "/garage.json";


Serial.print("Requesting URL: ");

Serial.println(url);


// This will send the request to the server

client.print(String("GET ") + url + " HTTP/1.1\r\n" +

"Host: " + host + "\r\n" +

"Connection: close\r\n\r\n");

unsigned long timeout = millis();

while (client.available() == 0) {

if (millis() - timeout > 300000) {

mediaPlayer.playFolder(1, ERR_SERVER_NOT_RESPONDING);

Serial.println("Client timeout");

client.stop();

digitalWrite(LED_RED, HIGH);

digitalWrite(LED_GREEN, LOW);

return;

}

}


bool connectionOkB = false;

bool malformedDataB = false;

bool garageDoorUpB = false;

// Read all the lines of the reply from server

while(client.available())

{

String line = client.readStringUntil('\r');

Serial.print(line);


if (line.indexOf("HTTP/1.1 200 OK") >= 0)

{

connectionOkB = true;

}

else

if (connectionOkB && line.indexOf("{") >= 0)

{

DynamicJsonDocument doc(512);

deserializeJson(doc, line);


const char *gs = doc["garageState"];

String garageState = String(gs);

Serial.println("garageState = " + garageState);

if (garageState)

{

if (garageState.equals("Up"))

{

garageDoorUpB = true;

}

else

if (garageState.equals("Down"))

{

//cool

}

else

{

malformedDataB = true;

}

}

else

{

malformedDataB = true;

}

}

}


if (malformedDataB)

{

Serial.println("Server returned garbage");

mediaPlayer.playFolder(1, ERR_SERVER_RETURNED_GARBAGE);

}

else

if (garageDoorUpB)

{

Serial.println("Garage door open");

mediaPlayer.playFolder(1, ERR_GARAGE_DOOR_OPEN);

}


digitalWrite(LED_RED, (malformedDataB || garageDoorUpB) ? HIGH : LOW);

digitalWrite(LED_GREEN, (malformedDataB || garageDoorUpB) ? LOW : HIGH);

Serial.println("Closing connection");

client.stop();

}


DFPlayer.h


/*!

* @file DFRobotDFPlayerMini.h

* @brief DFPlayer - An Arduino Mini MP3 Player From DFRobot

* @n Header file for DFRobot's DFPlayer

*

* @copyright [DFRobot]( http://www.dfrobot.com ), 2016

* @copyright GNU Lesser General Public License

*

* @author [Angelo](Angelo.qiao@dfrobot.com)

* @version V1.0.3

* @date 2016-12-07

*/


#include "Arduino.h"


#ifndef DFRobotDFPlayerMini_cpp

#define DFRobotDFPlayerMini_cpp



#define DFPLAYER_EQ_NORMAL 0

#define DFPLAYER_EQ_POP 1

#define DFPLAYER_EQ_ROCK 2

#define DFPLAYER_EQ_JAZZ 3

#define DFPLAYER_EQ_CLASSIC 4

#define DFPLAYER_EQ_BASS 5


#define DFPLAYER_DEVICE_U_DISK 1

#define DFPLAYER_DEVICE_SD 2

#define DFPLAYER_DEVICE_AUX 3

#define DFPLAYER_DEVICE_SLEEP 4

#define DFPLAYER_DEVICE_FLASH 5


#define DFPLAYER_RECEIVED_LENGTH 10

#define DFPLAYER_SEND_LENGTH 10


//#define _DEBUG


#define TimeOut 0

#define WrongStack 1

#define DFPlayerCardInserted 2

#define DFPlayerCardRemoved 3

#define DFPlayerCardOnline 4

#define DFPlayerPlayFinished 5

#define DFPlayerError 6

#define DFPlayerUSBInserted 7

#define DFPlayerUSBRemoved 8

#define DFPlayerUSBOnline 9

#define DFPlayerCardUSBOnline 10

#define DFPlayerFeedBack 11


#define Busy 1

#define Sleeping 2

#define SerialWrongStack 3

#define CheckSumNotMatch 4

#define FileIndexOut 5

#define FileMismatch 6

#define Advertise 7


#define Stack_Header 0

#define Stack_Version 1

#define Stack_Length 2

#define Stack_Command 3

#define Stack_ACK 4

#define Stack_Parameter 5

#define Stack_CheckSum 7

#define Stack_End 9


class DFPlayer {

Stream* _serial;

unsigned long _timeOutTimer;

unsigned long _timeOutDuration = 500;

uint8_t _received[DFPLAYER_RECEIVED_LENGTH];

uint8_t _sending[DFPLAYER_SEND_LENGTH] = {0x7E, 0xFF, 06, 00, 01, 00, 00, 00, 00, 0xEF};

uint8_t _receivedIndex=0;


void sendStack();

void sendStack(uint8_t command);

void sendStack(uint8_t command, uint16_t argument);

void sendStack(uint8_t command, uint8_t argumentHigh, uint8_t argumentLow);

void enableACK();

void disableACK();

void uint16ToArray(uint16_t value,uint8_t *array);

uint16_t arrayToUint16(uint8_t *array);

uint16_t calculateCheckSum(uint8_t *buffer);

void parseStack();

bool validateStack();

uint8_t device = DFPLAYER_DEVICE_SD;

public:

uint8_t _handleType;

uint8_t _handleCommand;

uint16_t _handleParameter;

bool _isAvailable = false;

bool _isSending = false;

bool handleMessage(uint8_t type, uint16_t parameter = 0);

bool handleError(uint8_t type, uint16_t parameter = 0);

uint8_t readCommand();

bool begin(Stream& stream, bool isACK = true, bool doReset = true);

bool waitAvailable(unsigned long duration = 0);

bool available();

uint8_t readType();

uint16_t read();

void setTimeOut(unsigned long timeOutDuration);

void next();

void previous();

void play(int fileNumber=1);

void volumeUp();

void volumeDown();

void volume(uint8_t volume);

void EQ(uint8_t eq);

void loop(int fileNumber);

void outputDevice(uint8_t device);

void sleep();

void reset();

void start();

void pause();

void playFolder(uint8_t folderNumber, uint8_t fileNumber);

void outputSetting(bool enable, uint8_t gain);

void enableLoopAll();

void disableLoopAll();

void playMp3Folder(int fileNumber);

void advertise(int fileNumber);

void playLargeFolder(uint8_t folderNumber, uint16_t fileNumber);

void stopAdvertise();

void stop();

void loopFolder(int folderNumber);

void randomAll();

void enableLoop();

void disableLoop();

void enableDAC();

void disableDAC();

int readState();

int readVolume();

int readEQ();

int readFileCounts(uint8_t device);

int readCurrentFileNumber(uint8_t device);

int readFileCountsInFolder(int folderNumber);

int readFileCounts();

int readFolderCounts();

int readCurrentFileNumber();

};




DFPlayer.ino


/*!

* @file DFPlayer.cpp

* @brief DFPlayer - An Arduino Mini MP3 Player From DFRobot

* @n Header file for DFRobot's DFPlayer

*

* @copyright [DFRobot]( http://www.dfrobot.com ), 2016

* @copyright GNU Lesser General Public License

*

* @author [Angelo](Angelo.qiao@dfrobot.com)

* @version V1.0.3

* @date 2016-12-07

*/


#include "DFPlayer.h"


void DFPlayer::setTimeOut(unsigned long timeOutDuration){

_timeOutDuration = timeOutDuration;

}


void DFPlayer::uint16ToArray(uint16_t value, uint8_t *array){

*array = (uint8_t)(value>>8);

*(array+1) = (uint8_t)(value);

}


uint16_t DFPlayer::calculateCheckSum(uint8_t *buffer){

uint16_t sum = 0;

for (int i=Stack_Version; i<Stack_CheckSum; i++) {

sum += buffer[i];

}

return -sum;

}


void DFPlayer::sendStack(){

if (_sending[Stack_ACK]) { //if the ack mode is on wait until the last transmition

while (_isSending) {

delay(0);

available();

}

}


#ifdef _DEBUG

Serial.println();

Serial.print(F("sending:"));

for (int i=0; i<DFPLAYER_SEND_LENGTH; i++) {

Serial.print(_sending[i],HEX);

Serial.print(F(" "));

}

Serial.println();

_serial->write(_sending, DFPLAYER_SEND_LENGTH);

_timeOutTimer = millis();

_isSending = _sending[Stack_ACK];

if (!_sending[Stack_ACK]) { //if the ack mode is off wait 10 ms after one transmition.

delay(10);

}

}


void DFPlayer::sendStack(uint8_t command){

sendStack(command, 0);

}


void DFPlayer::sendStack(uint8_t command, uint16_t argument){

_sending[Stack_Command] = command;

uint16ToArray(argument, _sending+Stack_Parameter);

uint16ToArray(calculateCheckSum(_sending), _sending+Stack_CheckSum);

sendStack();

}


void DFPlayer::sendStack(uint8_t command, uint8_t argumentHigh, uint8_t argumentLow){

uint16_t buffer = argumentHigh;

buffer <<= 8;

sendStack(command, buffer | argumentLow);

}


void DFPlayer::enableACK(){

_sending[Stack_ACK] = 0x01;

}


void DFPlayer::disableACK(){

_sending[Stack_ACK] = 0x00;

}


bool DFPlayer::waitAvailable(unsigned long duration){

unsigned long timer = millis();

if (!duration) {

duration = _timeOutDuration;

}

while (!available()){

if (millis() - timer > duration) {

return false;

}

delay(0);

}

return true;

}


bool DFPlayer::begin(Stream &stream, bool isACK, bool doReset){

_serial = &stream;

if (isACK) {

enableACK();

}

else{

disableACK();

}

if (doReset) {

reset();

waitAvailable(2000);

delay(200);

}

else {

// assume same state as with reset(): online

_handleType = DFPlayerCardOnline;

}


return (readType() == DFPlayerCardOnline) || (readType() == DFPlayerUSBOnline) || !isACK;

}


uint8_t DFPlayer::readType(){

_isAvailable = false;

return _handleType;

}


uint16_t DFPlayer::read(){

_isAvailable = false;

return _handleParameter;

}


bool DFPlayer::handleMessage(uint8_t type, uint16_t parameter){

_receivedIndex = 0;

_handleType = type;

_handleParameter = parameter;

_isAvailable = true;

return _isAvailable;

}


bool DFPlayer::handleError(uint8_t type, uint16_t parameter){

handleMessage(type, parameter);

_isSending = false;

return false;

}


uint8_t DFPlayer::readCommand(){

_isAvailable = false;

return _handleCommand;

}


void DFPlayer::parseStack(){

uint8_t handleCommand = *(_received + Stack_Command);

if (handleCommand == 0x41) { //handle the 0x41 ack feedback as a spcecial case, in case the pollusion of _handleCommand, _handleParameter, and _handleType.

_isSending = false;

return;

}

_handleCommand = handleCommand;

_handleParameter = arrayToUint16(_received + Stack_Parameter);


switch (_handleCommand) {

case 0x3D:

handleMessage(DFPlayerPlayFinished, _handleParameter);

break;

case 0x3F:

if (_handleParameter & 0x01) {

handleMessage(DFPlayerUSBOnline, _handleParameter);

}

else if (_handleParameter & 0x02) {

handleMessage(DFPlayerCardOnline, _handleParameter);

}

else if (_handleParameter & 0x03) {

handleMessage(DFPlayerCardUSBOnline, _handleParameter);

}

break;

case 0x3A:

if (_handleParameter & 0x01) {

handleMessage(DFPlayerUSBInserted, _handleParameter);

}

else if (_handleParameter & 0x02) {

handleMessage(DFPlayerCardInserted, _handleParameter);

}

break;

case 0x3B:

if (_handleParameter & 0x01) {

handleMessage(DFPlayerUSBRemoved, _handleParameter);

}

else if (_handleParameter & 0x02) {

handleMessage(DFPlayerCardRemoved, _handleParameter);

}

break;

case 0x40:

handleMessage(DFPlayerError, _handleParameter);

break;

case 0x3C:

case 0x3E:

case 0x42:

case 0x43:

case 0x44:

case 0x45:

case 0x46:

case 0x47:

case 0x48:

case 0x49:

case 0x4B:

case 0x4C:

case 0x4D:

case 0x4E:

case 0x4F:

handleMessage(DFPlayerFeedBack, _handleParameter);

break;

default:

handleError(WrongStack);

break;

}

}


uint16_t DFPlayer::arrayToUint16(uint8_t *array){

uint16_t value = *array;

value <<=8;

value += *(array+1);

return value;

}


bool DFPlayer::validateStack(){

return calculateCheckSum(_received) == arrayToUint16(_received+Stack_CheckSum);

}


bool DFPlayer::available(){

while (_serial->available()) {

delay(0);

if (_receivedIndex == 0) {

_received[Stack_Header] = _serial->read();

#ifdef _DEBUG

Serial.print(F("received:"));

Serial.print(_received[_receivedIndex],HEX);

Serial.print(F(" "));

if (_received[Stack_Header] == 0x7E) {

_receivedIndex ++;

}

}

else{

_received[_receivedIndex] = _serial->read();

#ifdef _DEBUG

Serial.print(_received[_receivedIndex],HEX);

Serial.print(F(" "));

switch (_receivedIndex) {

case Stack_Version:

if (_received[_receivedIndex] != 0xFF) {

return handleError(WrongStack);

}

break;

case Stack_Length:

if (_received[_receivedIndex] != 0x06) {

return handleError(WrongStack);

}

break;

case Stack_End:

#ifdef _DEBUG

Serial.println();

if (_received[_receivedIndex] != 0xEF) {

return handleError(WrongStack);

}

else{

if (validateStack()) {

_receivedIndex = 0;

parseStack();

return _isAvailable;

}

else{

return handleError(WrongStack);

}

}

break;

default:

break;

}

_receivedIndex++;

}

}

if (_isSending && (millis()-_timeOutTimer>=_timeOutDuration)) {

return handleError(TimeOut);

}

return _isAvailable;

}


void DFPlayer::next(){

sendStack(0x01);

}


void DFPlayer::previous(){

sendStack(0x02);

}


void DFPlayer::play(int fileNumber){

sendStack(0x03, fileNumber);

}


void DFPlayer::volumeUp(){

sendStack(0x04);

}


void DFPlayer::volumeDown(){

sendStack(0x05);

}


void DFPlayer::volume(uint8_t volume){

sendStack(0x06, volume);

}


void DFPlayer::EQ(uint8_t eq) {

sendStack(0x07, eq);

}


void DFPlayer::loop(int fileNumber) {

sendStack(0x08, fileNumber);

}


void DFPlayer::outputDevice(uint8_t device) {

sendStack(0x09, device);

delay(200);

}


void DFPlayer::sleep(){

sendStack(0x0A);

}


void DFPlayer::reset(){

sendStack(0x0C);

}


void DFPlayer::start(){

sendStack(0x0D);

}


void DFPlayer::pause(){

sendStack(0x0E);

}


void DFPlayer::playFolder(uint8_t folderNumber, uint8_t fileNumber){

sendStack(0x0F, folderNumber, fileNumber);

}


void DFPlayer::outputSetting(bool enable, uint8_t gain){

sendStack(0x10, enable, gain);

}


void DFPlayer::enableLoopAll(){

sendStack(0x11, 0x01);

}


void DFPlayer::disableLoopAll(){

sendStack(0x11, 0x00);

}


void DFPlayer::playMp3Folder(int fileNumber){

sendStack(0x12, fileNumber);

}


void DFPlayer::advertise(int fileNumber){

sendStack(0x13, fileNumber);

}


void DFPlayer::playLargeFolder(uint8_t folderNumber, uint16_t fileNumber){

sendStack(0x14, (((uint16_t)folderNumber) << 12) | fileNumber);

}


void DFPlayer::stopAdvertise(){

sendStack(0x15);

}


void DFPlayer::stop(){

sendStack(0x16);

}


void DFPlayer::loopFolder(int folderNumber){

sendStack(0x17, folderNumber);

}


void DFPlayer::randomAll(){

sendStack(0x18);

}


void DFPlayer::enableLoop(){

sendStack(0x19, 0x00);

}


void DFPlayer::disableLoop(){

sendStack(0x19, 0x01);

}


void DFPlayer::enableDAC(){

sendStack(0x1A, 0x00);

}


void DFPlayer::disableDAC(){

sendStack(0x1A, 0x01);

}


int DFPlayer::readState(){

sendStack(0x42);

if (waitAvailable()) {

if (readType() == DFPlayerFeedBack) {

return read();

}

else{

return -1;

}

}

else{

return -1;

}

}


int DFPlayer::readVolume(){

sendStack(0x43);

if (waitAvailable()) {

return read();

}

else{

return -1;

}

}


int DFPlayer::readEQ(){

sendStack(0x44);

if (waitAvailable()) {

if (readType() == DFPlayerFeedBack) {

return read();

}

else{

return -1;

}

}

else{

return -1;

}

}


int DFPlayer::readFileCounts(uint8_t device){

switch (device) {

case DFPLAYER_DEVICE_U_DISK:

sendStack(0x47);

break;

case DFPLAYER_DEVICE_SD:

sendStack(0x48);

break;

case DFPLAYER_DEVICE_FLASH:

sendStack(0x49);

break;

default:

break;

}

if (waitAvailable()) {

if (readType() == DFPlayerFeedBack) {

return read();

}

else{

return -1;

}

}

else{

return -1;

}

}


int DFPlayer::readCurrentFileNumber(uint8_t device){

switch (device) {

case DFPLAYER_DEVICE_U_DISK:

sendStack(0x4B);

break;

case DFPLAYER_DEVICE_SD:

sendStack(0x4C);

break;

case DFPLAYER_DEVICE_FLASH:

sendStack(0x4D);

break;

default:

break;

}

if (waitAvailable()) {

if (readType() == DFPlayerFeedBack) {

return read();

}

else{

return -1;

}

}

else{

return -1;

}

}


int DFPlayer::readFileCountsInFolder(int folderNumber){

sendStack(0x4E, folderNumber);

if (waitAvailable()) {

if (readType() == DFPlayerFeedBack) {

return read();

}

else{

return -1;

}

}

else{

return -1;

}

}


int DFPlayer::readFolderCounts(){

sendStack(0x4F);

if (waitAvailable()) {

if (readType() == DFPlayerFeedBack) {

return read();

}

else{

return -1;

}

}

else{

return -1;

}

}


int DFPlayer::readFileCounts(){

return readFileCounts(DFPLAYER_DEVICE_SD);

}


int DFPlayer::readCurrentFileNumber(){

return readCurrentFileNumber(DFPLAYER_DEVICE_SD);

}


Improvements


This project uses a client-server architecture that could be adapted for a wide variety of uses. It would be easy to add a capability to close the garage door remotely. All that you need to do is connect the ESP32 to your garage door switch (either directly or with a relay), and then figure out some way to acuate it. Maybe, a button. There are certainly other ways to do this but, if you’ve followed along so far, you’ve probably learned a bit about how versatile micro-controllers can be. If you’re interested in driving animatronics, it would be very easy to incorporate motion sensors, servos, and/or motors.







287 views0 comments
bottom of page