7. Using the relay for control (Arduino) - ControlBits/EMIT GitHub Wiki

As you've made it this far, you already have a functional Environmental Monitoring IoT Sensor sending data via MQTT, but we don't have to stop there ... EMIT has an on-board relay that we can use to provide some local control such as turning ON a heater when the temperature falls below a pre-set threshold (i.e. build a thermostat).

The relay could be used for any number of other use cases, including triggering a watering pump, fan or even an audible alarm but in this particular case, we're going to stick with adding a temperature based control loop to EMIT.

IMPORTANT SAFETY NOTICE: EMIT's maximum operating Voltage is 12 volts. UNDER NO CIRCUMSTANCES should you attempt to switch any voltages higher than 12 volts with on-board relay.

7.1 Setting up the relay in setup()

The ESP32's GPIO pins can not provide enough power to directly drive a relay so the EMIT PCB has a built in 'FET relay driver' which is connected to GPIO pin 26. Setting this pin to '1' will cause the FET relay driver to turn the relay ON, setting it to '0' will turn the relay OFF.

The first thing we need to do in setup() is to configure GPIO pin 26 as an output pin and set it to 0 to ensure the relay is off by default. We do this in exactly the same way as we did the LEDs earlier in this guide.

 // configure Relay GPIO to be OUTPUT
 // so that we would control the Relay (ON , OFF)
 pinMode(EMIT_RELAY , OUTPUT);
  // on the beginning we will turn OFF the Relay
 digitalWrite(EMIT_RELAY , LOW);

As we did for LEDs to improve the readability EMIT_RELAY is defined to GPIO 26 on the definition section of the sketch.

#define EMIT_RELAY    26

We will be adding the 'relay state' to our MQTT messages, to make this easier we'll define a global 'relayState' variable (of type String) and set the initial relay state to 'OFF'.

// define relayState string and set to "OFF"
String relayState = "OFF";      

We also need to define a 'temperature set point', which is the point we wish the relay to turn ON or OFF. We define another global float variable as follow:

// temperature set point for relay (Celsius)
float tempSet = 21.0;           

That's the setup done ... now onto the relay control loop.

7.2 Adding the relay control loop to loop()

Our relay control loop is very simple:

If the 'current temperature' is greater than or equal to the 'temperature set point' turn the relay OFF, otherwise turn the relay ON

Re-writing this control loop logic in Arduino/C++ we get:

 if(tempC >= tempSet){
   // turn relay OFF
   digitalWrite(EMIT_RELAY , LOW);
   // set relayState string to "OFF"
   relayState = "OFF";   
    
 }else{
   // turn relay ON
   digitalWrite(EMIT_RELAY , HIGH);
   // set relayState string to "ON"
   relayState = "ON";
 }

This control loop needs to be added to our loop() just after the AM2302 has been read and decoded.

7.3 Adding relay status to our MQTT message

The last thing left to do is add our 'temperature set point' (tempSet) and the 'relayState' to our MQTT message. We do this by appending two new attributes: 'TempSetPointC' and 'Relay' to our existing MQTTmsg string as follows:

 snprintf(JSONstring,150,
         "{\"TimeUTC\":\"%s\",\"TemperatureC\":\"%f\",\"Humidity\":\"%f\",\"TempSetPointC\":\"%f\",\"Relay\":\"%s\"}",
         get_time_str(),tempC,humidity,tempSet,relayState);

With our relay control loop and revised JSONstring added, the complete loop() should look like this:

void loop() {
 // handle the whole MQTT connection
 mqttclient_connection();
 //Wait for 5 seconds ( 5000 milliseconds )
 delay(5000);
 
 // turn the red LED ON
 digitalWrite(EMIT_RED_LED , HIGH);
 
 // Print a debugging message on the Serial Terminal indicating the start of the read attempt.
 Serial.println("Reading AM2302 ...");
 // Reading temperature or humidity takes about 250 milliseconds!
 // Sensor readings may also be up to 2 seconds
 float humidity = dht.readHumidity();
 // Read temperature as Celsius (the default)
 float tempC = dht.readTemperature();
 // Read temperature as Fahrenheit (isFahrenheit = true)
 float tempF = dht.readTemperature(true);
  // turn the red LED off indicating the end of the read cycle
 digitalWrite(EMIT_RED_LED , LOW);
 
 if(tempC >= tempSet){
   // turn relay OFF
   digitalWrite(EMIT_RELAY , LOW);
   // set relayState string to "OFF"
   relayState = "OFF";   
    
 }else{
   // turn relay ON
   digitalWrite(EMIT_RELAY , HIGH);
   // set relayState string to "ON"
   relayState = "ON";
 }
  snprintf(JSONstring,150,
         "{\"TimeUTC\":\"%s\",\"TemperatureC\":\"%f\",\"Humidity\":\"%f\",\"TempSetPointC\":\"%f\",\"Relay\":\"%s\"}",
         get_time_str(),tempC,humidity,tempSet,relayState);
 
       
 // Print the read values on the Serial Monitor.
 Serial.println(JSONstring);
 
 // only call the publish function if the client is really connected to the broker
 if(mqttclient.connected())
   mqttclient.publish(deviceUUID, JSONstring);
 else
   // if not connected the mqtt_connection function will try to recover shortly on the next loop
   Serial.println("failed to publish, no connection...");
 
}

