Module: GV_FSM::Templates

Included in:
FSM
Defined in:
lib/templates.rb

Constant Summary collapse

HEADER =

_ _ _ _ __ _ __

| | | | ____|  / \  |  _ \| ____|  _ \ 
| |_| |  _|   / _ \ | | | |  _| | |_) |
|  _  | |___ / ___ \| |_| | |___|  _ < 
|_| |_|_____/_/   \_\____/|_____|_| \_\
<<~EOHEADER
  /******************************************************************************
  Finite State Machine
  Project: <%= @project_name or @dotfile %>
  Description: <%= @description or "<none given>" %>
  
  Generated by gv_fsm ruby gem, see https://rubygems.org/gems/gv_fsm
  gv_fsm version <%= GV_FSM::VERSION %>
  Generation date: <%= Time.now %>
  Generated from: <%= @dotfile %>
  The finite state machine has:
    <%= @states.count %> states
    <%= transition_functions_list.select {|e| e != 'NULL'}.count %> transition functions
  <% if @prefix != '' %>
  Functions and types have been generated with prefix "<%= @prefix %>"
  <% end %>
  ******************************************************************************/

EOHEADER
HH =
<<~EOH
<% if !@ino then %>
#ifndef <%= File::basename(@cname).upcase %>_H
#define <%= File::basename(@cname).upcase %>_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
<% else -%>
#include <arduino.h>
<% end -%>

// State data object
// By default set to void; override this typedef or load the proper
// header if you need
typedef void <%= @prefix %>state_data_t;
<% if !@ino then -%>

// NOTHING SHALL BE CHANGED AFTER THIS LINE!
<% end -%>

// List of states
typedef enum {
<% @states.each_with_index do |s, i| -%>
  <%= @prefix.upcase %>STATE_<%= s[:id].upcase %><%= i == 0 ? " = 0" : "" %>,  
<% end -%>
  <%= @prefix.upcase %>NUM_STATES,
  <%= @prefix.upcase %>NO_CHANGE
} <%= @prefix %>state_t;

// State human-readable names
extern const char *<%= @prefix %>state_names[];

<% if transition_functions_list.count > 0 then -%>
// State function and state transition prototypes
typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
typedef void transition_func_t(<%= @prefix %>state_data_t *data);
<% else -%>
// State function prototype
typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
<%end -%>

// State functions
<% dest = destinations.dup -%>
<% @states.each do |s| -%>
<% stable = true if dest[s[:id]].include? s[:id] -%>
<% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} -%>
<% if dest[s[:id]].empty? or stable then
  dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
end -%>
// Function to be executed in state <%= s[:id] %>
// valid return states: <%= dest[s[:id]].join(", ") %>
<%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data);
<% end -%>


// List of state functions
extern state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES];


<% if transition_functions_list.count > 0 then -%>
// Transition functions
<% transition_functions_list.each do |t| -%>
<% next if t == "NULL" -%>
void <%= t %>(<%= @prefix %>state_data_t *data);
<% end -%>

// Table of transition functions
extern transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES];
<% else -%>
// No transition functions
<% end -%>

// state manager
<%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, <%= @prefix %>state_data_t *data);

<% if !@ino then -%>
#ifdef __cplusplus
}
#endif
#endif // <%= File::basename(@cname).upcase %>_H
<% end -%>
EOH
CC =
<<~EOC
<% if !@ino then -%>
<% if @syslog then -%>
<% log = :syslog %>
#include <syslog.h>
<% end -%>
<% else -%>
<% if @syslog then log = :ino end -%>
<% end -%>
#include "<%= File::basename(@cname) %>.h"

<% if sigint then %>// Install signal handler: 
// SIGINT requests a transition to state <%= self.sigint %>
#include <signal.h>
static int _exit_request = 0;
static void signal_handler(int signal) {
  if (signal == SIGINT) {
_exit_request = 1;<% if log == :syslog then %>
syslog(LOG_WARNING, "[FSM] SIGINT transition to <%= sigint %>");<% elsif log == :ino then %>
Serial.println("[FSM] SIGINT transition to <%= sigint %>");<% end %>
  }
}

<% end -%>
<% placeholder = "Your Code Here" -%>
// SEARCH FOR <%= placeholder %> FOR CODE INSERTION POINTS!

// GLOBALS
// State human-readable names
const char *<%= @prefix %>state_names[] = {<%= states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};

