Creating Interlocks In Code (C++) For Controlling Relays In the Real World

[RELEASING IN MAY 2023]




Work In progress

Are you familiar with an interlocks? Interlocking is a safety measure designed to prevent signals and switches from being changed in an improper sequence.

Interlocks are a staple of our modern lives, they exist in places you would or perhaps may not expect. In their most basic form, an interlock prevents a device changing state unless the corresponding device had been changed first.

This term has its origins nestled in railways with the official definition of interlocking is:

An arrangement of signals and signal appliances so interconnected that their movements must succeed each other in proper sequence“.

Wikipedia

Expand this to concept to relays, in the purest hardware sense, we now have a situation where we have two or more relays, composed of two or more coils, each with its own armature and associated contacts, is arranged so that movement of one armature or the energising of its coil is dependent on the position of the other armature.

[Insert] picture

Interlocks are important, from adding fail-safes to prevent damage and loss when electrical systems fail through to saving lives. Think about the train you may use in your commute, or perhaps working on a machine that could be fatal to a worker. Whilst software can fail, how do you ensure that train is not coming in the other direction, or a system is not energised whilst being worked on? Interlocks.

But interlocks also play their part in building reliable fail-safe systems in your house. Everything fails all the time, and we need to catch failure. Interlocks are designed to catch these edge cases and catching these edge cases in my house is how I was introduced to this important building block.

In my house I drive various devices with relays, (FSH Electronic Stikes, FSH Magnetic Locks, Roller Doors, Roller Shutters, Heating, Heating Zone Control), but sometimes failures can occur. Failures may occur due to my code (it happens, power loss, or mechanical failure).

For the developers reading this blog, an interlock is my ‘try-catch’ clause, because failure can have an undesirable result. As an example, all 4 of my roller doors are controllable by a momentary dry contact, perfect for a relay. But what happens if my MCU reloads or looses power and the relays change state, from a security perspective I dont want my doors to change state. Yes, I could other compensating logic such as door position and presence, but I would prefer they don’t change state in the first place.

Hopefully you can tell why relays are so important, they aren’t LLM sexy, but they are a staple of the modern world and in this post I am going to illustrate how you can build your own interlock in C++ for Arduino, ESP8266/ESP32 based MCU’s . I will cover

  • How interlocks work
  • Hardware vs Software
  • Pros and Cons of Domestic User
  • Arduino C++ Sketch for Hardware and Software Relay Interlocks

How Interlocks Work?

Hardware Vs Software


An interlock can be achieved via multiple methods. Hardware (the physical world) and software (the logical world). Both provide you the ability to create an interlocking system, but there are pros and cons to each approach.

  • Hardware
    • The biggest reason why hardware interlocks are the standard approach that is used is because of they are reliable. When there can be no compromise, leverage a physical hardware approach.

      There is no software to author, more so, there is no chance of getting your code wrong. There are no edge cases that wont work. This is physical electrical isolation. The draw-back of this approach is in the amount of hardware that is required and wiring complexity. You will need at least 2 relays per switched item you are driving and an equal amount of output ports on your MCU. A 2-relay interlock could be expressed as N+1, with 3-relays being N+2.
  • Software
    • This logic can be emulated in code to provide electrical isolation. Before turning on an output, our logic checks and adjusts other outputs to ensure that our interlock is in place.
      This approach works, but is dependant on code, how you have wired your outputs and there are drawbacks in place. The major drawback is this approach is susceptible to human error, what happens if your have the wrong output port selected in your code for your interlocking function? At best case, nothing happens, or you could damage a device but at other extreme it could be fatal.

      Think about impact vs likelihood in this approach. What is the impact if it does go wrong and is it palatable. Software interlocks are great method for providing the same functionality as hardware interlocks, with less cost, materials. Software interlocks must be wired in such a way that the load soft fails. What I mean by this is the load being driven will be off or in a safe state. An example of this is, my heater will not function, rather than stay on, or my roller shutter wont move up or down. More on this soon.

