Banner
Arduino,  Hardware

Making my air conditioner smarter – Part 2

<< Continuation of Part 1

I come back from the shadows to continue my “smart air conditioner” 😄

In the previous post, we got working a small test sketch that let us control our air conditioner with an arduino mega. Now it is time to port that sketch to an ESP-01 to make use of its WiFi connection and compact size, which is not much inside the air conditioner unit.

As in my smart washing machine (that keeps working a year and a half later 😊), the idea here is to use MQTT for the communication between my small IoT server which has OpenHAB installed. For this MQTT communication, we will need the PubSubClient library.

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <PubSubClient.h>

.
.
.

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    // Attempt to connect
    if (client.connect(mqttClientId, mqttUser, mqttPassword)) {
      // Once connected, resubscribe
      client.subscribe(mqttPowerCommandTopic);
      client.subscribe(mqttModeCommandTopic);
      client.subscribe(mqttTempCommandTopic);
      client.subscribe(mqttSpeedCommandTopic);
    } else {
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

For managing the WiFi connections, I found a library that lets us configure a new WiFi connection, in case we change our WiFi password, without having to reprogram our board, which is great. It is called WiFiManager. In this case, we choose to open a protected WiFi connection in case of not being able to connect to the saved WiFi connection, and retry after 3 minutes (180 seconds).

#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>

.
.
.

void setup_wifi() {
  WiFiManager wifiManager;
  wifiManager.setTimeout(180); //3 minutes

  if(!wifiManager.autoConnect(wifiSsid, wifiPassword)) {
    //Retry after 3 minutes with no WiFi connection
    ESP.reset();
    delay(5000);
  }
}

For sending the IR commands, as in the test sketch of the previous post, we will need the IRRemote library. In this case, the version for the ESP8266, IRremoteESP8266.

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

Once included the needed libraries, the rest of the implemented logic should be compatible, so no major change is really necessary, apart of encapsulating the logic so we can call it more neatly using subroutines in an easy way. In this case, I moved all that logic to an .h file, to leave the .ino file with the connection subroutines, topic management, etc.

#include "Configuration.h"
#include "Commands.h"

We will use in this case two MQTT topics for every function we will control (a topic for receiving commands from the MQTT server, and another one for notifying back the states to the server).

const char* mqttPowerStateTopic   = "Home/FF_MasterBedroom/AirConditioner/PowerS";
const char* mqttPowerCommandTopic = "Home/FF_MasterBedroom/AirConditioner/PowerC";
const char* mqttModeStateTopic    = "Home/FF_MasterBedroom/AirConditioner/ModeS";
const char* mqttModeCommandTopic  = "Home/FF_MasterBedroom/AirConditioner/ModeC";
const char* mqttTempStateTopic    = "Home/FF_MasterBedroom/AirConditioner/TemperatureS";
const char* mqttTempCommandTopic  = "Home/FF_MasterBedroom/AirConditioner/TemperatureC";
const char* mqttSpeedStateTopic   = "Home/FF_MasterBedroom/AirConditioner/SpeedS";
const char* mqttSpeedCommandTopic = "Home/FF_MasterBedroom/AirConditioner/SpeedC";

The functions we will control are:

  • Power state: ON/OFF
  • Mode: AUTO, COOL, FAN and HEAT
  • Speed: AUTO, LOW, MED y HIGH
  • Temperature: AUTO or a value between 17 and 30 degrees celsius

The rest of the logic should be simple: When receiving MQTT commands from the server, we will actuate over the function relevant to the topic (change mode, change temperature…). For the power state command, the expected values are ON or OFF:

void callback(char* topic, byte* payload, unsigned int length) {
  String topicString = String(topic);
  byte* payloadZeroTerm = (byte*)malloc(length+1);
  String payloadString;
  byte payloadByte;

// Conversion of payload to String
  memcpy(payloadZeroTerm, payload, length);
  payloadZeroTerm[length] = '\0';
  payloadString = String((char*)payloadZeroTerm);

// Conversion of payload to Byte
  payloadByte = (byte)payloadString.toInt();

// Power Topic: Payload will be "ON" or "OFF"
  if(topicString.equals(String(mqttPowerCommandTopic))) {
    if (payloadString.equals("ON")) {
      sendPowerCommand(true);
    } else if (payloadString.equals("OFF")) {
      sendPowerCommand(false);
    }

.
.
.

For the speed command, the expected values are AUTO (which corresponds to the speed 0 in our test sketch), LOW (corresponding to the speed 1), MED (speed 2) or HIGH (speed 3).

.
.
.
// Speed Topic: Payload will be "AUTO", "LOW", "MED", or "HIGH"
  } else if(topicString.equals(String(mqttSpeedCommandTopic))) {
    if (payloadString.equals("AUTO")) {
      sendSpeedCommand(0);
    } else if (payloadString.equals("LOW")) {
      sendSpeedCommand(1);
    } else if (payloadString.equals("MED")) {
      sendSpeedCommand(2);
    } else if (payloadString.equals("HIGH")) {
      sendSpeedCommand(3);
    }
.
.
.

For the mode command, the logic is almost the same as for the speed, but with the values AUTO (mode 0), COOL (mode 1), FAN (mode 2) and HEAT (mode 3).

.
.
.
// Mode Topic: Payload will be "AUTO", "COOL", "FAN" or "HEAT"
  } else if(topicString.equals(String(mqttModeCommandTopic))) {
    if (payloadString.equals("AUTO")) {
      sendModeCommand(0);
    } else if (payloadString.equals("COOL")) {
      sendModeCommand(1);
    } else if (payloadString.equals("FAN")) {
      sendModeCommand(2);
    } else if (payloadString.equals("HEAT")) {
      sendModeCommand(3);
    }
.
.
.

The temperature expects values of AUTO (value 0 in our test sketch) or an amount of degrees celsius, between 17 and 30.

.
.
.
// Temperature Topic: Payload will be "AUTO" or a number corresponding to the desired temperature, between 17 and 30
  } else if(topicString.equals(String(mqttTempCommandTopic))) {
    if (payloadString.equals("AUTO")) {
      sendTemperatureCommand(0);
    } else {
      if (payloadByte >= 17 && payloadByte <= 30) {
        sendTemperatureCommand(payloadByte);
      }
    }
  }

  publishStates();
}

Apart of sending the status update for the changed function, to let the server know the command was executed, we will send all of the status every 5 seconds, for cases in which the server status get corrupted or reset, for example if the server is restarted.

void loop() {
  String payload;
  
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  long now = millis();
  if (now - lastCheck > 5000) {
    lastCheck = now;

    publishStates();
  }
}

void publishStates() {
//  Publish power state
  if (powerState) {
    client.publish(mqttPowerStateTopic, "ON");
  } else {
    client.publish(mqttPowerStateTopic, "OFF");
  }

//  Publish mode
  if (mode == 0) {
    client.publish(mqttModeStateTopic, "AUTO");
  } else if (mode == 1) {
    client.publish(mqttModeStateTopic, "COOL");
  } else if (mode == 2) {
    client.publish(mqttModeStateTopic, "FAN");
  } else if (mode == 3) {
    client.publish(mqttModeStateTopic, "HEAT");
  }

//  Publish temperature
  if (temperature == 0) {
    client.publish(mqttTempStateTopic, "AUTO");
  } else {
    client.publish(mqttTempStateTopic, String(temperature).c_str());
  }

//  Publish speed
  if (fanSpeed == 0) {
    client.publish(mqttSpeedStateTopic, "AUTO");
  } else if (fanSpeed == 1) {
    client.publish(mqttSpeedStateTopic, "LOW");
  } else if (fanSpeed == 2) {
    client.publish(mqttSpeedStateTopic, "MED");
  } else if (fanSpeed == 3) {
    client.publish(mqttSpeedStateTopic, "HIGH");
  } else if (fanSpeed == 4) {
    client.publish(mqttSpeedStateTopic, "SPECIAL");
  } else if (fanSpeed == 5) {
    client.publish(mqttSpeedStateTopic, "OFF");
  }
}

Once this is done, we have the software part done! Let’s see what is behind that case. Time to get a screwdriver!

Out victim

First, we will remove the lid and carefully observe the amount of dust a machine like this can accumulate 😂

Yup… I was expecting that…

It looks like all is fixed in place with four screws: one clearly visible once the lid is removed, and another three inside the air vent, behind small plastic covers. After removing all of them, the whole cover is removed without major fight.

Umm… Yep… A ton of cleaning is needed here…

The hardware modification seems simple enough: The small board at the bottom right contains the status LEDs and the IR receiver, and it is connected to the main PCB with some wires and a connector. The idea is to glue an IR LED pointing to the receiver, in a way that the commands sent are visible to that receiver but not blocking all the view from the outside, in case we need to use the original remote in the future.

The small board: our entry point

The next step will be creating a small board for the ESP-01, a cable connecting it to the IR LED and the installation of all that inside the air conditioner unit.

See you in the next post!

Continues on part 3 >>

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Ver
Privacidad