// List of state functions
<% fw = state_functions_list.max {|a, b| a.length <=> b.length}.length -%>
state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES] = {
<% @states.each do |s| -%>
  <%= (s[:function] + ',').ljust(fw+1) %> // in state <%= s[:id] %>
<% end -%>
};
<% if transition_functions_list.count > 0 then -%>

// Table of transition functions
transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES] = {
<% sl = states_list -%>
<% fw = transition_functions_list.max {|a, b| a.length <=> b.length}.length -%>
<% sw = [states_list, "states:"].flatten.max {|a, b| a.length <=> b.length}.length -%>
  /* <%= "states:".ljust(sw) %>     <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
<% transitions_map.each_with_index do |l, i| -%>
  /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>}, 
<% end -%>
};
<% else -%>
// No transition functions
<% end -%>

/*  ____  _        _       
 * / ___|| |_ __ _| |_ ___ 
 * \\___ \\| __/ _` | __/ _ \\
 *  ___) | || (_| | ||  __/
 * |____/ \\__\\__,_|\\__\\___|
 *                         
 *   __                  _   _                 
 *  / _|_   _ _ __   ___| |_(_) ___  _ __  ___ 
 * | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
 * |  _| |_| | | | | (__| |_| | (_) | | | \\__ \\
 * |_|  \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
 */                                             
<% dest = destinations.dup -%>
<% topo = self.topology -%>
<% @states.each do |s| -%>
<% stable = true if dest[s[:id]].include? s[:id] -%>
<% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} -%>
<% if dest[s[:id]].empty? or stable then
  dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
end %>
// Function to be executed in state <%= s[:id] %>
// valid return states: <%= dest[s[:id]].join(", ") %>
<% if sigint && stable && topo[:sources][0] != s[:id] then -%>
// SIGINT triggers an emergency transition to <%= self.sigint %>
<% end -%>
<%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data) {
  <%= @prefix %>state_t next_state = <%= dest[s[:id]].first -%>;
<% if sigint && topo[:sources][0] == s[:id] then -%>
  signal(SIGINT, signal_handler); 
  <% end -%>
<% if log == :syslog then -%>
  syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
<% elsif log == :ino then -%>
  Serial.println("[FSM] In state <%= s[:id] %>");
<% end -%>
  /* <%= placeholder %> */
  
  switch (next_state) {
<% dest[s[:id]].each  do |str| -%>
  case <%= str %>:
<% end -%>
break;
  default:
<% if log == :syslog then -%>
syslog(LOG_WARNING, "[FSM] Cannot pass from <%= s[:id] %> to %s, remaining in this state", <%= @prefix %>state_names[next_state]);
<% elsif log == :ino then -%>
Serial.print("[FSM] Cannot pass from <%= s[:id] %> to ");
Serial.print(<%= @prefix %>state_names[next_state]);
Serial.println(", remaining in this state");
<% end -%>
next_state = <%= @prefix.upcase %>NO_CHANGE;
  }
<% if sigint && stable && topo[:sources][0] != s[:id] then -%>
  // SIGINT transition override
  if (_exit_request) 
next_state = <%= (@prefix+"STATE_"+self.sigint ).upcase %>;
<% end %>
  return next_state;
}

<% end %>

<% if transition_functions_list.count > 0 then -%>
/*  _____                    _ _   _              
 * |_   _| __ __ _ _ __  ___(_) |_(_) ___  _ __   
 *   | || '__/ _` | '_ \\/ __| | __| |/ _ \\| '_ \\
 *   | || | | (_| | | | \\__ \\ | |_| | (_) | | | | 
 *   |_||_|  \\__,_|_| |_|___/_|\\__|_|\\___/|_| |_| 
 *                                                
 *   __                  _   _                 
 *  / _|_   _ _ __   ___| |_(_) ___  _ __  ___ 
 * | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
 * |  _| |_| | | | | (__| |_| | (_) | | | \\__ \\
 * |_|  \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
 */    
                                      
<% transition_functions_list.each do |t| -%>
<% next if t == "NULL" -%>
<% tpaths = transitions_paths[t] -%>
// This function is called in <%= tpaths.count %> transition<%= tpaths.count == 1 ? '' : 's' %>:
<% tpaths.each_with_index do |e, i| -%>
// <%= i+1 %>. from <%= e[:from] %> to <%= e[:to] %>
<% end -%>
void <%= t %>(<%= @prefix %>state_data_t *data) {
<% if log == :syslog then -%>
  syslog(LOG_INFO, "[FSM] State transition <%= t %>");
<% elsif log == :ino then -%>
  Serial.println("[FSM] State transition <%= t %>");
<% end -%>
  /* <%= placeholder %> */
}

