Best way to create Latching/toggle relays with out Delay

Here is one that I can't really get my head around.

So we are controlling a two Gate motor (Stock standard) and it has two modes.

  1. Free Access
  2. Trigger

This is to control the gates for a Hydro Station to Generate Power.

To open the gate you from static you can use free access and the gate will open till 100%. Should you want to stop the gate midway you can use a trigger. After stop, if you use trigger again it will close.

So we need to have 3 controls for the gate motor:
Open
Close
Stop

By using the two modes one can "predict" each mode with the following logic.

Open = Free Access
Close = Free Access + Trigger (To Stop) + Trigger (To Close)
Stop = Free Access + Trigger

So here is the challenge that we have.
There are four Relays, two for each gate that commits Free Access and Trigger (They need to Switch On then Off for each mode with a 1-second delay). There are also other functions that run so I don't want to use delay() in the functions.

In the program, I have tried a couple of options.
The first was to use the "Virtual.timer" function but it seems to be slow. Then I went the "proper way" using millis but the Code keeps on looking wrong for a better word - it does work, however :slight_smile: .

Here are the attempts that I made.

First was with the Delay option and is triggered by a Command from MQTT

void RelayCommand(int command){
  // Declare the Gate Options
  int openGateA[] = {0,0};
  int stopGateA[] = {0,0,1,1};
  int closeGateA[] ={0,0,1,1,1,1};
 for (int thisPin = 0; thisPin < pinCount; thisPin++) {
    digitalWrite(relayPins[thisPin], HIGH); // to make sure that all the relays is OFF
 }

  if (command == 1){  // Open Gate A
    for (int thisPin = 0; thisPin < 2; thisPin++) {
      int  i = openGateA[thisPin-1];
      RelaySwitching(i);
      delay(1000); 

    }
  }else if (command == 2){
    for (int thisPin = 0; thisPin < 4; thisPin++) {
      Serial.println(thisPin); 
      int  i = stopGateA[thisPin];
      RelaySwitching(i);
      delay(1000);  
      
    }
  }else if (command == 3){
       for (int thisPin = 0; thisPin < 6; thisPin++) {
      int  i = closeGateA[thisPin-1];
      RelaySwitching(i);
      delay(1000);  
      }
    }
  Serial.println(F("Command Done"));  
  
}



void RelaySwitching(int RelayPin ){
 if(digitalRead(relayPins[RelayPin]) == HIGH){
          digitalWrite(relayPins[RelayPin], LOW);
            }else{
          digitalWrite(relayPins[RelayPin], HIGH);
        } 
}

The Delay does work but at the cost of the program stopping for x seconds

The second attempt was to create a Timer that will run a bunch of functions. If one of them is true
it will run the command and then reset but that creates a lot of global variables.

byte counterGateAopen = 0 ;
byte counterGateAstop = 0 ;
byte counterGateAclose = 0 ;

byte gateAOpenCommand = 0;
byte gateAstopCommand = 0;
byte gateAcloseCommand = 0 ;

void manualGateTimer(){                             // This runs within the loop
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillisGate > 1000){
// -------------- Gate A -------------
    gateAOpenF ();
    gateAstopF ();
    gateAcloseF ();
    gateAcloseFull ();

     previousMillisGate = currentMillis; 
  }
}

void gateAOpenF (){                      
 if (gateAOpenCommand == 1){
 int rPins[] = {0,0};

if (counterGateAopen == 0){
      char status_0[40]="";
      sprintf(status_0 ,"%s%s%s","stat/",topic,"/GATE1");
      client.publish(status_0,"0");
}

  if (counterGateAopen < 2){
     counterGateAopen++ ;
     int  i = rPins[counterGateAopen-1];
      
  if(digitalRead(relayPins[i]) == HIGH){
    digitalWrite(relayPins[i], LOW);
    }else{
      digitalWrite(relayPins[i], HIGH);
    }
  }else{
   gateAOpenCommand = 0; 
   gateAcommandActive = 1 ;
   counterGateAopen = 0 ;
      char status_0[40]="";
      sprintf(status_0 ,"%s%s%s","stat/",topic,"/GATE1");
      client.publish(status_0,"1");
  }
 }
}

void gateAstopF (){                      
 if (gateAstopCommand == 1){
    int rPins[] = {0,0,1,1};

if (counterGateAstop == 0){
      char status_0[40]="";
      sprintf(status_0 ,"%s%s%s","stat/",topic,"/GATE1");
      client.publish(status_0,"0");
}
  if (counterGateAstop < 4){
     counterGateAstop++ ;
     int  i = rPins[counterGateAstop-1];
      
  if(digitalRead(relayPins[i]) == HIGH){
    digitalWrite(relayPins[i], LOW);
    }else{
      digitalWrite(relayPins[i], HIGH);
    }
  }else{
   gateAstopCommand = 0;
   gateAcommandActive = 1 ; 
   counterGateAstop = 0 ;
      char status_0[40]="";
      sprintf(status_0 ,"%s%s%s","stat/",topic,"/GATE1");
      client.publish(status_0,"1");
  }
 }
}

