gv_fsm 0.2.1 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
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