Home Assistant – part 2: code template

template code snippet
20 Aug
2017

A code template for the ESP8266 boards

The Home Assistant project is going really well. As I anticipated in part one, I am working on a ESP8266-based setup to connect my devices over Wi-Fi. This is a serious project and therefore it needs some good planning. After fiddling with my boards for a bit, I started thinking about the key features that every ESP should implement.

Keeping these important points in mind, I wrote down a code template that will be a starting point for every future ESP-based project. This snippet will also help me to put in the code the features that make the boards stable, should an unexpected situation occur.

Overview

I recommend checking out the template on my GitHub before reading the explanations. You can see that the code uses some libraries:  the ESP8266 library provided by the IDE handles the wireless connection, while the PubSubClient library manages the MQTT communication. To test the code, a dummy binary input and a dummy output (LED) must be connected to pin 14 and 12 respectively.

The main capabilities of the code are: automatic reconnection, MQTT status publishing, MQTT command reception, command execution, error reporting with status LED, short reaction time, EEPROM status saving.

Timers

The template uses the AsyncTimer library. Every sketch that I wrote before starting my work on the template used several timers, so I coded this Arduino library that implements them in an object-oriented fashion. Here is a link of my post about this library. I recommend checking this out in order to understand the code snippets.

Automatic reconnection

The foundation of the code template is of course connectivity. The boards need to connect to the Wi-Fi network and then to the MQTT server. If one of them is missing it should simply try to reconnect. However, there is an issue here. Experience taught me that without a proper timer between reconnection attempts, the board sends thousands of packets per second to the network, and both the router and the server just give up. This is of course not an ideal scenario for a production environment.

This is the part of the code that handles the reconnections, called in the loop() method of the Arduino sketch.

//This method checks that the wifi is connected and sets the led blink time if there is anything wrong
void checkConnectivity() {

  //Check if the wifi is not connected
  if(WiFi.status() != WL_CONNECTED) {
    if(!wifiRequested) {
      WiFi.begin(WL_SSID, WL_PWD);
      blinkTimer.setInterval(NO_WIFI);
      wifiRequested = true;
    }
  } else {
    wifiRequested = false;
    //Check if MQTT is not connected
    if (!mqttClient.connected()) {
      if (recTimer.expired()) {
        Serial.println(" MQTT RECONNECT");
        if(mqttClient.connect(CLIENT_ID, USERNAME, PASSWORD)) {
          mqttClient.subscribe(COMMAND_TOPIC);
          /* Please add topics */
        }
        recTimer.reset();
      }
      blinkTimer.setInterval(NO_MQTT);
    } else {
      blinkTimer.setInterval(WORKING);
      //Publish the status
      if(publishStatus) {
        publishStatus = false;

        mqttClient.publish(STATE_TOPIC, toPayload(relayStatus));
        mqttClient.publish(STATE_TOPIC, toPayload(sensorValue));
        
        mqttClient.publish(STATE_TOPIC, " "); //Debug only
        
        /* Please add publish statements here */
      }
      //Loop the MQTT client. This is mandatory.
      mqttClient.loop();
    }
  }
  
}

You can see that, in case of errors, the interval of the blinkTimer is changed. This will be explained in the status LED section.

MQTT publishing

The code snippet posted above shows the message publishing policy: it happens only if the MQTT server is available and if the publishStatus boolean is set to true. This boolean is set every 30 seconds through a timer, with the idea to send a continuous feed of status information. This way Home Assistant never works with wrong information for more that that short period of time. Here is the code that handles the timer:

//Publish status timer handling
if(pubTimer.expired()) {
  publishStatus = true;
  pubTimer.reset();
}

MQTT reception and command execution

The reception of commands is handled as the MQTT library demands. The programmer must set a callback method that gets executed whenever a new message arrives. Of course, the board must be subscribed to the correct topics upon reconnecting to the MQTT server (see first snippet). Note that the callback method is executed, as the name suggests, as a callback: the library uses a hardware interrupt and therefore any delay() call will pause the message reception. If the QoS of MQTT is 0, this could cause a message loss.

Since the strings for MQTT topics, payloads and credentials are often memory demanding, they are saved in the PROGMEM space (memory dedicated to the source code) to save variable space.

The callback method checks if the topic of the message is correct. Based on the topic and the payload, it translates the command in a uint8_t volatile variable that is checked at the next loop. The following snippets report the callback and the command execution methods.

