Controlling Air Conditioner temperature automatically with ESP32 after reading real-time weather dat

7 55246 Hard
projectImage

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.

projectImage

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.

CODE
#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.

CODE
// 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.

CODE
#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.

CODE
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.

CODE
#define LEGACY_TIMING_INFO false

We define irrecv with features it will capture and results to store the captured data somewhere.

CODE
// 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.

CODE
/ 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.

CODE
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.

projectImage

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.

projectImage

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.

projectImage

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,

CODE
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>

We set the PinNumber and use this in IRsend function

CODE
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.

CODE
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,

CODE
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.

CODE
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.

CODE
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.

projectImage
projectImage

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.

projectImage

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.

projectImage

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.

projectImage

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,

CODE
#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.

CODE
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.

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.

CODE
unsigned long lastTime = 0;
unsigned long timerDelay = 10000;
String jsonBuffer;

Let’s look at our setup function,

CODE
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,

CODE
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.

CODE
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.

projectImage

Now we will see, how the httpGETRequest() function makes the call to the API server to retrieve the query object.

CODE
  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.

icon code_ctrl_AC_Weather_ESP32+IR.zip 7KB Download(219)
License
All Rights
Reserved
licensBg
7