Question regarding enum and ++ or -- operators

Hi All

End of a long day and my brain is fried. Slowly building my spa controller. I'm up to the last state of creating a menu to adjust the various settings. I've read a lot about menus and various menu libraries but I thought it would be easy to create my own structure for a simple menu. (I'm thinking I might be wrong)

Question:
can you change the enum states using some form of ++ or -- and then have a switch case that has the appropriate lcd.print items (a select button and an if statement would send them to actually editing the values). reading Enumeration declaration - cppreference.com suggests I can manipulate things via the number of the state, but I'm new to this C++ stuff

ie if sState = SetComfortTimes therefore sState = 1 can I use a sState++ to move it to 2 which is SetTemps?

if I have

// States for Settings
//                    0            1           2         3         4        5     
enum SSTATES {SetDateTime, SetComfortTimes, SetTemps, SetDelays};

SSTATES sState;

can I do something like (this is one of many attempts at SSTATES, sState SSTATES.sState SSTATE[sState] I've tried a few but get errors, this ones produces "no match for 'operator++' (operand type is 'SSTATES')." or dor ++ and -- operators only work on bytes and ints ?

         if(btnUP.wasPressed())  // using JC_Button.h
                 {
                       backlightMillis = currentMillis;
                       if (sState <= 3) sState ++;

and a switch case

           // Menu Switch Case

              switch (sState)
                  {
                  //********************* 0
                     case SetDateTime:
                      {
                      lcd.setCursor(0,0);
                      lcd.print(" Set Date/Time");
                      }
                      break;

                     case SetComfortTimes:
                     {
                     lcd.setCursor(0,0);
                     lcd.print("Set Comfot Times");
                     }
                     break;

                    case SetTemps:
                    {
                    lcd.setCursor(0,0);
                    lcd.print("Set Temps     ");
                    }
                    break;

                    case SetDelays:
                    {
                    lcd.setCursor(0,0);
                    lcd.print("Edit Delay Times");
                    }
                    break;
                  }// end sState

Cheers
Al

If sState is SetDelays and sState is incremented, what is the expected outcome?

You cannot increment sState because it is the name of an enum. One of the major advantages of using an enum is that it allows you to give names to states without knowing the underlying value of the variables, but you are subverting that mechanism by trying to increment a name. Even if you could do that you would not know the name of the current state.

You could allocate the current state to a variable and increment that but you would still not know the name of the state

Do you use the named states elsewhere in the program

Hi Allan,
I don't use enum because it hides the relationship between the state and what it does. I just use the number for each state and put a comment next to it to remind me what it does:

switch (state) {
  case 0:        // Top menu
    // Do top menu stuff
    break;
  case 1:        // Next menu
    // Do next menu stuff
    break;
// you get the idea

PerryBebbington:
I don't use enum because it hides the relationship between the state and what it does

I use enum because it makes clear the relationship between the state and what it does :wink:

switch (state) {
  case Top_menu:
    // Do top menu stuff
    break;
  case Next_menu:
    // Do next menu stuff
    break;
// you get the idea

Thanks UKHB

No the states were particular to that part of the program, compiling with states that had the same name returned errors. ie mState {editDateTime}; confilicted with sState {editDateTime}; I guess I assumed the enum was like an array or typedef that it created its own object within its name. My bad, still learning

My assumption (wrongly) was that if you Serial.print(sState) it returns the number of the state you are in rather than the name, I assumed you could reverse that to manipulate the number to change the state. Seemed rational at the time :slight_smile:

Tomorrow is another day I will re-look at how I want to achieve a basic menu. There is a billion posts on how to create a menu, I'm sure one will sink in. The switch case part I understand 0-3 is not going to be hard to work out with a few comments to remind me.

Cheers
Al

I use enum because it makes clear the relationship between the state and what it does :wink:

Which of course illustrates that there is no one 'right' way to do things, we all have our preferences!

can you change the enum states using some form of ++ or -- and then have a switch case that has the appropriate

if you have already a switch/case for each state, you could just define the next state instead ++ according to the next enum. E.g. sState = SetComfortTimes;

If you just want to "increase" why not writing a small helper function like increaseState() and do all the magic in this function. At least you could add the handling for your last state also...

So you are saying in a switch machine

If In state y “upbutton” go to state x
If in state x “dnbutton” go to state y

Then just add the menu text in each state the “select” button moved to another area

If in state y “select” go to state A
If in state x “select” go to state B

Not sure how to do that as a Helper function.

I don't use enum because it hides the relationship between the state and what it does.

I can't believe that you wrote that. Give the members of the enum meaningful names and the switch/state becomes almost self documenting

Not sure how to do that as a Helper function.

either a switch case or an if else ... doesn't matter so much. I guess if else are less lines of code and the compiler will do the best anyway.

untested

void increaseState()
{
  if (sState == SetDateTime) sState=SetComfortTimes;  
  else if (sState == SetComfortTimes) sState = SetTemps;
  else if (sState == SetTemps) sState = SetDelay;

  else sState = SetDelay;
}

I can't believe that you wrote that.

Hi Bob!
I guess it shows we have different approaches for our own reasons. Works for me, obviously doesn't work for you. I guess it is partly habit; I first used state machines in Z80 assembly language, no switch, no enum; had to write my own. Got used to associating a number with some activity without the benefit of a name associated with it, never felt the need to change when I learned C and learned of the existence of switch and enum.
Using enum has its advantages, I can see that, but if Allan had not use it then this discussion would not have started as there wouldn't be a problem.

Allan has to decide what's best for him.

when we are discussing the usage of enum in general, why not even use an enum class for this state machine to keep the namespace tidy, just as an idea :wink:

I understand what you say and it is everybody's choice as to how they write code, but I do think that it behoves us to illustrate the advantages of doing things in ways that seem (to me, at least) more obvious and maintainable.

If you use an enum with sensible names then you can, of course, just do

if (currentState != waiting)
  {
    //do something
  }

whereas if you use numbers for the states then you do

if (currentState != 5)  //need to refer to declarations to remind yourself what state 5 is
  {
    //do something
  }

Of course, you don't have to use enums as they are really just const variables and you can used those instead

Hello Bob,
For things that are going to be tested using if() I generally have meaningful names, often these are flags for things. Switch() is best for things that go in sequence (0, 1, 2, 3...) and my preference is to see the numbers rather than have names, but I totally understand not everyone thinks the same. I think it perfectly reasonable to tell people there is more than one way to do things and let them decide what works best for them.

Anyway, thanks, because you have got me thinking about something that is habit going back many years.

Thanks for the help yesterday. I spent some time on the program today but it was a sunny Autumn day so ended up on the end of a chainsaw for most of it. But I have moved further along

Where I got up to with your help is something that compiles but doesn't work :slight_smile: but I sort of understand why.
EDIT = The information you gave has allowed me to build a menu that cycles through nicely
Here is my program layout, I have done this as I want to split the program into different files (don't know how to do that yet, everything is currently in one big sketch of 2230 lines)

Currently I can go from the Main State Machine to the Settings State Machine. The main state machine calls the Settings statemachine in a loop. What i haven't got working is the next step if from settings I call Edit Comfort. Because I am executing it from a button it becomes false when the button is release, then of course the EditComfort State Machine should call the specific Comfort Time to edit in its own state machine.

So
Loop -> MainStateMachine -> Settings -> Settings State Machine --broken--> edit comfort times --broken--> edit Comfort Time X

So I either have to add in another set of states in settings that calls the editcomfort statemachine OR use flags in the setting loop to say if flag editcomfortflag = true (set by confirm button) go to that state machine for further stuff to do and keep doing that.

UKHB alluded to it with if(currentState != Waiting) {do stuff}

So the question is would I create states within the various layouts called "waiting" and in the state possibly have so if statements to test some flags to control which state machine should be operating. ?

I have read lots of the posts on here about state machines but they are all relatively simple examples with flat structures rather than compartmentalized.

next learning curve is EEPROM to save the settings between reboots.

Attached is my full program out of interest - still has a lot of work to go especially removing repeated code into a common functions.

//Main States MainStateMachine(); Called from void loop 
//                0         1             2                3          4        5             6           7          8             9            10           11          12
enum MSTATES {StartUp, CheckSettings, ConfirmSettings, CheckTimes, Economy,   StandBy,   InUse,    ExitDelay,  Settings,    EditDateTime,}; 
MSTATES mState = StartUp;
MSTATES lastMstate;

// States for Settings - SettingsStateMachine()
//                    0            1           2         3
enum SSTATES {SetDateTime, SetComfortTimes, SetTemps, SetDelays };
SSTATES sState;
SSTATES lastSstate;

//States to Edit Comfort Times - EditComfortTimes() // problem here as it is called by button press
//                  1           2          3          4           5         6
enum ESTATES   { EditComf1, EditComf2, EditComf3, EditComf4, EditComf5, EditComf6 };
ESTATES eState;


//States to Edit Temps and Delays  - EditTempsDelays()
//                    1                   2              3                 4       5             6               7
enum RSTATES  { EditComfTempMax, EditComfTempMin, EditEcoTempMax, EditEcoTempMin,  UseTime,  EditExitDelay,  EditLDRlevel };
RSTATES rState;
RSTATES lastRstate;

// Edit time and Date - EditTimeStateMachine()
//                0      1       2         3         4        5       6           7         8          9       10
enum TSTATES {UpDate, EditTime, EditHRS, EditMIN, EditSEC, EditDate, EditYRS, EditMTHS, EditDAYS };  // step through state machine with Update returning to last mState
TSTATES tState;
TSTATES lastTstate;

// Edit Comfort Times States - ComfortTimeEdit(byte Comfortx) - edits specific comfort time to save back to typedef
//                0         1            2         3         4            5               6           7         8          9       10
enum CSTATES {Select, EditComfTime, EditOnHRS, EditOnMIN, EditOffHRS, EditOffMIN,   EditFREQ, UpDateComfort };
CSTATES cState;
CSTATES lastCstate;

//****************************************************************************

spa_controller_V3.ino (65.3 KB)

So the question is would I create states within the various layouts called "waiting" and in the state possibly have so if statements to test some flags to control which state machine should be operating. ?

if you have identified a new state, as "waiting" why not add this additional state?

I suggest you take a big piece of paper and start documenting your menu structure before adding more lines of code. It hasn't to be a full UML statechart, but some documentation will give you a good starting point not to get lost in your menu - and it might make support from others easier.

by the way this is not what you will get:

//                  1           2          3          4           5         6
enum ESTATES   { EditComf1, EditComf2, EditComf3, EditComf4, EditComf5, EditComf6 };

either adopt your comment starting with 0 or at least start the first value with 1

//                  1           2          3          4           5         6
enum ESTATES   { EditComf1 = 1, EditComf2, EditComf3, EditComf4, EditComf5, EditComf6 };

in general you should get used to that c++ starts indexing with 0.

Thanks noiasca

I do have a big note book of state machine diagrams but you are 100% right. I think I am blending menu states and state machine "states" and i need to map this all out.

Given where I started a month ago I'm not disappointed but it is hard learning a very complex subject via google and guesswork rather than a formal educational setting. Tough times :slight_smile:

I have seen this before "{ EditComf1 = 1" does this start the enum count from 1 rather than 0? can you use that elsewhere?

The comments of 1 2 3 were probably more for my benefit rather than a critique but starting from 0 makes sense given the c++ language starts at 0 (any probably most programming languages), I will change my ways :slight_smile:

Cheers
Al

Starting from 0 makes sense given the c++ language starts at 0 (any probably most programming languages)

Everything starts at zero in software because fundamentally counting starts at zero. For example if you have 8 bits there are 256 possible states, the first being zero (00000000), the last being 255 (11111111).

I think mostly I remember to start at zero for things like arrays etc. The enums were a bit of a slip as a suppose the point of enums is to use plain English for states rather numbers. I will correct though.