//Callback to receive MQTT commands. This only sets the command voltaile variable.
void callback(char* topic, byte* payload, unsigned int len) {
  
  //Parse the message
  char message_buff[len+1];
  int i = 0;
  for(i=0; i<len; i++) {
    message_buff[i] = payload[i];
  }
  message_buff[i] = '\0';
  
  //Check the topic and the command
  if(strcmp(topic, COMMAND_TOPIC) == 0) {
    if(strcmp(message_buff, PAYLOAD_ON) == 0) {
      command = 0;
    } else if(strcmp(message_buff, PAYLOAD_OFF) == 0) {
      command = 1;
    } else {
      Serial.println("Command not accepted");
    }
  }
  
}
//Execute a command
void executeCommand() {

  //Copy the command variable to avoid incoherent results caused by its modification while executing this method
  uint8_t cmd = command;
  command = ERR;
  if (cmd == 0) {
    digitalWrite(RELAY_PIN, HIGH);
    relayStatus = true;
    EEPROM.write(VALID_ADDR, true);
    EEPROM.write(STATUS_ADDR, true);
    EEPROM.commit();
    Serial.println("Executed command 0");
    publishStatus = true; //Publish the status immediately to let Home Assistant know of the change
  } else if (cmd == 1) {
    digitalWrite(RELAY_PIN, LOW);
    relayStatus = false;
    EEPROM.write(VALID_ADDR, true);
    EEPROM.write(STATUS_ADDR, false);
    EEPROM.commit();
    Serial.println("Executed command 1");
    publishStatus = true;
  }
  
  /* Please complete with other commands */
  
}

The EEPROM related lines are explained below.

Status LED

//Led blink timer handling
if (blinkTimer.getInterval() == WORKING) { //If the system is working keep the led on
  digitalWrite(LED_BUILTIN, LOW);
  ledStatus = true;
} else if(blinkTimer.expired()) {
  triggerLed();
  blinkTimer.reset();
}

Every ESP board has a built-in LED diode that is very handy for error reporting. In the template, it remains steadily on when there is no issue and it blinks when there is a connectivity error. More precisely, it blinks every 100 ms when the Wi-Fi network is not available and every second when the MQTT server can’t be reached. This LED helps identifying any network error in order to quickly fix it.

Short reaction time

Another key feature of the project is the immediate communication of every change of status. A Home Assistant server with outdated information could trigger some unwanted automations and make the programmer think something is wrong with his setup. To avoid this, the template sends the information as soon as sensor changes are detected, by setting the publishStatus boolean to true.

//Sensor reading timer handling
if(sensorTimer.expired()) {
  bool tempValue = digitalRead(SENSOR_PIN);
  if(tempValue != sensorValue) {
    //If there has been a change of the sensor value, publish the status to alert the server.
    //Generally, for int values, check if abs(difference) is greater than a threshold value.
    publishStatus = true;
    Serial.println("Change of sensor value");
  }
  sensorValue = tempValue;
  sensorTimer.reset();
}

Automatic EEPROM status saving

The template provides another interesting feature, that is the EEPROM status storage. The EEPROM is a persistent memory chip that preserves the information when the board is shut down. The template saves the status here whenever a command is executed. This way, if the sensor goes down or resets itself, it will know its previous status, restore it and send it to the server. For example, imagine a setup with a light on and no EEPROM safety checks. If the ESP board goes down, the light will be off at the restart and the only way to turn it back on would be to do it manually.

The sketch provides an additional valid bit check to tell the board if it should restore the status or fall back to default values. The status information is written in the EEPROM when any command is executed (see the snippet before “Status LED”) and it is restored in the setup() method of the code:

//EEPROM initialization. The EEPROM is used to save the status in case the board resets. Nothing should be written in the EEPROM in this stage.
EEPROM.begin(10);
bool valid = EEPROM.read(VALID_ADDR);
if(valid) {
  bool is_on = EEPROM.read(STATUS_ADDR);
  relayStatus = is_on;
  Serial.println("EEPROM value: "); Serial.println(is_on);
  digitalWrite(RELAY_PIN, is_on);
} else {
  //Else leave the default values
  Serial.println("Invalid EEPROM");
}

Conclusions

The template has been widely tested and it provides a good starting point for real ESP8266 IoT applications. The next step is getting rid of the buggy programs that I am currently running on my boards and replace them with some better template-based code.

Thank you for following my project so far.

Part 1 – home automation project

Part 3 – install on Arch Linux

Search bar

Subscribe

Did you miss my latest post? Subscribe to be notified whenever I pusblish a new article!

Archive

Social links

%d bloggers like this: