gv_fsm 0.2.1 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2896e7f9617c0ed1a9917e78cd947d1da5ceef2d8554727a4db268a7f70e6229
4
- data.tar.gz: 63d897002c102d5291ecbfd7b4c854f8c9fc029e01155ba573d13e2043621afb
3
+ metadata.gz: 61d5d6c1ef593ce7d516c48ab905c210d3005081fdd241b47298c55c5340236c
4
+ data.tar.gz: d8bc69220e149140bd150ad3890dbf11e33651352bf22df483b7941c9a9911c7
5
5
  SHA512:
6
- metadata.gz: ae5be7ee072d06983c139320227c6b234d703b2556071df085cb3f6a3365d3d1c6a8cf38f9f6daac0b866ed5b77c4726933eee69eda7aa8d9ffe17b9f2c2ca0b
7
- data.tar.gz: bd1129ccf730d930b42a8ba53f83eb2adeb6d66368ebd9aab6466c69e2b951d6b461d0e04579254153d2c753c390f16f023806040615e6ac52d53f3b2f60c71a
6
+ metadata.gz: c92f9ebb2275bfb8fd4fa23060cc22c8d34fe2e7eb219e9833f60517417ca11a420758c99fb0bee82cc58795f184a44d230bf03ae947072e1a72271bad34b2c9
7
+ data.tar.gz: 7a2898d7f153fe7c80a9eb046c54bd4076ffa15bf69987f069d94a70708a17085641fc7dff765fd7816df839f469ce363e7793122e5d09538e882667d5e83650
data/bin/gv_fsm CHANGED
@@ -6,7 +6,7 @@ require 'optparse'
6
6
  sm = GV_FSM::FSM.new
7
7
 
8
8
  options = {header: true, source: true}
9
- OptionParser.new do |parser|
9
+ op = OptionParser.new do |parser|
10
10
  parser.banner =<<~EOB
11
11
  Graphviz to Finite State Machine generator
12
12
  Version: #{GV_FSM::VERSION}
@@ -28,7 +28,7 @@ OptionParser.new do |parser|
28
28
  sm.cname = f
29
29
  end
30
30
 
31
- parser.on("-h", "--header-only", "Only generate header file") do
31
+ parser.on("-e", "--header-only", "Only generate header file") do
32
32
  options[:source] = false
33
33
  end
34
34
 
@@ -40,21 +40,47 @@ OptionParser.new do |parser|
40
40
  sm.prefix = p
41
41
  end
42
42
 
43
- end.parse!
43
+ parser.on("-i", "--ino", "Generate a single .ino file (for Arduino)") do
44
+ sm.ino = true
45
+ end
44
46
 
45
- raise ArgumentError, "I need the path to a Graphviz file!" unless ARGV[0]
46
- raise ArgumentError, "#{ARGV[0]} does not look like a Graphviz file!" unless File.extname(ARGV[0]) == ".dot"
47
+ parser.on("-l", "--no-log", "Omit log calls in stub functions") do
48
+ sm.syslog = false
49
+ end
47
50
 
48
- sm.parse(ARGV[0])
51
+ parser.on("-h", "--help", "Prints this help") do
52
+ puts parser
53
+ exit
54
+ end
55
+
56
+ end
57
+
58
+ op.parse!
59
+
60
+ unless ARGV[0]
61
+ STDERR.puts "ERROR: I need the path to a Graphviz file!\n\n"
62
+ STDERR.puts op
63
+ exit 1
64
+ end
65
+ if !File.extname(ARGV[0]) == ".dot" or !File.exist? ARGV[0] then
66
+ STDERR.puts "ERROR: #{ARGV[0]} does not look like a Graphviz file!\n\n"
67
+ STDERR.puts op
68
+ exit 2
69
+ end
70
+
71
+ unless sm.parse(ARGV[0]) then
72
+ puts "Error parsing the file #{ARGV[0]}: #{sm.error}"
73
+ exit 3
74
+ end
49
75
 
50
76
  puts "Parsed #{sm.dotfile}.\nGenerating C stub for states: #{sm.states_list.join(", ")}."
77
+
51
78
  if options[:header] then
52
- sm.generate_h
53
- puts "Generated header #{sm.cname}.h"
79
+ name = sm.generate_h
80
+ puts "Generated header #{name}"
54
81
  end
55
82
  if options[:source] then
56
- sm.generate_c
57
- puts "Generated source #{sm.cname}.c"
83
+ name = sm.generate_c
84
+ puts "Generated source #{name}"
58
85
  end
