Controlling Air Conditioner temperature automatically with ESP32 after reading real-time weather dat
P.S: This tutorial is aimed at advanced and intermediate microcontroller users. People with limited experience with sensors or microcontrollers are encouraged to attempt the project since the project is quite elaborative. However, they will need to research unfamiliar topics on their own.
What you will make
This tutorial teaches you how to fetch the real-time weather data with a microcontroller and how to mimic the air conditioner remote control that uses IR (Infrared) technology to control the AC using an IR transmitter.
What you will learn
How to fetch real-time weather data using ESP32
How to use OpenWeatherMap API
How HTTP requests work for fetching json objects
How to use IR receiver sensor to decode and store the signal code/raw data of the remote for later use.
How to transmit the stored signal code/ raw data using IR transmitter and the use the fetched weather data to control the AC.
Things you will need to complete this project:
Air conditioner and IR remote for the AC.
(I will be using an AC for this tutorial. You can use any IR remote controlled device for learning purpose.)
FireBeetle ESP32 IoT Microcontroller (any other microcontroller can be used too)
Gravity: Digital IR Receiver Module
Gravity: Digital IR Transmitter Module
Jumper Cables (If you donât want to use jumper cables, you can choose FireBeetle Covers-Gravity I/O Expansion Shield)
Getting the HexCodes of the AC remote control using an IR receiver
The air conditioner is normally controlled by an IR remote control that sends or transmits infrared signals in the form of hexCode or raw data when a certain button in pressed. An important thing to remember that each button has its unique signal code. Then the receiver in the AC interprets the signal codes and converts them into instructions to perform certain tasks such an turning on/off, increasing/reducing temperature, changing modes etc. In our project we will try to mimic the IR remote control and transmit the signals to the receiver already installed in the AC. To do that, firstly, we need to decode the signals from the AC remote.
Connection Diagram
Connect the IR receiver to your ESP32 pins as shown below.
Head over to Arduino IDE, open a new file and save it before moving on. We adopt the code from IRremoteESP8266 libraryâs example code for receiving data with IR receiver. We start the code by importing some necessary libraries. If you donât have the libraries install and download them from Arduino library manager.
#include <Arduino.h>
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRac.h>
#include <IRtext.h>
#include <IRutils.h>
Then, we define Pin number, Baudrate and buffersize of the codes that are going to be captured by the IR receiver. Status messages will be sent to the PC at the baud rate of 115200. (We recommend 115200 or faster baudrate.)
As this program is a special purpose capture/decoder meaning AC hexCodes and raw data are usually longer than other devices since it controls many things at the same time such as fan speed, blade direction, mode, times etc. So, let us use a larger than normal buffer so we can handle Air Conditioner remote codes.
// An IR Receiver is connected to GPIO pin D4
// e.g. D4 on an ESP32 board.
const uint16_t RecvPin = D4;
const uint32_t BaudRate = 115200;
const uint16_t CaptureBufferSize = 1024;
Timeout is the number of milli-Seconds of no-more-data before we consider a message ends. This parameter is an interesting trade-off. The longer the timeout, the more complex a message it can capture. e.g. Some device protocols will send multiple message packets in quick succession, like Air Conditioner remotes. Air Conditioner protocols often have a considerable gap (20-40+ms) between packets. The downside of a large timeout value is a lot of less complex protocols (we will talk more about it) send multiple messages when the remote's button is held down. The gap between them is often also around 20+ms. This can result in the raw data be 2-3+ times larger than needed as it has captured 2-3+ messages in a single capture. Setting a low timeout value can resolve this. So, choosing the best Timeout value for your use particular case is quite nuanced. Good luck and happy hunting.
NOTE: Don't exceed MaxTimeoutMs. Typically 130ms.
#if DECODE_AC
// Some A/C units have gaps in their protocols of ~40ms. e.g. Kelvinator
// A value this large may swallow repeats of some protocols
const uint8_t Timeout = 50;
#else // DECODE_AC
// Suits most messages, while not swallowing many repeats.
const uint8_t Timeout = 15;
#endif // DECODE_AC
Alternatives:
const uint8_t Timeout = 90;
Suits messages with big gaps like XMP-1 & some aircon units, but can accidentally swallow repeated messages in the rawData[] output.
const uint8_t Timeout = MaxTimeoutMs;
This will set it to our currently allowed maximum. Values this high are problematic because itâs roughly the typical boundary where most messages repeat. e.g. It will stop decoding a message and start sending it to serial at precisely the time when the next message is likely to be transmitted and may miss it.
Next, we Set the smallest sized "UNKNOWN" message packets we only care about. This value helps reduce the false-positive detection rate of IR background noise as real messages. The chances of background IR noise getting detected as a message increases with the length of the Timeout value. The downside of setting this message too large is, you can miss some valid short messages for protocols that this library doesn't yet decode. Set higher if you get lots of random short UNKNOWN messages when nothing should be sending a message. Set lower if you are sure your setup is working, but it doesn't see messages from your device. (e.g. Other IR remotes work.)
NOTE: Set this value very high to effectively turn off UNKNOWN detection.
const uint16_t MinUnknownSize = 12;
The legacy time info is no longer supported, change to true if you miss or need the old raw timing display.
#define LEGACY_TIMING_INFO false
We define irrecv with features it will capture and results to store the captured data somewhere.
// Use turn on the save buffer feature for more complete capture coverage.
IRrecv irrecv(RecvPin, CaptureBufferSize, Timeout, true);
decode_results results; // Somewhere to store the results
Next, we have our setup function. Here We need to include SERIAL_TX_ONLY since we are receiving signals.
/ This section of code runs only once at start-up.
void setup() {
#if defined(ESP8266)
Serial.begin(BaudRate, SERIAL_8N1, SERIAL_TX_ONLY);
#else // ESP8266
Serial.begin(BaudRate, SERIAL_8N1);
#endif // ESP8266
while (!Serial) // Wait for the serial connection to be established.
delay(50);
Serial.printf("\n" D_STR_IRRECVDUMP_STARTUP "\n", RecvPin);
#if DECODE_HASH
// Ignore messages with less than minimum on or off pulses.
irrecv.setUnknownThreshold(MinUnknownSize);
#endif // DECODE_HASH
irrecv.enableIRIn(); // Start the receiver
}
As the comment says, this function will only run once at the beginning of the program. Once the serial connection is established the receiver is enabled.
Finally, the loop function that repeats each time we press a button.
void loop() {
// Check if the IR code has been received.
if (irrecv.decode(&results))
// Display a crude timestamp.
uint32_t now = millis();
Serial.printf(D_STR_TIMESTAMP " : %06u.%03u\n", now / 1000, now % 1000);
// Check if we got an IR message that was to big for our capture buffer.
if (results.overflow)
Serial.printf(D_WARN_BUFFERFULL "\n", CaptureBufferSize);
// Display the library version the message was captured with.
Serial.println(D_STR_LIBRARY " : v" _IRREMOTEESP8266_VERSION_ "\n");
// Display the basic output of what we found.
Serial.print(resultToHumanReadableBasic(&results));
// Display any extra A/C info if we have it.
String description = IRAcUtils::resultAcToString(&results);
if (description.length()) Serial.println(D_STR_MESGDESC ": " + description);
yield(); // Feed the WDT as the text output can take a while to print.
#if LEGACY_TIMING_INFO
// Output legacy RAW timing info of the result.
Serial.println(resultToTimingInfo(&results));
yield(); // Feed the WDT (again)
#endif // LEGACY_TIMING_INFO
// Output the results as source code
Serial.println(resultToSourceCode(&results));
Serial.println(); // Blank line between entries
yield(); // Feed the WDT (again)
}
}
The function checks whether a code has been received then shows a timestamp, then it checks the buffersize and the library version it captured. In the next few lines, the results are displayed and any extra info about the AC such as whether its protocol is known or unknown or the raw timing of the results are printed. Now, Verify and upload your program to your ESP32 board. And start the serial monitor to see the received data. If everything goes well, you should see something like the figure below each time you press a button pointing at the IR receiver.
In the above figure we can see that, the protocol is unknown, and the code generated is quite long (50 bits) and the raw data is of array size 99, also quite large. In this case, the IRremoteESP8266.h library does not have any decoded protocol written for this particular brand of AC. Usually, the library includes protocols for well-known/bigger brands such as LG, GREE etc. Hence, the IR receiver prints raw data for my AC remote control. If you happen to get raw data just like this, save the whole array of the raw data for each button you pressed in a text file and note the button functionality for later use.
If you happen to use any well-known brands you might as well get the hexCodes with a protocol name such as the figure below. In this case, just save the hexCode (unint64_t data) for each button you pressed along with the buttonsâ functions.
Sending the HexCodes using IR transmitter to control the AC
Now that we have the hexcodes, we can send them using an IR transmitter to control the AC.
Connection Diagram
Connect the IR transmitter to ESP32 pins as shown below.
Letâs have a look at the code. We adopt the code from IRremoteESP8266 libraryâs example code for sending data with IR Transmitter First, letâs import the necessary libraries,
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
We set the PinNumber and use this in IRsend function
const uint16_t IrPin = D4; // ESP32 GPIO pin to use. Recommended: (D4).
IRsend irsend(IrPin); // Set the GPIO to be used to sending the message.
Now, if the data you received was raw data meaning your AC remote doesnât have a protocol, do as following.
uint16_t powerOn[] = {9058, 4438, 512, 1728, 512, 1726, 512, 610, 510, 608, 510, 610,510, 608, 510, 608, 510, 1728, 510, 610, 510, 1728, 510, 1728, 510, 610, 510, 608,510, 610, 510, 610, 510, 608, 510, 610, 510, 608, 510, 1728, 512, 608, 510, 610, 510, 608, 510, 610, 510, 610, 510, 610, 510, 1730, 510, 610, 510, 610, 510, 608, 510, 610, 608, 510, 608, 510, 610, 510, 608, 510, 608, 510, 608, 510, 608, 510, 608, 510, 608,510, 610, 510, 608, 510, 608, 510, 610, 510, 608, 510, 608, 510, 608, 510, 608, 510, 610, 510};
uint16_t ModeCool[] = {9050, 4356, 510, 610,510, 608, 510, 608, 510, 1728, 510, 610, 510, 1728, 510, 1728, 510, 610, 510, 608,510, 610, 510, 610, 510, 608, 510, 610, 510, 608, 510, 1728, 512, 608, 510, 610, 510, 608, 510, 610, 510, 610, 510, 610, 510, 1730, 510, 610, 510, 610, 510, 608, 510, 610, , 608, 510, 608, 510, 610, 510, 608, 510, 608, 510, 608, 510, 608, 510, 608, 510, 608,510, 610, 510, 608, 510, 608, 510, 610, 510, 608, 510, 608, 510, 608, 510, 608, 510, 610, 510, 608, 510, 610, 510, 610, 610, 510, 608};
We define variables of arrays for the sets of raw data associated with each button that we wish to transmit over through the IR transmitter.
If you happened to get hexCodes with protocol for your AC remote, do as following,
uint8_t samsungState[kSamsungAcStateLength] = {
0x02, 0x92, 0x0F, 0x00, 0x00, 0x00, 0xF0,
0x01, 0xE2, 0xFE, 0x71, 0x40, 0x11, 0xF0};
Here is an example using Samsung remote control. The hexCodes are saved in an array. You can use the whole array or only some its indexes to send over through the transmitter.
We then define our setup function where the braudrate is initialized to communicate with the receiver in AC.
void setup() {
irsend.begin()
#if ESP8266
Serial.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY);
#else // ESP8266
Serial.begin(115200, SERIAL_8N1);
#endif // ESP8266
}
Similarly as with Receiving, if you are using the RX pin to drive the IR LED/transmitter, make sure to add the following line in the setup() function. Serial.begin(115200,SERIAL_8N1,SERIAL_TX_ONLY); That should limit in-bound serial communications from interfering on the ESP8266.
void loop() {
Serial.println("a powerOn capture from IRrecv");
irsend.sendRaw(powerOn, sizeof(powerOn) / sizeof(powerOn[0]), 38); // Send a raw data captured at 38kHz.
irsend.sendRaw(ModeCool, sizeof(ModeCool) / sizeof(ModeCool[0]), 38);
delay(2000);
Serial.println("a Samsung A/C state from IRrecv");
irsend.sendSamsungAC(samsungState);
delay(2000);}
Next, in the loop function, irsend.sendRaw sends the raw data/hexCode to the receiver. If everything goes well, after pointing the IR transmitter towards the AC, it should power on and perform whatever signals you are sending.
Note: Remember, you must send the signal to power on the AC first, only then you send can other signals for the AC to perform such as controlling the temperate, weather modes etc.
Fetching the real-time weather data
We can already control the AC temperature with IR transmitter, we just need one more thing, we need to get the weather data from online. To do this, we will be going to use an API called âOpenWeatherMapâ.
Configuring OpenWeatherMap
You will need to register on Open Weather Map website to receive an API key. OpenWeatherMap.org has an amazing API for individuals that is both free and simple. Just select a service, generate an API key, and send requests! Then, use that data to create displays, change the state of a machine, or even control other devices.
Upon entering the site, first create a new account.
After signing in, navigate to the API page at the top to view their wide variety of weather APIs. Click the âSubscribeâ button under the âCurrent Weather Dataâ section.
This is the API that is available for free. At this tier, users are limited to 1 request per second, which should be plenty for a single project. The free tier also has a 5-day/3-hour forecast option and weather alerts. Select the âGet API keyâ button to go to the next page.
When you register, an email is sent to your e-mail address that contains a key. Make sure to allow a few hours for it to become active before using it. Once you have your key, keep it safe and don't share it online as it is used to identify you when using the service.
Now you can start testing the API and figure how to get the information you need from the Open Weather Map API service. (for full documentation, click here)
To test whether your API key is working, open your browser and navigate to: http://api.openweathermap.org/data/2.5/forecast?q=torino,IT&cnt=3&appid=xxxxxxxxxxxxxxxxxxxxxxxxxx (replace q with your city, country id and appid with API key)
You should now see the json data containing weather information.
Letâs get the data on ESP32 now. This code is based on example provided on https://randomnerdtutorials.com/esp32-http-get-open-weather-map-thingspeak-arduino/
First, we will need to include three more libraries, and your imports should look like this,
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Arduino_JSON.h>
Our three new imports are Wifi, HTTPclient and Arduino_Json. We will use Wifi.h to connect ESP32 with wifi network and HTTPClient.h to make HTTP requests for fetching the weather data from the API server and Arduino_json library to parse the json data.
Go ahead and define variables for your wifi and itâs password.
const char* ssid = "*************"; // Replace with your wifi
const char* password = "***********"; // Replace with your wifi password
Few more variables to store our API key, city and country code.
String openWeatherMapApiKey = "*******************";//Replace with your API code
// Replace with your country code and city
String city = "Shanghai";
String countryCode = "CN";
We define a test timer which is usually set to 10 seconds for testing purpose, then add a delay and a buffer for our json object. For a final application, check the API call limits per hour/minute to avoid getting blocked/banned, and create a string to store the json buffer as well.
unsigned long lastTime = 0;
unsigned long timerDelay = 10000;
String jsonBuffer;
Letâs look at our setup function,
void setup() {
WiFi.begin(ssid, password);
Serial.println("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
Serial.println("Timer set to 10 seconds (timerDelay variable), it will take 10 seconds before publishing the first reading.");
#if ESP8266
Serial.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY);
#else // ESP8266
Serial.begin(115200, SERIAL_8N1);
#endif // ESP8266
The wifi attempts to connect, If the wifi connection is successful it prints out the local IP of the your internet and The serial communication starts. Now, letâs go through the most important function,
uint16_t fetch_weather()
{
if ((millis() - lastTime) > timerDelay) {
// Check WiFi connection status
if(WiFi.status()== WL_CONNECTED){
String serverPath = "http://api.openweathermap.org/data/2.5/weather?units=metric&q=" + city + "," + countryCode + "&APPID=" + openWeatherMapApiKey;
jsonBuffer = httpGETRequest(serverPath.c_str());
Serial.println(jsonBuffer);
JSONVar myObject = JSON.parse(jsonBuffer);
// JSON.typeof(jsonVar) can be used to get the type of the var
if (JSON.typeof(myObject) == "undefined") {
Serial.println("Parsing input failed!");
//return;
}
Serial.print("JSON object = ");
Serial.println(myObject);
Serial.print("Temperature: ");
Serial.println(myObject["main"]["temp"]);
Serial.print("Pressure: ");
Serial.println(myObject["main"]["pressure"]);
Serial.print("Humidity: ");
Serial.println(myObject["main"]["humidity"]);
Serial.print("Wind Speed: ");
Serial.println(myObject["wind"]["speed"]);
if(int(myObject["main"]["temp"]) >= 30)
{
Serial.print("so fridggging hot!\n");
return (int(myObject["main"]["temp"]));
}
if(int(myObject["main"]["temp"]) < 30)
{
Serial.print("so hot!!\n");
}
}
else {
Serial.println("WiFi Disconnected");
}
lastTime = millis(); }}
Our fetch_weather function will fetch the weather data with HTTP requests (more explanation on it in in a bit) and control the AC temperature by comparing with the temperature. We check the wifi status, if there is a successful wifi connection, then we send a request to the API server of OpenWeatherMap by the httpGETRequest() function and it retrieves a string with a JSON object that contains all the information about the weather for your city and save the information in the jsonBuffer. Here we use the list property of Json to access the query result. The serial monitor outputs the temperature, pressure, humidity and wind speed info (there are many more features listed in the API doc). Finally, we use some simple logics to compare the temperature print it out on the screen.
void loop() {
if (fetch_weather()) {
Serial.println("a rawData capture from IRrecvDumpV2");
irsend.sendRaw(powerOn, sizeof(powerOn) / sizeof(powerOn[0]), 38); // Send a raw data capture at 38kHz.
irsend.sendRaw(ModeCool, sizeof(ModeCool) / sizeof(ModeCool[0]), 38);
//Serial.println("a Samsung A/C state from IRrecvDumpV2");
//irsend.sendSamsungAC(samsungState);
//delay(2000);
Serial.println("it's working!!!!");
delay(10000);}}
On to the loop function, we call the fetch_weather function and send the hexCodes with the IRsend libraryâs sendRaw function since our remote generated raw data. Here, the parameters are signal data, size of the array of the signal which is automatically calculated here, and the frequency at which the signal data was captured. Depending on what your AC remote generated, you can also send signal data with protocol, example is shown in comments. If everything goes well, you should be seeing the serial monitor printing out decoded JSON objects and your AC should turn on itself (and perform whatever action buttonsâ signal code you transmitted) once you point the IR transmitter towards it.
Now we will see, how the httpGETRequest() function makes the call to the API server to retrieve the query object.
String httpGETRequest(const char* serverName) {
HTTPClient http;
// Your IP address with path or Domain name with URL path
http.begin(serverName);
// Send HTTP POST request
int httpResponseCode = http.GET();
String payload = "{}";
if (httpResponseCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
payload = http.getString();}
else {
Serial.print("Error code: ");
Serial.println(httpResponseC}
// Free resources
http.end();
The httpGETRequest() function sends an http request to API server and attempts to connect. If the httpResponseCode is larger than 0 (look at the figure above, itâs 200) then it prints out the responsecode and returns the payload which in turn later gets saved in to the jsonbuffer inside fetch_weather() function.
This tutorial only shows the overall steps to control the AC with an IR transmitter after fetching the real-time weather data with a microcontroller. You can use any microcontroller to achieve this. The API offers a lot more options. Explore them on your own. Happy Exploring!
You can find the code for this project in the zipfile attached at the end of the post.