<% end -%>
<% end -%>

/*  ____  _        _        
 * / ___|| |_ __ _| |_ ___  
 * \\___ \\| __/ _` | __/ _ \\
 *  ___) | || (_| | ||  __/ 
 * |____/ \\__\\__,_|\\__\\___| 
 *                          
 *                                              
 *  _ __ ___   __ _ _ __   __ _  __ _  ___ _ __ 
 * | '_ ` _ \\ / _` | '_ \\ / _` |/ _` |/ _ \\ '__|
 * | | | | | | (_| | | | | (_| | (_| |  __/ |   
 * |_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_|   
 *                              |___/           
 */

<%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, <%= @prefix %>state_data_t *data) {
  <%= @prefix %>state_t new_state = <%= @prefix %>state_table[cur_state](data);
  if (new_state == <%= @prefix.upcase %>NO_CHANGE) new_state = cur_state;
<% if transition_functions_list.count > 0 then %>
  transition_func_t *transition = <%= @prefix %>transition_table[cur_state][new_state];
  if (transition)
transition(data);
<% end %>
  return new_state;
};

<% if @ino then %>
/* Example usage:
<%= @prefix %>state_data_t data = {count: 1};

void loop() {
  static <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
  cur_state = <%= @prefix %>run_state(cur_state, &data);
}
*/
<% else %>
<% nsinks = topology[:sinks].count %>
#ifdef TEST_MAIN
#include <unistd.h>
int main() {
  <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_<%= @states.first[:id].upcase %>;
<% if @syslog then %>
  openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
  syslog(LOG_INFO, "Starting SM");
<% end %>
  do {
cur_state = <%= @prefix %>run_state(cur_state, NULL);
sleep(1);
<% if nsinks == 1 %>
  } while (cur_state != <%= @prefix.upcase %>STATE_<%= topology[:sinks][0].upcase %>);
  <%= @prefix %>run_state(cur_state, NULL);
<% else %>
  } while (1);
<% end %>
  return 0;
}
#endif
<% end %>
EOC
HPP =
<<~EOHPP
#ifndef <%= File::basename(@cname).upcase %>_HPP
#define <%= File::basename(@cname).upcase %>_HPP
#include <functional>
#include <iostream>
#include <map>
#include <string>
#include <tuple>
<% if @syslog then -%>
<% log = :syslog -%>
#include <syslog.h>
<% end -%>
<% if sigint then -%>
// Install signal handler: 
// SIGINT requests a transition to state <%= self.sigint %>
#include <csignal>
<% end %>

using namespace std::string_literals;
<% ns = @project_name || "FSM" -%>
namespace <%= ns %> {
static bool <%= self.sigint %>_requested = false;

// List of states
typedef enum {
<% @states.each do |s| -%>
  <%= @prefix.upcase %>STATE_<%= s[:id].upcase %>,
<% end -%>
  <%= @prefix.upcase %>NUM_STATES,
  <%= @prefix.upcase %>NO_CHANGE,
  <%= @prefix.upcase %>UNIMPLEMENTED
} <%= @prefix %>state_t;

// State human-readable names
std::map<<%= @prefix %>state_t, char const *> state_names = {
<% @states.each do |s| -%>
  {<%= @prefix.upcase %>STATE_<%= s[:id].upcase %>, "<%= s[:id].upcase %>"},
<% end -%>
  {<%= @prefix.upcase %>NUM_STATES, "NUM_STATES"},
  {<%= @prefix.upcase %>NO_CHANGE, "NO_CHANGE"},
  {<%= @prefix.upcase %>UNIMPLEMENTED, "UNIMPLEMENTED"}
};

// Custom state functions:
<% @states.each do |s| -%>
template<class T> 
<%= @prefix %>state_t <%= s[:function] %>(T &data);
<% end -%>

<% if transition_functions_list.count > 0 then -%>
// Custom transition functions:
<% transition_functions_list.each do |t| -%>
template<class T>
void <%= t %>(T &data);
<% end -%>
<% end -%>

// Finite State Machine class
template <typename DATA_T> 
class FiniteStateMachine {

// Function templates
using state_fun = std::function<<%= @prefix %>state_t(DATA_T &data)>;
using transition_fun = std::function<void(DATA_T &data)>;
using operation_fun = std::function<void(DATA_T &data)>;

private:
  std::pair<<%= @prefix %>state_t, <%= @prefix %>state_t> _state{<%= @prefix.upcase %>STATE_<%= states[0][:id].upcase %>, <%= @prefix.upcase %>STATE_<%= states[0][:id].upcase %>};
  std::map<<%= @prefix %>state_t, state_fun> _states;
  std::map<<%= @prefix %>state_t, std::map<<%= @prefix %>state_t, transition_fun>> _transitions;
  std::function<void()> _timing_func;
  DATA_T *_data;

public:

  FiniteStateMachine(DATA_T *data) : _data(data) {
    install_functions();
  }
  ~FiniteStateMachine(){};

  void set_timing_function(std::function<void()> timing_func) {
    _timing_func = timing_func;
  }

  void add_state(<%= @prefix %>state_t name, state_fun func) { _states[name] = func; }

  void add_transition(<%= @prefix %>state_t from, <%= @prefix %>state_t to, transition_fun func) {
    _transitions[from][to] = func;
  }

  inline <%= @prefix %>state_t state() { return _state.second; }
  inline std::string state_name() { return std::string(state_names[_state.second]); }
  inline <%= @prefix %>state_t prev_state() { return _state.first; }

  <%= @prefix %>state_t operator()(<%= @prefix %>state_t state) {
    if (_states.find(state) == _states.end()) {
      throw std::runtime_error("State not found: "s + state_names[state]);
    }
    return _states[state](*_data);
  }

  void operator()(<%= @prefix %>state_t from, <%= @prefix %>state_t to) {
    if (_transitions.find(from) != _transitions.end()) {
      if (_transitions[from].find(to) != _transitions[from].end()) {
        _transitions[from][to](*_data);
      }
    }
  }

  // Run the FSM from a given state
  void run(<%= @prefix %>state_t state, operation_fun operation = nullptr) {
    <%= ns %>::<%= self.sigint %>_requested = false;
    <%= @prefix %>state_t prev_state = state;
    _state.first = state;
    _state.second = state;
<% if sigint then -%>
    std::signal(SIGINT, [](int signum) {
<% if log == :syslog then -%>
      syslog(LOG_WARNING, "[FSM] SIGINT transition to <%= sigint %>");
<% end -%>
      <%= ns %>::<%= self.sigint %>_requested = true; 
    });
<% end -%>
    do {
      if (operation) {
        operation(*_data);
      }
      (*this)(_state.first, _state.second);
      _state.first = _state.second;
      _state.second = (*this)(_state.second);
      if (_timing_func) {
        _timing_func();
      }
    } while (_state.second != <%= @prefix.upcase %>STATE_<%= topology[:sinks][0].upcase %>);
    // Call the exit state once more:
    (*this)(<%= @prefix.upcase %>STATE_<%= topology[:sinks][0].upcase %>);
<% if sigint then -%>
    std::signal(SIGINT, SIG_DFL);
<% end -%>
  }

  // Run the FSM from the initial state
  void run(operation_fun operation = nullptr) { run(<%= @prefix.upcase %>STATE_<%= states[0][:id].upcase %>, operation); }

  // install state and transition functions
  void install_functions() {

    // State functions
<% dest = destinations.dup -%>
<% @states.each do |s| -%>
<% stable = true if dest[s[:id]].include? s[:id] -%>
<% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} -%>
<% if dest[s[:id]].empty? or stable then
    dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
end -%>
    add_state(<%= ns %>::<%= @prefix.upcase %>STATE_<%= s[:id].upcase %>, [](DATA_T &data) -> <%= ns %>::<%= @prefix %>state_t {
<% if log == :syslog then -%>
      syslog(LOG_INFO, "[FSM] In state <%= s[:id].upcase %>");
<% end -%>
      <%= ns %>::<%= @prefix %>state_t next_state = <%= @prefix%>do_<%= s[:id] %>(data);
    
      switch (next_state) {
      case <%= ns %>::<%= @prefix.upcase %>UNIMPLEMENTED:
        throw std::runtime_error("State function not fully implemented: "s + "<%= s[:id].upcase %>");
        break;
<% dest[s[:id]].each  do |str| -%>
      case <%= ns %>::<%= str %>:
<% end -%>
        break;
      default:
<% if log == :syslog then -%>
        syslog(LOG_WARNING, "[FSM] Cannot pass from <%= s[:id] %> to %s, remaining in this state", state_names[next_state]);
<% end -%>
        next_state = <%= ns %>::<%= @prefix.upcase %>NO_CHANGE;
      }
<% if sigint && stable && self.topology[:sources][0] != s[:id] then -%>
      // SIGINT transition override
      if (<%= self.sigint %>_requested) next_state = <%= (@prefix+"STATE_"+self.sigint ).upcase %>;
<% end -%>
      return next_state;
    });

<% end -%>

<% if transition_functions_list.count > 0 then -%>
    // Transition functions
<% transition_functions_list.each do |t| -%>
    add_transition(<%= @prefix.upcase %>STATE_<%= transitions_paths[t][0][:from].upcase %>, <%= @prefix.upcase %>STATE_<%= transitions_paths[t][0][:to].upcase %>, [](DATA_T &data) {
<% if log == :syslog then -%>
      syslog(LOG_INFO, "[FSM] State transition <%= t %>");
<% end -%>
      <%= t %>(data);
    });

<% end -%>
<% end -%>
  }

}; // class FiniteStateMachine

}; // namespace <%= @project_name || "FSM" %>