59
86
 
60
-
@@ -8,12 +8,15 @@ require File.expand_path("../version.rb", __FILE__)
8
8
 
9
9
  module GV_FSM
10
10
  class FSM
11
- attr_reader :states, :transitions, :dotfile, :prefix
12
- attr_accessor :project_name, :description, :cname
11
+ attr_reader :states, :transitions, :dotfile, :prefix, :error
12
+ attr_accessor :project_name, :description, :cname, :syslog, :ino
13
13
  include GV_FSM::Templates
14
14
 
15
15
  def initialize(filename = nil)
16
16
  @prefix = ""
17
+ @syslog = true
18
+ @ino = false
19
+ @error = nil
17
20
  parse(filename) if filename
18
21
  end
19
22
 
@@ -27,16 +30,37 @@ module GV_FSM
27
30
  @dotfile = filename
28
31
  @states = []
29
32
  @transitions = []
30
- GraphViz.parse(filename) do |g|
33
+ graph = GraphViz.parse(filename) do |g|
34
+ if g.graph_count > 1 then
35
+ @error = "Only one graph in the dot file is permitted"
36
+ return nil
37
+ end
38
+ unless g.type == "digraph" then
39
+ @error = "Graph is not directed"
40
+ return nil
41
+ end
42
+ if g.node_count == 0 then
43
+ @error = "Graph is empty"
44
+ return nil
45
+ end
46
+ @description = g.name
31
47
  g.each_node do |id|
32
48
  n = g.get_node(id)
33
- label = n[:label].source.empty? ? "do_#{id}" : n[:label].source
49
+ if n[:label].source.empty? or
50
+ (n[:label].source == id and !n[:label].source.match(/^do_/)) then
51
+ label = "do_#{id}"
52
+ else
53
+ label = n[:label].source
54
+ end
34
55
  @states << {id: id, function: @prefix+label}
35
56
  end
36
57
  g.each_edge do |e|
37
58
  from = e.node_one
38
59
  to = e.node_two
39
- next unless e[:label]
60
+ unless e[:label] then
61
+ @transitions << {from: from, to: to, function: nil}
62
+ next
63
+ end
40
64
  case e[:label].source
41
65
  when ""
42
66
  label = nil
@@ -48,6 +72,11 @@ module GV_FSM
48
72
  @transitions << {from: from, to: to, function: label ? @prefix+label : nil}
49
73
  end
50
74
  end
75
+ unless graph then
76
+ @error = "Parsing error"
77
+ return nil
78
+ end
79
+ return graph
51
80
  end
52
81
 
53
82
  def state_functions_list
@@ -61,7 +90,7 @@ module GV_FSM
61
90
  def transition_functions_list
62
91
  lst = []
63
92
  @transitions.each do |t|
64
- if !lst.include? t[:function] then
93
+ if t[:function] and !lst.include? t[:function] then
65
94
  lst << (t[:function] or "NULL")
66
95
  end
67
96
  end
@@ -98,18 +127,24 @@ module GV_FSM
98
127
  end
99
128
 
100
129
  def generate_c(filename = @cname)
101
- File.open("#{filename}.c", "w") do |f|
130
+ ext = @ino ? "cpp" : "c"
131
+ fname = "#{filename}.#{ext}"
132
+ File.open(fname, "w") do |f|
102
133
  f.puts ERB.new(HEADER, 0, "<>").result(binding)
103
134
  f.puts ERB.new(CC, 0, "<>").result(binding)
104
135
  end
136
+ return fname
105
137
  end
106
138
 
107
139
  def generate_h(filename = @cname)
108
- File.open("#{filename}.h", "w") do |f|
140
+ fname = "#{filename}.h"
141
+ File.open(fname, "w") do |f|
109
142
  f.puts ERB.new(HEADER, 0, "<>").result(binding)
110
143
  f.puts ERB.new(HH, 0, "<>").result(binding)
111
144
  end
145
+ return fname
112
146
  end
147
+
113
148
  end
114
149
 
115
150
  end
@@ -13,7 +13,6 @@ module GV_FSM
13
13
  Generated from: <%= @dotfile %>
14
14
  The finite state machine has:
15
15
  <%= @states.count %> states
16
- <%= @transitions.count %> transitions
17
16
  <%= transition_functions_list.select {|e| e != 'NULL'}.count %> transition functions
18
17
  <% if @prefix != '' %>
19
18
  Functions and types have been generated with prefix "<%= @prefix %>"