If you upload the new Sketch to EMIT, and head back over to the EMIT 'LIVE' dashboard, you should now see the relay status and relay set point displayed like this:

EMIT LIVE with relay But we're not done quite yet ... there is one more improvement we can make ... add some 'hysteresis' to our relays control loop.

7.4 Adding hysteresis to our control loop

The simple control loop that we've created above works well but isn't ideal because the resolution of the temperature sensor is 0.1 Celsius - making it very sensitive to minor changes in temperature.

As a result of this sensitivity, it is not uncommon for the temperature to change every time it is read - this can in turn result in the relay turning 'ON' or 'OFF' with every measurement - every 10 seconds! This behaviour is not helpful and can actually reduce the life of the relay.

We can overcome this problem by adding 'hysteresis' to our relay's control loop. Hysteresis effectively reduces the sensitivity of the control loop by adding a 'dead band'.

Once our relay has turned OFF, we want it to stay OFF until it has passed a lower threshold set by our: 'temperature set point' less the 'hysteresis' we wish to set.

As an example, if our temperature set point is 21.0 Celsius, if we introduce a hysteresis of 0.5 Celsius, we don't want the relay to turn back on until the temperature drops below 21.0 - 0.5 = 20.5 Celsius.

Thankfully, this is easier to implement than explain!

define a global float hysteresis variable:

// relay control loop hysteresis (Celcius)
float hysteresis = 0.5;         

Then, on our loop() we just need to add a third, intermediate stage to add the hysteresis to our control loop. This is done as follows:

 if ((tempC > (tempSet - hysteresis)) && relayState == "OFF"){
   // turn relay OFF
   digitalWrite(EMIT_RELAY , LOW);
 }

This needs to be added between the 'if' and 'else' statements in our existing control loop.

With the hysteresis added, the complete loop() should look like this:

void loop() {
 // handle the whole MQTT connection
 mqttclient_connection();
 //Wait for 5 seconds ( 5000 milliseconds )
 delay(5000);
 
 // turn the red LED onma
 digitalWrite(EMIT_RED_LED , HIGH);
 
 // Print a debugging message on the Serial Terminal indicating the start of the read attempt.
 Serial.println("Reading AM2302 ...");
 // Reading temperature or humidity takes about 250 milliseconds!
 // Sensor readings may also be up to 2 seconds
 float humidity = dht.readHumidity();
 // Read temperature as Celsius (the default)
 float tempC = dht.readTemperature();
 // Read temperature as Fahrenheit (isFahrenheit = true)
 float tempF = dht.readTemperature(true);
  // turn the red LED off indicating the end of the read cycle
 digitalWrite(EMIT_RED_LED , LOW);
 
 if(tempC >= tempSet){
   // turn relay OFF
   digitalWrite(EMIT_RELAY , LOW);
   // set relayState string to "OFF"
   relayState = "OFF";   
    
 }
  if ((tempC > (tempSet - hysteresis)) && relayState == "OFF"){
   // turn relay OFF
   digitalWrite(EMIT_RELAY , LOW);
 }
 else{
   // turn relay ON
   digitalWrite(EMIT_RELAY , HIGH);
   // set relayState string to "ON"
   relayState = "ON";
 }
  snprintf(JSONstring,150,
         "{\"TimeUTC\":\"%s\",\"TemperatureC\":\"%f\",\"Humidity\":\"%f\",\"TempSetPointC\":\"%f\",\"Relay\":\"%s\"}",
         get_time_str(),tempC,humidity,tempSet,relayState);
 
       
 // Print the read values on the Serial Monitor.
 Serial.println(JSONstring);
 
 // only call the publish function if the client is really connected to the broker
 if(mqttclient.connected())
   mqttclient.publish(deviceUUID, JSONstring);
 else
   // if not connected the mqtt_connection function will try to recover shortly on the next loop
   Serial.println("failed to publish, no connection...");
 
}