Try to build a state machine but have problems...

You should care more about variables and data types.

The posted code uses currentMillis, but I cannot find a declaration for it, nor where it gets a value. Replace it by millis(), then the delay should work.

Also consider which value !mTempState will have. In a state machine it's better to assign the next state directly, so that you can be sure which state is reached next.

Guido_Ov:
unfortunately it won't work...

You seem to have two programs in your Post. Which one should I look at? I have only a small brain so keep things simple.

...R

I think you're making life too complicated :wink:

You already have a function that does the reading of a sensor; make use of it. You can use the below as a framework.

// sensible names for the temperature statemachine states
#define ST_TEMP_DELAY 0   // temperature reading in delay state
#define ST_TEMP_READ 1    // temeprature reading in reading state

// current 'time'
unsigned long currentTime = 0;

// temperature sensor I2C addresses
byte mSensAddress[] = {0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F};
// temperatures; number of elements based on the number of elements in mSensAddress
float mTemp[sizeof(mSensAddress)];


void setup()
{
  // init serial port
  Serial.begin(9600);

  // make sure currentTime has a value other than 0
  // this prevents getTemp() from starting the delay three times because the Arduino is 'too' fast
  delay(1);
}

void loop()
{
  getTemp();
}

/*
  temperature reading state machine
*/
void getTemp()
{
  // store the current state for this statemachine; start with READ
  static byte currentState = ST_TEMP_READ;
  // 'time' when delay is finished
  static unsigned long delayEndtime = 0;

  // update current time
  currentTime = millis();

  switch (currentState)
  {
    case ST_TEMP_DELAY:
      if (currentTime >= delayEndtime)
      {
        Serial.print("Delay finished at\t"); Serial.println(currentTime);
        // change the state
        currentState =  ST_TEMP_READ;
      }
      break;
    case ST_TEMP_READ:
      Serial.println("Reading");
      // read all devices
      for (int cnt = 0; cnt < sizeof(mSensAddress); cnt++)
      {
        mTemp[cnt] = readDevice(mSensAddress[cnt]);
      }
      // set new delay end time
      delayEndtime += 5000;
      Serial.print("Delay start time set to\t"); Serial.println(delayEndtime);
      // done, change state
      currentState = ST_TEMP_DELAY;
      break;
  }
}


float readDevice(int address)
{
  Serial.print("Reading sensor at address:\t0x"); Serial.println(address, HEX);
}

Notes
1)
Only make variables global if you need to. The getTemp uses two static variables to remember values; they are similar to global variables but can not be accessed from any other function. currentTime is global as it will be used by multiple functions in future).
2)
You have to write (copy the existing) readDevice(). I don't have experience with I2C so I leave that to you.
3)
To make the code easier to maintain, the size of the temperature array is based on the size of the address array; this is done at compile time. The same principle is used in the for loop when reading the sensors. So when you change the number of sensors, the code will automatically adjust.

Note that the type of the address array has changed from integer to byte for this to work (will require a change if you want to keep it integers).

As DrDiettrich indicated, it's better to use values instead of toggling the state variable.

@srerretje

Thwnks a lot for your explanations and the code. This shows how to handle this kind of stuff. As explained, I'm a beginner so I do not have so many commands in use. For instance "sizeof" is completely new for me. This is a great command because it makes it max flexible.

Ok, I know my "Beginners project" is definetely not small... but I start with only two things

  • handle a pump
  • measure temperatur

To get experience with "multi tasking" on arduino one need minimum two modules. As I wrote, the state machine jumps from module to module based on mTempState and millis.

You are using st_temp_delay and st_temp_read to jump into a case. This I don't understand. Whats the reason of to use select case instaed of skiping the whole module if the mills are not correct for the next read operation?

Howsoever, you gave new Ideas. Thank you very much for this!!!!

I'll check my module, data types and so on and do my next lessons.

I'll keep you up to date. Great ideas...

Best regards

Guido

I''ve simply reworked your getTemp() code :wink: Before I can say something sensible, please explain what you mean by 'module'.

Sterretje,

ok, as explained I'm a noob. So I'm not familiar with the correct wording.

An project is stored in an ino file. Correct?

In an ino file are functions, sub functions, one setup area, one loop area.

I think my "modules" are functions

In the loop area I "call" sequentilly a bunch of functions, each function check at first current millis against previous millis+ functiontimegap. That allows to exit directly if the functiontimegap is not reached.

I think this is a kind of state machine...

I call it module because I try to program it that way, that I can copy it into a new project an reuse it more or less directly. I think a professional programmer calls it simply function.

