tinkerspy / Automaton

Reactive State Machine Framework for Arduino
https://github.com/tinkerspy/Automaton/wiki
MIT License
369 stars 63 forks source link

2 timers in 1 state machine, only timer with smallest duration works #76

Closed Tsjompie closed 4 years ago

Tsjompie commented 4 years ago

LS,

I added 2 timers in a state machine. When one of the timers expires it looks like both the timers are reset. This results in the situation the timer with the smallest duration is executed, the other one seems to get a reset and isn't executed at all.

What do I oversee here? How to make both times work using a single state machine?

Thanks for the heads-up.

Tsjompie

I got the following h file:

#pragma once

#include <Automaton.h>

class Atm_management: public Machine {

 public:
  enum { IDLE, OFF, ON, DISPLAYUPDATE, DEBUGVALUES }; // STATES
  enum { EVT_OFF, EVT_ON, EVT_INPUT, EVT_TMRDISPLAY, EVT_TMRVALUES, ELSE }; // EVENTS
  Atm_management( void ) : Machine() {};
  Atm_management& begin( bool initialState = false );
  Atm_management& trace( Stream & stream );
  Atm_management& trigger( int event );
  int state( void );
  Atm_management& off( void );
  Atm_management& on( void );
  Atm_management& input( void );
  Atm_management& tmrdisplay( void );
  Atm_management& tmrvalues( void );

  Atm_management& onChange( bool status, atm_cb_push_t callback, int idx = 0 );
  Atm_management& onChange( bool status, Machine& machine, int event = 0 );
  Atm_management& onChange( atm_cb_push_t callback, int idx = 0 );
  Atm_management& onChange( Machine& machine, int event = 0 );
  Atm_management& onInput( bool status, atm_cb_push_t callback, int idx = 0 );
  Atm_management& onInput( bool status, Machine& machine, int event = 0 );
  Atm_management& IF( Machine& machine, char relOp = '>', int match = 0 );
  Atm_management& IF( atm_cb_pull_t callback, int idx = 0 );
  Atm_management& AND( Machine& machine, char relOp = '>', int match = 0 );
  Atm_management& AND( atm_cb_pull_t callback, int idx = 0 );
  Atm_management& OR( Machine& machine, char relOp = '>', int match = 0 );
  Atm_management& OR( atm_cb_pull_t callback, int idx = 0 );
  Atm_management& XOR( Machine& machine, char relOp = '>', int match = 0 );
  Atm_management& XOR( atm_cb_pull_t callback, int idx = 0 );
  Atm_management& led( int led, bool activeLow = false );

 private:
  enum { ENT_OFF, ENT_ON, ENT_DISPLAYUPDATE, ENT_DEBUGVALUES }; // ACTIONS
  int event( int id ); 
  void action( int id ); 

  state_t last_state;
  // atm_connector connector[_CONN_SIZE_];
  // atm_connector operand[ATM_CONDITION_OPERAND_MAX];
  int8_t indicator;
  bool indicatorActiveLow;
  const static char relOps[];

  atm_timer_millis tmrDisplay, tmrValues;
  // atm_counter counter;

  Atm_management& OP( char logOp, Machine& machine, char relOp, int match );
  Atm_management& OP( char logOp, atm_cb_pull_t callback, int idx );
  bool eval_one( atm_connector& connector );
  bool eval_all();
};

and this cpp file:

#include "Atm_management.h"
#include "management_functions.h"

/* Add optional parameters for the state machine to begin()
 * Add extra initialization code
 */

Atm_management& Atm_management::begin( bool initialState /* = false */) {
  // clang-format off
  const static state_t state_table[] PROGMEM = {
    /*                           ON_ENTER  ON_LOOP  ON_EXIT  EVT_OFF  EVT_ON  EVT_INPUT  EVT_TMRDISPLAY  EVT_TMRVALUES  ELSE */
    /*          IDLE */                -1,      -1,      -1,      -1,     -1,        -1,  DISPLAYUPDATE,   DEBUGVALUES,   -1,
    /*           OFF */           ENT_OFF,      -1,      -1,      -1,     ON,       OFF,             -1,            -1,   -1,
    /*            ON */            ENT_ON,      -1,      -1,     OFF,     -1,        ON,             -1,            -1,   -1,
    /* DISPLAYUPDATE */ ENT_DISPLAYUPDATE,      -1,      -1,      -1,     -1,        -1,             IDLE,            -1, -1,
    /*   DEBUGVALUES */   ENT_DEBUGVALUES,      -1,      -1,      -1,     -1,        -1,             -1,            IDLE, -1,
  };
  // clang-format on
  Machine::begin( state_table, ELSE );
  tmrDisplay.set(700);
  tmrValues.set(800);
  // atm_counter counter;

  return *this;          
}

/* Add C++ code for each internally handled event (input) 
 * The code must return 1 to trigger the event
 */

int Atm_management::event( int id ) {
  switch ( id ) {
    case EVT_OFF:
      return 0;
    case EVT_ON:
      return 0;
    case EVT_INPUT:
      return 0;
    case EVT_TMRDISPLAY:
    // Serial.println("EVT_TMRDISPLAY");
      return tmrDisplay.expired(this);
    case EVT_TMRVALUES:
    // Serial.println("EVT_TMRVALUES");
      return tmrValues.expired(this);
  }
  return 0;
}

/* Add C++ code for each action
 * This generates the 'output' for the state machine
 */