@@ -23,16 +22,22 @@ module GV_FSM
23
22
  EOHEADER
24
23
 
25
24
  HH =<<~EOH
25
+ <% if !@ino then %>
26
26
  #ifndef <%= @cname.upcase %>_H
27
27
  #define <%= @cname.upcase %>_H
28
28
  #include <stdlib.h>
29
+ <% else %>
30
+ #include <arduino.h>
31
+ <% end %>
29
32
 
30
33
  // State data object
31
34
  // By default set to void; override this typedef or load the proper
32
35
  // header if you need
33
36
  typedef void <%= @prefix %>state_data_t;
34
-
37
+ <% if !@ino then %>
38
+
35
39
  // NOTHING SHALL BE CHANGED AFTER THIS LINE!
40
+ <% end %>
36
41
 
37
42
  // List of states
38
43
  typedef enum {
@@ -43,24 +48,36 @@ module GV_FSM
43
48
  <%= @prefix.upcase %>NO_CHANGE
44
49
  } <%= @prefix %>state_t;
45
50
 
46
- const char *state_names[] = {<%= states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
47
-
51
+ // State human-readable names
52
+ extern const char *state_names[];
53
+
54
+ <% if transition_functions_list.count > 0 then %>
48
55
  // State function and state transition prototypes
49
56
  typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
50
57
  typedef void transition_func_t(<%= @prefix %>state_data_t *data);
58
+ <% else %>
59
+ // State function prototype
60
+ typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
61
+ <%end %>
51
62
 
52
63
  // State functions
64
+ <% dest = destinations.dup %>
53
65
  <% @states.each do |s| %>
66
+ <% stable = true if dest[s[:id]].include? s[:id] %>
67
+ <% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} %>
68
+ <% if dest[s[:id]].empty? or stable then
69
+ dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
70
+ end %>
71
+ // Function to be executed in state <%= s[:id] %>
72
+ // valid return states: <%= dest[s[:id]].join(", ") %>
54
73
  <%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data);
55
74
  <% end %>
56
75
 
76
+
57
77
  // List of state functions
58
- state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES] = {
59
- <% @states.each do |s| %>
60
- <%= s[:function] %>, // in state <%= s[:id] %>
61
- <% end %>
62
- };
78
+ extern state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES];
63
79
 
80
+
64
81
  <% if transition_functions_list.count > 0 then %>
65
82
  // Transition functions
66
83
  <% transition_functions_list.each do |t| %>
@@ -69,32 +86,60 @@ module GV_FSM
69
86
  <% end %>
70
87
 
71
88
  // Table of transition functions
