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
  #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 *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 %>
  #endif
  <% 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 *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", state_names[next_state]);
  <% elsif log == :ino then %>
        Serial.print("[FSM] Cannot pass from <%= s[:id] %> to ");
        Serial.print(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 == <%= @prefix.upcase %>NO_CHANGE ? cur_state : 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