#endif // <%= File::basename(@cname).upcase %>_HPP

EOHPP
CPP =
<<~EOCPP
<% if @syslog then -%>
<% log = :syslog -%>
#include <syslog.h>
<% end -%>
#include "<%= File::basename(@cname) %>.hpp"
    
using namespace std;
<% ns = @project_name || "FSM" -%>
    
<% placeholder = "Your Code Here" %>
// SEARCH FOR <%= placeholder %> FOR CODE INSERTION POINTS!


namespace <%= @project_name || "FSM" %> {

/*  ____  _        _       
 * / ___|| |_ __ _| |_ ___ 
 * \\___ \\| __/ _` | __/ _ \\
 *  ___) | || (_| | ||  __/
 * |____/ \\__\\__,_|\\__\\___|
 *                         
 *   __                  _   _                 
 *  / _|_   _ _ __   ___| |_(_) ___  _ __  ___ 
 * | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
 * |  _| |_| | | | | (__| |_| | (_) | | | \\__ \\
 * |_|  \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
 */                                             
<% dest = destinations.dup -%>
<% topo = self.topology -%>
<% @states.each do |s| -%>
<% stable = true if dest[s[:id]].include? s[:id] -%>
<% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} -%>
<% if dest[s[:id]].empty? or stable then
  dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
end %>
// Function to be executed in state STATE_<%= s[:id].upcase %>
// valid return states: <%= dest[s[:id]].join(", ") %>
<% if sigint && stable && topo[:sources][0] != s[:id] then -%>
// SIGINT triggers an emergency transition to STATE_<%= self.sigint.upcase %>
<% end -%>
template<class T> 
<%= @prefix %>state_t <%= s[:function] %>(T &data) {
  <%= @prefix %>state_t next_state = <%= ns %>::<%= @prefix.upcase %>UNIMPLEMENTED;
  /* <%= placeholder %> */
  
  return next_state;
}
<% end -%>


<% if transition_functions_list.count > 0 then -%>
/*  _____                    _ _   _              
 * |_   _| __ __ _ _ __  ___(_) |_(_) ___  _ __   
 *   | || '__/ _` | '_ \\/ __| | __| |/ _ \\| '_ \\
 *   | || | | (_| | | | \\__ \\ | |_| | (_) | | | | 
 *   |_||_|  \\__,_|_| |_|___/_|\\__|_|\\___/|_| |_| 
 *                                                
 *   __                  _   _                 
 *  / _|_   _ _ __   ___| |_(_) ___  _ __  ___ 
 * | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
 * |  _| |_| | | | | (__| |_| | (_) | | | \\__ \\
 * |_|  \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
 */                                              

<% transition_functions_list.each do |t| -%>
<% next if t == "NULL" -%>
<% tpaths = transitions_paths[t] -%>
// This function is called in <%= tpaths.count %> transition<%= tpaths.count == 1 ? '' : 's' %>:
<% tpaths.each_with_index do |e, i| -%>
// <%= i+1 %>. from <%= e[:from] %> to <%= e[:to] %>
<% end -%>
template<class T>
void <%= t %>(T &data) {
  /* <%= placeholder %> */
}

<% end -%>
<% end -%>

}; // namespace <%= @project_name || "FSM" %>


<% nsinks = topology[:sinks].count -%>
// Example usage:
#ifdef TEST_MAIN
#include <unistd.h>
#include <thread>

struct Data {
  int count;
};

int main() {
  Data data = {1};
  auto fsm = <%= ns %>::FiniteStateMachine(&data);
  fsm.set_timing_function([]() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
  });
  fsm.run([&](Data &s) {
    std::cout << "State: " << fsm.state() << " data: " << s.count << std::endl;
  });
  return 0;
}
#endif // TEST_MAIN
EOCPP