Check and correct based of sensor many sensors- logic structure


#1

Friends,

I am having trouble thinking this one through. I have a bunch of inputs from various sensors. I have;
-water temp sensor
-ec meter
-ambient temp sensor

  • a few float swiches
    and a few more that escape me.

I am looking to get a top level strategy for how to code this. Here is what i want. I have the code setup with functions and interrupts that change flag variable. I plan to use the flags to make the code do things. The question is how do I do this? With a bunch of nested if statements?

What is important to me is that say, for example, the water temperature is too high I’d like for it to correct the water temperature then restart the checks of all the sensors to make sure
that they’re still in range.

I hope this makes sense and someone can lend some insight to my newb-y problem. Thanks in advance.


#2

@jjlee32,

I have been working on similar projects for some time. I started with a “nested if” approach but have been moving to a finite state machine approach. My journey to this approach is extensively documented on this thread starting with a suggestion from @peekay123. A more net version of this can be found here thanks to @gusgonnet

the thing I like about this approach is that before you start writing code, you need to think about what you want the program to do and lay out the flow in a visual state diagram. Once you have this, writing the code is more straightforward as you are simply writing the code for each unique and well defined state and its assigned outcomes.

I hope this is helpful.

Chip


#3

Thanks Chip. I think that is going to what i am going to implement into my system eventually but the learning curve seems steep, and i don’t have much time. I have to deliver this prototype by the end of this week and I don’t think that i can pick up enough knowledge to implement this in time.

Is there any other advice you can give me to get it to a working state (no pun intended) but also make the transition to FSM as smooth as possible?


#4

Just get the code working for each sensor individually and then merge them all together one at a time.


#5

I have them all merged into one .ino file now, I just need the code to check all the sensor to make sure they are where i want them. If they are I’d like to to run a pump for a fixed amount of time. If they are not i’d like to publish the reason for the error and/or take corrective action.

For my inexperienced brain it sounds like its going to be one big nasty nested if statement or a bunch of smaller possibly more nasty if statements.


#6

Just run down a list of if statements to trigger an action based on the read sensor data.

It’s simple, just get started and share your code here if you get stuck. Nobody is going to write it for you.

A week is more than enough time.


#7

Thanks, definitely wasn’t expecting someone to write it for me. I just wanted to get some advice before i started to work with it. Thanks for all of your insight and I will report back with my results.


#8

@jjlee32,

I understand your reluctance to take on a new approach like state machines. Thinking about it, I realized that @peekay123 suggested it to me when I already had working code and it just happened to make sense for me to take the plunge.

If it helps, I have written some code that may be similar to what you are trying to do. I had a number of factors (based on sensors, time of day and weather) that had to be considered before deciding whether of not to water. I used a single “super loop” of nested IF statements which would be evaluated and a watering interval assigned. If the interval was non-zero, there was a separate IF that would catch this and then send the command to the solenoid valve. Here is the main loop so you can see the structure:

void loop() {
  if (Time.hour() != currentPeriod)                       // Spring into action each hour on the hour
  {
    Particle.publish("weatherU_hook");                    // Get the weather forcast
    NonBlockingDelay(5000);                               // Give the Weather Underground time to respond
    takeMeasurements("1");                                // Take measurements
    currentPeriod = Time.hour();                          // Set the new current period
    currentDay = Time.day();                              // Sets the current Day
    if (waterEnabled)
    {
      if (currentPeriod >= startWaterHour && currentPeriod <= stopWaterHour)
      {
        if ((strcmp(capDescription,"Very Dry") == 0) || (strcmp(capDescription,"Dry") == 0) || (strcmp(capDescription,"Normal") == 0))
        {
          if (currentPeriod != lastWateredPeriod || currentDay != lastWateredDay)
          {
            if (expectedRainfallToday <= rainThreshold)
            {
              strcpy(wateringContext,"Watering");
              if (currentPeriod == startWaterHour) wateringMinutes = longWaterMinutes;  // So, the first watering is long
              else wateringMinutes = shortWaterMinutes;                                 // Subsequent are short - fine tuning
            }
            else strcpy(wateringContext,"Heavy Rain Expected");
          }
          else strcpy(wateringContext,"Already Watered");
        }
        else strcpy(wateringContext,"Not Needed");
      }
      else strcpy(wateringContext,"Not Time");
    }
    else strcpy(wateringContext,"Not Enabled");
    Particle.publish("Watering", wateringContext);        // Update console on what we are doing
    sendToUbidots();                                      // Let Ubidots know what we are doing
  }
  if (wateringMinutes)                                    // This IF statement waits for permission to water
  {
    unsigned long waterTime = wateringMinutes * oneMinuteMillis;    // Set the watering duration
    wateringMinutes = 0;                                  // Reset wateringMinutes for next time
    turnOnWater(waterTime);                               // Starts the watering function
  }
}