72
- transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES] = {
73
- <% sl = states_list %>
74
- <% fw = transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
75
- <% sw = states_list.max {|a, b| a.length <=> b.length}.length %>
76
- /* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
77
- <% transitions_map.each_with_index do |l, i| %>
78
- /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
79
- <% end %>
80
- };
89
+ extern transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES];
81
90
  <% else %>
82
91
  // No transition functions
83
92
  <% end %>
84
93
 
85
94
  // state manager
86
- <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, void *data);
95
+ <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, state_data_t *data);
87
96
 
97
+ <% if !@ino then %>
88
98
  #endif
99
+ <% end %>
89
100
  EOH
90
101
 
91
102
  CC =<<~EOC
103
+ <% if !@ino then %>
104
+ <% if @syslog then %>
105
+ <% log = :syslog %>
92
106
  #include <syslog.h>
107
+ <% end %>
108
+ <% else %>
109
+ <% if @syslog then log = :ino end %>
110
+ <% end %>
93
111
  #include "<%= @cname %>.h"
94
112
 
95
113
  <% placeholder = "Your Code Here" %>
96
114
  // SEARCH FOR <%= placeholder %> FOR CODE INSERTION POINTS!
97
115
 
116
+ // GLOBALS
117
+ // State human-readable names
118
+ const char *state_names[] = {<%= states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
119
+
120
+ // List of state functions
121
+ <% fw = state_functions_list.max {|a, b| a.length <=> b.length}.length %>
122
+ state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES] = {
123
+ <% @states.each do |s| %>
124
+ <%= (s[:function] + ',').ljust(fw+1) %> // in state <%= s[:id] %>
125
+ <% end %>
126
+ };
127
+ <% if transition_functions_list.count > 0 then %>
128
+
129
+ // Table of transition functions
130
+ transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES] = {
131
+ <% sl = states_list %>
132
+ <% fw = transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
133
+ <% sw = [states_list, "states:"].flatten.max {|a, b| a.length <=> b.length}.length %>
134
+ /* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
135
+ <% transitions_map.each_with_index do |l, i| %>
136
+ /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
137
+ <% end %>
138
+ };
139
+ <% else %>
140
+ // No transition functions
141
+ <% end %>
142
+
98
143
  // ____ _ _
99
144
  // / ___|| |_ __ _| |_ ___
100
145
  // \\___ \\| __/ _` | __/ _ \\
@@ -115,20 +160,30 @@ module GV_FSM
115
160
  dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
116
161
  end %>
117
162
  // Function to be executed in state <%= s[:id] %>
163
+ // valid return states: <%= dest[s[:id]].join(", ") %>
118
164
  <%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data) {
119
165
  <%= @prefix %>state_t next_state = <%= dest[s[:id]].first %>;
120
166
 
167
+ <% if log == :syslog then %>
121
168
  syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
169
+ <% elsif log == :ino then %>
170
+ Serial.println("[FSM] In state <%= s[:id] %>");
171
+ <% end %>
122
172
  /* <%= placeholder %> */
123
173
 
124
- // valid return states: <%= dest[s[:id]].join(", ") %>
125
174
  switch (next_state) {
126
175
  <% dest[s[:id]].each do |str| %>
127
176
  case <%= str %>:
128
177
  <% end %>
129
178
  break;
130
179
  default:
180
+ <% if log == :syslog then %>
131
181
  syslog(LOG_WARNING, "[FSM] Cannot pass from <%= s[:id] %> to %s, remaining in this state", state_names[next_state]);
182
+ <% elsif log == :ino then %>
183
+ Serial.print("[FSM] Cannot pass from <%= s[:id] %> to ");
184
+ Serial.print(state_names[next_state]);
185
+ Serial.println(", remaining in this state");
186
+ <% end %>
132
187
  next_state = <%= @prefix.upcase %>NO_CHANGE;
133
188
  }
134
189
  return next_state;
@@ -158,7 +213,11 @@ module GV_FSM
158
213
  // <%= i+1 %>. from <%= e[:from] %> to <%= e[:to] %>
159
214
  <% end %>
160
215
  void <%= t %>(<%= @prefix %>state_data_t *data) {
216
+ <% if log == :syslog then %>
161
217
  syslog(LOG_INFO, "[FSM] State transition <%= t %>");
218
+ <% elsif log == :ino then %>
219
+ Serial.println("[FSM] State transition <%= t %>");
220
+ <% end %>
162
221
  /* <%= placeholder %> */
163
222
  }
164
223
 
@@ -180,6 +239,7 @@ module GV_FSM
180
239
 
181
240
  <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, <%= @prefix %>state_data_t *data) {
182
241
  <%= @prefix %>state_t new_state = <%= @prefix %>state_table[cur_state](data);
242
+ if (new_state == <%= @prefix.upcase %>NO_CHANGE) new_state = cur_state;
183
243
  <% if transition_functions_list.count > 0 then %>
184
244
  transition_func_t *transition = <%= @prefix %>transition_table[cur_state][new_state];
185
245
  if (transition)
@@ -188,13 +248,24 @@ module GV_FSM
188
248
  return new_state == <%= @prefix.upcase %>NO_CHANGE ? cur_state : new_state;
189
249
  };
190
250
 
251
+ <% if @ino then %>
252
+ /* Example usage:
253
+ <%= @prefix %>state_data_t data = {count: 1};
191
254
 
255
+ void loop() {
256
+ static <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
257
+ cur_state = <%= @prefix %>run_state(cur_state, &data);
258
+ }
259
+ */
260
+ <% else %>
192
261
  #ifdef TEST_MAIN
193
262
  #include <unistd.h>
194
263
  int main() {
195
264
  <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
265
+ <% if @syslog then %>
196
266
  openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
197
267
  syslog(LOG_INFO, "Starting SM");
268
+ <% end %>
198
269
  do {
199
270
  cur_state = <%= @prefix %>run_state(cur_state, NULL);
200
271
  sleep(1);
@@ -202,6 +273,7 @@ module GV_FSM
202
273
  return 0;
203
274
  }
204
275
  #endif
276
+ <% end %>
205
277
  EOC
206
278
  end
207
279
  end
@@ -1,3 +1,3 @@
1
1
  module GV_FSM
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.6"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gv_fsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paolo Bosetti
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-08 00:00:00.000000000 Z
11
+ date: 2020-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-graphviz