void gateAcloseF (){  
                   
 if (gateAcloseCommand == 1){
  //Serial.print(F("Gate A Close"));
    int rPins[] = {0,0,1,1,1,1};
 if (counterGateAclose == 0){
      char status_0[40]="";
      sprintf(status_0 ,"%s%s%s","stat/",topic,"/GATE1");
      client.publish(status_0,"0");
}

  if (counterGateAclose < 6){
     counterGateAclose++ ;
     int  i = rPins[counterGateAclose-1];
      
  if(digitalRead(relayPins[i]) == HIGH){
    digitalWrite(relayPins[i], LOW);
    }else{
      digitalWrite(relayPins[i], HIGH);
    }
  }else{
   gateAcloseCommand = 0; 
   gateAcommandActive = 1 ;
   counterGateAclose = 0 ;
      char status_0[40]="";
      sprintf(status_0 ,"%s%s%s","stat/",topic,"/GATE1");
      client.publish(status_0,"1");
  }
 }
}

Is there a more efficient way by controlling these relays?
Here is the full code of the program if you need to see the whole picture..

The Full code is a bit big so here is the ZIP file if you want to have a look ..

Hydro_Controller.zip (9.3 KB)

I can't even get my head round your description, but that might just be be me.

I'm confused about your use of the word "trigger".

Here you use it as a mode:

So we are controlling a two Gate motor (Stock standard) and it has two modes.

  1. Free Access
  2. Trigger

... but here it sounds more like a control:

Should you want to stop the gate midway you can use a trigger. After stop, if you use trigger again it will close.

... but trigger's not listed as a control:

So we need to have 3 controls for the gate motor:
Open
Close
Stop

I'd need clarity on that before trying to understand further.

I'd wager a state machine approach will do the trick:

Apologies - Trigger refers to what the gate motor controllers names it. Usually, it would be controlled by a remote. But you could also use a "hard controller.
A better description would be a "push button" and I would like the relay act as a push button. I do hope this helps..
I also guess if you are looking/working with a piece of code for too long you assume everyone knows what you are thinking / meaning.

PaulF007:
I do hope this helps..

I also guess if you are looking/working with a piece of code for too long you assume everyone knows what you are thinking / meaning.

No and Yes, respectively :wink:

Might I suggest you look at this thread, where in #7 there the OP provided a sort of walk through of the cycle from start to finish. I then drew up a state diagram (#14, #18) and ultimately a full state machine sketch #19.

Follow my state diagram as you look down his list, and perhaps you'll get an idea of how to derive your own state diagram.

I tend to think in state machines nowadays, and while I know it's not necessarily the ideal method in al cases (everything's a nail when all you have is a hammer) it certainly works for me.

I'd need a walk through like that: we start and when we do , , unless .... kind of thing.

Thank you for the link and info, I think I have an idea as to how this should work and I have attached a diagram of what I am trying to achieve.

Here is the code that I came up with would you say that it is the correct way of using the state machines?

/*
 Reconnecting MQTT example - non-blocking

 This sketch demonstrates how to keep the client connected
 using a non-blocking reconnect function. If the client loses
 its connection, it attempts to reconnect every 5 seconds
 without blocking the main loop.

*/

#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>