By the way. I implement the temperature process based on your idea in my project. It runs nearly direct and looped through the six addresses and shows six temperatures for minutes. But if I restarted the uno, it stops after the I2c_start command and den hung...

I'll try to figure out the problem. Curious.

Thanks again.

Guido

Don't worry, after 30 years programming experience, I still don't know the difference between a definition and a declaration in C; it's only a problem for me if I try to explain something and I probably get it wrong.

Your program code is stored in one or more files; in the case of the Arduino IDE, the first file that you encounter is the ino file. And each file usually contains functions. So you do understand the basics there.

And setup() and loop() are the first two functions that you encounter.

They are called from another function that the Arduino IDE hides from you. Every C / C++ program starts with a function called main(). main() calls setup() once and in an endless loop it calls loop(). It looks something like

void main()
{
  // call setup once
  setup();
  // call loop; once it's finished call it again
  while(true)
  {
    loop();
  }
}

The appproach you are describing (from my point of view) is organizing the code in functions that perform a certain task and calling those functions in sequence; I think that those functions is what you refer to as 'modules'. This is a good approach as it keeps your code neat and tidy.

I have never considered that calling a few functions in sequence can be seen as a statemachine. In theory one can when I think about it. I think that this is where I was confused with your use of the term statemachine. In my dictionary, a statemachine is something different.

In a statemachine, one has a number of functionalities that do something; e.g switch a LED on, switch off a LED, sound a buzzer. Those are called in sequence but the sequence is determined by 'external' factors like e.g. a button press or temperature that is not within limits or by 'internal' factors like a delay or the result of a calculation.

So I did not consider this a statemachine

void loop()
{
  // state 1
  getTemp();
  // state 2
  displayTemp();
  // state 3
  runPump();

  // and the next time loop is called it goes back to state 1
}

But I do consider this a statemachine

void loop()
{
  // state 1
  getTemp();
  // state 2
  displayTemp();
  // only go to state 3 if temperature is above 75
  if(temp >75)
  {
    // state 3
    runPump();
  }

  // and the next time loop is called it goes back to state 1
}

So what you are describing is a statemachine that basically is not affected by anything; it just does it things without caring what is going on outside it.

Because the fact that I was confused, I rewrote your getTemp() in a way that I would implement getTemp using a statemachine. Your getTemp() function can be in one of two states (delay and read). In the 'delay' state, it checks if a time has lapsed (an 'internal' factor).

It stays in that state till the time has lapsed. Staying in that state basically means

change nothing
return (go back) to loop()
So the next time that getTemp() is called the state is still 'delay' and it checks again if the delay has lapsed.

Once the time has lapsed, the state is changed to 'read' and further nothing is done and the function returns (goes back) to loop().

Now the next time that getTemp() is called, the state is 'read' and it will read the sensors. Once all sensors are read, the state is changed to 'delay' and the function returns (goes back) to loop.

As said, all this was done because I was confused about what you considered a statemachine :wink: But at least you learned something :slight_smile:

//Edit
I see that I forgot about the switch/case. It's nothing more than something like

if(currentState == ST_TEMP_DELAY)
{
  ...
  ...
}
else if(currentState == ST_TEMP_READ)
{
  ...
  ...
}

The only reason I use it is that I consider it tidier and slightly easier to maintain. Imagine you have 10 states; they will all have their own 'case entry' in the switch and it's immediately clear (for me) that they belong together. It only works for single values, not for a range of values. The below can not directly be done in a switch case.

if(temp > 75)
{
}
else if(temp < 0)
{
}
else if(temp < 25)
{
}

Sterretje,

thank you very much for your explanations! I appreciate.

You are right. You use logical order to switch a state.

1st read temperature, then display temperatue and if temperature reach a specific value switch the pump on.

State1 = get temperature then goto state 2

State2 = display temperature then compare to a temp level (>75°C) then goto state3 or skip...

State3 = pump on

For my project my first idea is to do things from time to time... Let me say I'll run different functions time related.

For instance:

  • Pump on every 60s for 3 drops

  • Reading temperature every 5s then update display and/or switch LED (YELLOW,GREEN,RED)

  • Writing temperature to logfile

-...

Howsoever, your explanations tell me, that I have to combine time and state. Earliest all temperatures are collected (read), it is time to update display and switch the LED.

Then go back to loop an check (by time or state) what function shall run next...

I like those exchange of thinking/meaning how to solve issues...

Thank you so much for your suggestions/ explanations.

The weekend is save... :slight_smile:

Br Guido