void Atm_management::action( int id ) {
  switch ( id ) {
    case ENT_OFF:
      return;
    case ENT_ON:
      return;
    case ENT_DISPLAYUPDATE:
      Serial.println("DIsplayTimer expired");
      displayUpdate();
      return;
    case ENT_DEBUGVALUES:
      Serial.println("ENT_DEBUGVALUES expired");
      showDebugValues();

      return;
  }
}

/* Optionally override the default trigger() method
 * Control how your machine processes triggers
 */

Atm_management& Atm_management::trigger( int event ) {
  Machine::trigger( event );
  return *this;
}

/* Optionally override the default state() method
 * Control what the machine returns when another process requests its state
 */

int Atm_management::state( void ) {
  return Machine::state();
}

/* Nothing customizable below this line                          
 ************************************************************************************************
*/

/* Public event methods
 *
 */

Atm_management& Atm_management::off() {
  trigger( EVT_OFF );
  return *this;
}

Atm_management& Atm_management::on() {
  trigger( EVT_ON );
  return *this;
}

Atm_management& Atm_management::input() {
  trigger( EVT_INPUT );
  return *this;
}

Atm_management& Atm_management::tmrdisplay() {
  trigger( EVT_TMRDISPLAY );
  return *this;
}

Atm_management& Atm_management::tmrvalues() {
  trigger( EVT_TMRVALUES );
  return *this;
}

/* State trace method
 * Sets the symbol table and the default logging method for serial monitoring
 */

Atm_management& Atm_management::trace( Stream & stream ) {
  Machine::setTrace( &stream, atm_serial_debug::trace,
    "MANAGEMENT\0EVT_OFF\0EVT_ON\0EVT_INPUT\0EVT_TMRDISPLAY\0EVT_TMRVALUES\0ELSE\0IDLE\0OFF\0ON\0DISPLAYUPDATE\0DEBUGVALUES" );
  return *this;
}

The ino has this:

leveller.begin()
  .trace(Serial);

The debugger shows this:

981353 Switch MANAGEMENT@D4D from DISPLAYUPDATE to IDLE on EVT_TMRDISPLAY (1125 cycles in 700 ms)
982057 Switch MANAGEMENT@D4D from IDLE to DISPLAYUPDATE on EVT_TMRDISPLAY (1106 cycles in 701 ms)
DIsplayTimer expired
displayUpdate
982761 Switch MANAGEMENT@D4D from DISPLAYUPDATE to IDLE on EVT_TMRDISPLAY (1120 cycles in 700 ms)
983465 Switch MANAGEMENT@D4D from IDLE to DISPLAYUPDATE on EVT_TMRDISPLAY (1103 cycles in 700 ms)
DIsplayTimer expired
displayUpdate
984169 Switch MANAGEMENT@D4D from DISPLAYUPDATE to IDLE on EVT_TMRDISPLAY (1114 cycles in 700 ms)
984872 Switch MANAGEMENT@D4D from IDLE to DISPLAYUPDATE on EVT_TMRDISPLAY (1109 cycles in 700 ms)
DIsplayTimer expired
displayUpdate
985578 Switch MANAGEMENT@D4D from DISPLAYUPDATE to IDLE on EVT_TMRDISPLAY (1114 cycles in 701 ms)
986283 Switch MANAGEMENT@D4D from IDLE to DISPLAYUPDATE on EVT_TMRDISPLAY (1110 cycles in 700 ms)
DIsplayTimer expired
displayUpdate
986986 Switch MANAGEMENT@D4D from DISPLAYUPDATE to IDLE on EVT_TMRDISPLAY (1115 cycles in 700 ms)
987689 Switch MANAGEMENT@D4D from IDLE to DISPLAYUPDATE on EVT_TMRDISPLAY (1105 cycles in 700 ms)
DIsplayTimer expired
displayUpdate
988394 Switch MANAGEMENT@D4D from DISPLAYUPDATE to IDLE on EVT_TMRDISPLAY (1120 cycles in 701 ms)
989099 Switch MANAGEMENT@D4D from IDLE to DISPLAYUPDATE on EVT_TMRDISPLAY (1108 cycles in 700 ms)
DIsplayTimer expired
displayUpdate

Thanks again! Any push towards a solution is highly appreciated.

tinkerspy commented 4 years ago

Timers are reset upon every state change. So in this case in IDLE it seems the first timer expires, the display is updated and the state jumps back to IDLE. Both timers are reset, then the first timer will again expire first. The second never becomes active.

To solve this you could use two idle states. One with a 700 timer and one with a 100 timer.

IDLE1 switches to IDLE2 on timer(700) IDLE2 switches to IDLE1 on timer(100)

ENT_IDLE2 calls updatedisplay ENT_IDLE1 calls updatevalues

Remember that a timer is always relative to the state, it counts from the last state change.

Tsjompie commented 4 years ago

THANKS!

Tsjompie commented 4 years ago

Hi @tinkerspy: your suggestion implies the timers run consecutive. Based on the above example, the updatedisplay runs every 800 ms as that state also depends on the expiry of the 100 ms timer.

Just finish the SM dealing with this, but: Would there also be a way to make those timers independent from each other?

Just asking.

Tsjompie

tinkerspy commented 4 years ago

The timers objects are always relative to the last state change, but there's no reason you couldn't store your own timestamps and compare them to millis().

Something like this?

case EVT_TMRDISPLAY:
  if ( millis() - last_tmr_display > tmr_display_interval ) {
   last_tmr_display = millis();
   return 1;
  }
  return 0;
case EVT_TMRVALUES:
  if ( millis() - last_tmr_values > tmr_values_interval ) {
   last_tmr_values = millis();
   return 1;
  }
  return 0;
Tsjompie commented 4 years ago

💪