As you can see, I also assigned a “context” to the outcome regardless of watering or not. This helped in debugging.

Here is the repository with the full program. Again, I hope this is helpful and as @RWB pointed out, you can post your code as you make progress and folks will be willing to give you a hand.

Thanks,

Chip


#9

Thanks for sharing that. I am thinking of doing something along the lines of check and correct all the variables one at a time. Do you think that I will run into problems this way? Not all of the variables are indepandant. For example, if the system needs to correct the EC of the working fluid it calls an addWater function which depend on state of the float valves in the system.

The float valves are in an ISR and the addWater is a fuction called from an if… I will find out and report back.

BUT the reason for my reply is, in all this time i have never driven a pin mode from a function. I am doing using a while statement and a timer.

Here is the if that class the addWater function in the loop;

if(BottomLastState!=g){
        if(g==1){
            Serial.println("bottom flowing up");
        }
    else if(g==0){
       Serial.println("bottom flowing down");
       addWater(addWaterInterval);
    }
      BottomLastState=g;

I know the ISR works and i’ve tested it using a serial monitor, and the addWater function that doesn’t work.

void addWater(int addWaterInterval){
     timer0=0;
     while(timer0> addWaterInterval){
         digitalWrite(addWaterValve, HIGH);
     }
      
  }

I tried to look at your post at how to do it and got all confused. Most of my functions are super simple like this, if this, turn something on for a declared amount of time.

I am almost certain this is wrong but couldn’t find much on the internets, most searches yielded a simple timed led using delay. I don’t think I want to use delay as it suspends the ISR and such, is this correct?

Thanks again.


#10

@jjlee32,

It can be hard looking at code snippets but, here are my thoughts based on what you have posted

  • I would recommend more descriptive variable names for your and your reader’s benefit
  • Indentation helps folks understand your code structure, it also helps if you include complete statements (we don’t see the outer IF statements closing bracket in the first code snippet
  • If g can be either true or false, you can declare it a type bool and make more concise code if(!g) instead of if (g==0)

Now, as for your code, it is generally best if you avoid using “blocking” code such as delay. Delay will not prevent an interrupt service routine from being called but, if it is long, it can interfere with the Particle processes. Here is a simple non-blocking interval routine:

int startedAddWater = 0;  // Particle int is 32 bits
int addWaterInterval = 0;
bool valveOpen = fasle;

void setup { 
  Serial.begin(9600);
  Serial.println("Example code for watering");
}

void loop 
{
  if (statements and other logic determines it is time to water which is stated with these two lines) 
  {
    startedAddWater = mills();
    addWaterInterval = 60000;  // This will add water for 60 seconds
  }

  if (addWaterInterval >= 0) 
  {
    if (mills() < startedAddWater + addWaterInterval)
    {
      if (!valveOpen) // Testing here prevents us from having to do a bunch of digitalWrites
      { 
        digitalWrite(addWaterValve,HIGH);
        valveOpen = true;  // Using this flag keeps us from having to do a bunch of digitalReads
        Serial.println("Turning on the water");
      }
    }
    else 
    {
      digitalWrite(addWaterValve,LOW);
      valveOpen = false;
      Serial.println("Turning off the water");
      addWaterInterval = 0;  // Interval over, reset for next cycle
    }
  }
}

This will allow the main loop to run which will allow Particle to process and there are no delays.

Make sense?

Chip


#11

Chip,

Thanks for the detailed explanation. In my last post i mentioned that I was going to try to check them one by one to try and avoid any unforeseen bugs. So the code I posted was just one of the many ‘checks’ in the loop. I may implement boolean statements for each and at the end check all of them in one long if statement that would start the main cycle. Do you think it’d be better as many nested ifs?

In regard to your sample code you posted. I think that example is great, I am going to try to implement a similar style right now. Ill touch base with results soon. Thank you for all your help thus far.


#12

@jjlee32,

Your approach could work but, at the risk of over complicating things, I wanted to point out one other approach which may work for you.

The idea is to create a control register and store the “state” of each of your boolean checks in each bit of the register. The value of the register would then represent the “state” of your system. The nice thing about this approach is that you can store the register in the EEPROM on the Particle device so the system can remember and reinitialize its state if it is reset. This will enable the use of a watchdog timer or the quick recovery from a reset or power glitch.

In order to make this work, you will need to use bit-wise operators which can be confusing if you are new to them. If this sounds interesting to you, I can provide some sample code. Otherwise, proceed with your plan and share your progress.

Thanks,

chip


#13

I thought of doing something similar originally. Something like incrementing a counter for each check and if the counter is equal to the number of checks proceed to run the other code.

In regard to my previous issue, your suggestion helped me lay out my loop code and functions. Here is what I have;

#include <elapsedMillis.h>
#define FloatPinBottom D3       //pin must be interruptable
#define addWaterValve D5
int addWaterInterval = 5000;
int BottomLastState=-1;//
int startWaterTime=-1;
volatile int g = -1;
bool addWaterTrigger = false; 
elapsedMillis timer2;
elapsedMillis timer1;
int interval =100;

void setup(){

pinMode(addWaterValve,OUTPUT);
digitalWrite(addWaterValve,LOW);
pinMode(FloatPinBottom, INPUT_PULLUP);
attachInterrupt(FloatPinBottom, BottomLevelChange, CHANGE);

}
void loop(){
//BOTTOM FLOAT VALVE
       if(g==1) {
             if(addWaterValveOpen == true){
                 if(timer2-startWaterTime>addWaterInterval){
                    addWaterTrigger = false;
                    // Particle.publish("BottomFloat","FlowingUP");
                    // Serial.println("bottom flowing up");
                    Serial.println("turning the water off");
                    turnWaterOff();
               
            }
         }
    }
    if(g==0){
     //Particle.publish("BottomFloat","FlowingDOWN");
      // Serial.println("bottom flowing down");

        if(addWaterValveOpen==false){
           Serial.println("turning water on");
           addWaterTrigger = true;
           turnWaterOn();
        }
    }
}
void turnWaterOn(){
    if(addWaterTrigger){
        
        digitalWrite(addWaterValve, HIGH);
        addWaterValveOpen=true;
        startWaterTime=timer2;
        Serial.println("water is on");
    }
    
    
}
void turnWaterOff(){
    
    if (!addWaterTrigger){
    digitalWrite(addWaterValve,LOW);
    addWaterValveOpen=false;
    Serial.println("water is off");
     }

 
}

void BottomLevelChange() {
if (timer1 >interval){
    if((g==-1) || (g==0)){

        timer1=0;
        g=1;
    }
}
if (timer1 >interval){
    if (g==1){

        timer1=0;
        
        g=0;
    }
}
}

Mind you I only copy and pasted snippets from my master code, i don’t know if the above code compiles but you can probably get the idea. Thank you for your help. sometimes making a simple flowchart helps a whole lot.

one last question, I’ve read that timers will time out after a certain time and its nice to reset them after a certain amount of time. Is this true?


#14

@jjlee32,

Looks like you are making progress, great news.

I have not used the elapsedMillis library but, generally timers use unsigned long types for their math which means they will roll over in about 47 days. There is a great post on this topic here. Generally this is not an issue and, if you write the code correctly, roll overs are not an issue thanks to the wonders of 2s complement math.

I have a few comments to consider as you keep working:

  • if a value is to be just true or false use the type bool (g?) - this saves space as int is a 32-bit number while bool is 1
  • Avoid #define if you can as it can have unexpected consequences if one of your libraries happens to use the same term. One convention is that #define terms are ALLCAPS. In your example you can use “const int FloatPinBottom = D3;” instead
  • If you are going to use Serial.print(), you need to add Serial.begin(9600); to your Setup()
  • Again, I am not familiar with elapsedMillis but, I would expect you would need to reset the timers before or after you use them.

I hope this helps, and good luck with your continued progress.

Chip


#15

@chipmc, just for clarification:

  • bool in ARM GCC uses 1 byte per variable, still better than 4 bytes for an int.
  • The bootloader on Particle devices already initializes the Serial device. The side effect is that not having Serial.begin() still works! I’m not crazy about the “unexplicit” nature of this but it is what it is.
  • elapaseMillis() extends the millis() class so the incrementing of timers occurs “in the background”. Just like any timer, these need to be reset to begin the timing from zero again. :wink:

#16

you could also look at the Timer class:
create a global timer object:

Timer waterTimer(5000, [](){
    digitalWrite(addWaterValve,LOW); 
    addWaterValveOpen = false;
    Serial.println("water is off");
  }, true); // 5 second callback

and just start the timer after turning the water on:

if(addWaterValveOpen==false){
           Serial.println("turning water on");
           digitalWrite(addWaterValve, HIGH);
           addWaterValveOpen = true;
           waterTimer.start();

#17

Thanks for your continued support. The define tip you shared is great I would’ve never caught that if I accidentally used a shared term. The code above that I posted wasn’t my full code I just copy and pasted the relevant parts, so for sure i’d forget something like a Serial.begin command. I’ll keep you posted on the progress, thanks again!


#18

Thanks for sharing that. Does the Timer library offer any major benefits as opposed to the elapssedMillis library? Almost all of my timers so far have been from that library and haven’t had any major problems thus far.


#19

He is referring to the built-in software times which you can have 10, you can read about them here.
https://docs.particle.io/reference/firmware/photon/#software-timers
or
http://www.freertos.org/FreeRTOS-Software-Timer-API-Functions.html