// Update these with values suitable for your hardware/network.
byte mac[]    = {  0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(192, 168, 8, 99);
IPAddress server(192, 168, 8, 30);

//+------------------------------------------------------------------+
//|Setup Relay
//+------------------------------------------------------------------+
const int AfreeAccess = 22;
const int Atrigger = 23;
const int BfreeAccess = 24;
const int Btrigger = 25;
byte runI ;
byte runIB ;
//+------------------------------------------------------------------+
//|Setup States
//+------------------------------------------------------------------+
//states
enum {ST_idle,ST_open, ST_stop, ST_closeA,ST_closeB} currentState = ST_idle;
bool running = true; //used later to prevent re-run without turning off first
bool newVisitToStart_up; //see later so as not to mess the timing
unsigned long stateTransitionAtMillisA;

enum {ST_Bidle,ST_Bopen, ST_Bstop, ST_BcloseA,ST_BcloseB} currentStateB = ST_Bidle;
bool runningB = true; //used later to prevent re-run without turning off first
bool newVisitToStart_B; //see later so as not to mess the timing
unsigned long stateTransitionAtMillisB;

enum {ST_RelayIdle,ST_AfreeAccess,ST_Atrigger} RelayCurrentState = ST_RelayIdle;
unsigned long relayMillis;


enum {ST_RelayBIdle,ST_BfreeAccess,ST_Btrigger} RelayCurrentStateB = ST_RelayBIdle;
unsigned long relayBMillis;

void callback(char* topic, byte* payload, unsigned int length) {
  // ------------------- Convert Payload to Int to use later ------------------
  char myNewArray[length+1];
  for (int i=0;i<length;i++) {
    myNewArray[i] = (char)payload[i];
  }
 myNewArray[length] = NULL;
 int command = atoi(myNewArray);  // 
 //-------------------- DONE -------------------------------
if (running == true){
switch (command) {
  case 1:
  if (currentState != ST_open) currentState = ST_open;
  Serial.println("Open Command");
  break;
  case 2:
  if (currentState != ST_stop) currentState = ST_stop;
  Serial.println("Stop Command");
  break;
  case 3:
  if (currentState != ST_closeA) currentState = ST_closeA;
  Serial.println("Close A Command");
  break;
  case 4:
  if (currentState != ST_closeB) currentState = ST_closeB;
  Serial.println("Close B Command");
  break;
  }
} 
if (runningB == true){
switch (command) {
  case 5:
  if (currentStateB != ST_Bopen) currentStateB = ST_Bopen;
  Serial.println("Open B Command");
  break;
  case 6:
  if (currentStateB != ST_Bstop) currentStateB = ST_Bstop;
  Serial.println("Stop B Command");
  break;
  case 7:
  if (currentStateB != ST_BcloseA) currentStateB = ST_BcloseA;
  Serial.println("Close B A Command");
  break;
  case 8:
  if (currentStateB != ST_BcloseB) currentStateB = ST_BcloseB;
  Serial.println("Close B B Command");
  break;
  }
} 

}

EthernetClient ethClient;
PubSubClient client(ethClient);

long lastReconnectAttempt = 0;

boolean reconnect() {
  if (client.connect("arduinoClient")) {
    // Once connected, publish an announcement...
    client.publish("outTopic","hello world");
    // ... and resubscribe
    client.subscribe("cmnd/hydro/#");
    Serial.println(F("READY"));
  }
  return client.connected();
}

void setup()
{ Serial.begin(57600);
  Serial.println("Start");
  client.setServer(server, 1883);
  client.setCallback(callback);

  Ethernet.begin(mac, ip);
  delay(1500);
  lastReconnectAttempt = 0;
  setupRelays ();
}


void loop()
{
  if (!client.connected()) {
    long now = millis();
    if (now - lastReconnectAttempt > 5000) {
      Serial.println(F("Con ......"));
      lastReconnectAttempt = now;
      // Attempt to reconnect
      if (reconnect()) {
        lastReconnectAttempt = 0;
      }
    }
  } else {
    // Client connected

    client.loop();
  }
  manageStatesA();
  manageRelaysA();
  manageStatesB();
  manageRelaysB();
}

void setupRelays (){
  pinMode(AfreeAccess, OUTPUT);
  pinMode(Atrigger, OUTPUT);
  pinMode(BfreeAccess, OUTPUT);
  pinMode(Btrigger, OUTPUT);
}

void manageStatesA(){
 switch (currentState){
  case ST_idle:
   stateTransitionAtMillisA = millis();  
  break;
  case ST_open:
    running = false ;
        if (RelayCurrentState == ST_RelayIdle && runI != 1){
      RelayCurrentState = ST_AfreeAccess ; 
      runI = 1;
      Serial.println("Run Free Access");
    }
      running = true ;
      currentState = ST_idle;
      runI = 0;
    break;
  case ST_stop:
    running = false ;
    if (RelayCurrentState == ST_RelayIdle && runI != 1){
      RelayCurrentState = ST_AfreeAccess ; 
      runI = 1;
      Serial.println("Run Free Access");
    }
    if (millis() - stateTransitionAtMillisA > 2000 && RelayCurrentState == ST_RelayIdle ){
      RelayCurrentState = ST_Atrigger ;
      currentState = ST_idle;
      running = true ;
      runI = 0;
    } 
  break;

    case ST_closeB:
    //  running = false ;
     Serial.println("Close B Running");
    running = false ;
    if (RelayCurrentState == ST_RelayIdle && runI != 1){
      RelayCurrentState = ST_Atrigger ; 
      runI = 1;
      Serial.println("Run Trigger");
    }
      currentState = ST_idle;
      running = true ;
      runI = 0;
  break; 

    
   case ST_closeA:
    //  running = false ;
    running = false ;
    if (RelayCurrentState == ST_RelayIdle && runI != 1){
      RelayCurrentState = ST_AfreeAccess ; 
      runI = 1;
      Serial.println("Run Free Access");
    }
    unsigned long now = millis() - stateTransitionAtMillisA ;
    // currentState = ST_idle;
    if (now  >= 2000 && now < 2500 && RelayCurrentState == ST_RelayIdle){
      RelayCurrentState = ST_Atrigger ;
    }
    if (millis() - stateTransitionAtMillisA >= 4000 && RelayCurrentState == ST_RelayIdle ){
      RelayCurrentState = ST_Atrigger ;
      currentState = ST_idle;
      running = true ;
      runI = 0;
    } 
  break;


  
 }
}