Like any skilled builders, being aware of the approaches and patterns will allow you to make right decision that balances the pros and cons of each approach. I use a combination of hardware and software in my house. I am selective on where I choose to use software interlocks, because failure in these scenarios will result only in an inconvenience and not damage.

Wiring Diagrams

Examples

//Timers for Timing
unsigned long lastRelayCH1Time = 0;
unsigned long lastRelayCH2Time = 0;


//Setup Relays And Define Pins
#define RELAY_CH1  2
#define RELAY_CH2  3

//Start MQTT in establishment of HTTP
//IP Address of MQTT Server
IPAddress MQTTserver(10, 0, 0, 200);
// MQTT
EthernetClient ethClient;
PubSubClient client(ethClient);    

void setup() {
  // *** DEFINE SETUP ***
    Serial.begin(9600); // set the baud rate
    Serial.println("Serial Port Ready"); // print "Ready" once
    Serial.flush();
  
    // Set outputs but initial state is the state of OFF
    pinMode(RELAY_CH1, OUTPUT);
    pinMode(RELAY_CH2, OUTPUT);
    digitalWrite(RELAY_CH1, HIGH);
    digitalWrite(RELAY_CH2, HIGH);
}

// Functions to check if relays need to be switched LOW for Roller Shutters  
// If relay is HIGH, stop it when timer is ringing is reached
void checkRelayCH1 () {
    if (lastRelayCH1Time  == 0) {
        return;
    }
    if ((millis() - lastRelayCH1Time)  > relayDelay) {
          digitalWrite(RELAY_CH1, HIGH);
          lastRelayCH1Time = 0;
    }
} 

void checkRelayCH2 () {
    if (lastRelayCH2Time  == 0) {
        return;
    }
    if ((millis() - lastRelayCH2Time)  > relayDelay) {
          digitalWrite(RELAY_CH2, HIGH);
          lastRelayCH2Time = 0;
    }
} 


void loop() {
    // Send Data To Serial Port
    char inByte = ' ';
    if(Serial.available()){ // only send data back if data has been sent
      char inByte = Serial.read(); // read the incoming data
      Serial.println(inByte); // send the data back in a new line so that it is not all one long line
    }



    //check if we need to toggle relays for shutters
  
    checkRelayCH1();
    checkRelayCH2();
}

  if (MQTTTopic.indexOf("Roller_1") > 0) {
      if ((MQTTPayload == "off") && (MQTT_Roller_1 != "off")) {
        digitalWrite(RELAY_CH9, LOW);
        digitalWrite(RELAY_CH10, LOW);
        lastRelayCH9Time = millis();            
        //MQTT PUBLISH AN UPDATE BACK
        client2.publish("stat/ARDUINO_Roller_1/POWER","off");
        delay(5);
        //Use for Home Assistant 
        client2.publish("stat/ARDUINO_Roller_1_Close/POWER","off");
        delay(5);
        client2.publish("stat/ARDUINO_Roller_1_Open/POWER","on");
        delay(5);
        client2.publish("stat/ARDUINO_Roller_1_Mesh/POWER","off");
        delay(5);
        client2.publish("stat/ARDUINO_Roller_1_Half/POWER","off");
        MQTT_Roller_1 = "off";
      }     
      if ((MQTTPayload == "on") && (MQTT_Roller_1 != "on")) {
         digitalWrite(RELAY_CH10, HIGH);
         digitalWrite(RELAY_CH9, HIGH);
        
        lastRelayCH10Time = millis();             
        //MQTT PUBLISH AN UPDATE BACK
        client2.publish("stat/ARDUINO_Roller_1/POWER","on");
        delay(5);
        
        //Use for Home Assistant 
        client2.publish("stat/ARDUINO_Roller_1_Close/POWER","on");
        delay(5);
        client2.publish("stat/ARDUINO_Roller_1_Open/POWER","off");
        delay(5);
        client2.publish("stat/ARDUINO_Roller_1_Mesh/POWER","off");
        delay(5);
        client2.publish("stat/ARDUINO_Roller_1_Half/POWER","off");
        delay(5);
        MQTT_Roller_1 = "on";
      }





Leave a Comment