gv_fsm 0.2.2 → 0.2.7

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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/bin/gv_fsm +31 -19
  3. data/lib/gv_fsm.rb +52 -12
  4. data/lib/templates.rb +63 -27
  5. data/lib/version.rb +1 -1
  6. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3579818099a2f3e8d172c9534af08877f3e4b27f4fc09f9c9aa77c6275256ce3
4
- data.tar.gz: 5b4acdd1da41b63fbac514f2a481766c5b5c08ba34881ef708d11871910e8d0d
3
+ metadata.gz: 1706418c51873674098f304fe267d662d211728675db2ca78687efcb49b2ba95
4
+ data.tar.gz: 991b5f7b6d1173d4e08a9635ebbce25f654c88521a913ce7514a51e09cd1d3fd
5
5
  SHA512:
6
- metadata.gz: 5f481dacfc667927287eecc1b5f901fd915a593ab533b6de35f573e0cb1b66219c5cd37e24f9c54095e1049f8a7f3f67094530338a9c653cbb72585d1ccbcdf3
7
- data.tar.gz: 4b835c1bc4f03a6d80e37f64f219b0801756ffaeeedbd7da20f53ffbc428b48efd92ac075c189b5379d30ef125ebaf4417e259b0729d1c9e9ac0a94db08b3e14
6
+ metadata.gz: 3f2d8954ab0a5a009bd5cf6b31ca7d6927a680681e81dbd3a70d0aaa598234b8c71e91098c6e1aa011244ec05973611184b9c22bbbd28d32a1ff90e7e89350f9
7
+ data.tar.gz: 0b8d6e7a3fe3fe9434d3d58371b38edf5a82651cd9b9fdffec415c531ee0deaea6418ddce70520d19c20d4e83f044c9e51cb5bb53964827b9eaae762621d59e9
data/bin/gv_fsm CHANGED
@@ -28,7 +28,7 @@ op = 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
 
@@ -44,7 +44,7 @@ op = OptionParser.new do |parser|
44
44
  sm.ino = true
45
45
  end
46
46
 
47
- parser.on("-l", "--no-syslog", "Omit syslog calls in stub functions") do
47
+ parser.on("-l", "--no-log", "Omit log calls in stub functions") do
48
48
  sm.syslog = false
49
49
  end
50
50
 
@@ -60,28 +60,40 @@ op.parse!
60
60
  unless ARGV[0]
61
61
  STDERR.puts "ERROR: I need the path to a Graphviz file!\n\n"
62
62
  STDERR.puts op
63
- exit
63
+ exit 1
64
64
  end
65
- unless File.extname(ARGV[0]) == ".dot"
65
+ if !File.extname(ARGV[0]) == ".dot" or !File.exist? ARGV[0] then
66
66
  STDERR.puts "ERROR: #{ARGV[0]} does not look like a Graphviz file!\n\n"
67
67
  STDERR.puts op
68
- exit
68
+ exit 2
69
69
  end
70
70
 
71
- sm.parse(ARGV[0])
71
+ unless sm.parse(ARGV[0]) then
72
+ puts "Error parsing the file #{ARGV[0]}: #{sm.error}"
73
+ exit 3
74
+ end
72
75
 
73
- puts "Parsed #{sm.dotfile}.\nGenerating C stub for states: #{sm.states_list.join(", ")}."
74
- if sm.ino then
75
- sm.generate_ino
76
- puts "Generated .ino file #{sm.cname}.ino"
77
- else
78
- if options[:header] then
79
- sm.generate_h
80
- puts "Generated header #{sm.cname}.h"
81
- end
82
- if options[:source] then
83
- sm.generate_c
84
- puts "Generated source #{sm.cname}.c"
85
- end
76
+ puts "Parsed #{sm.dotfile}"
77
+ top = sm.topology
78
+ puts "Graph topology:"
79
+ puts " Pure source nodes: #{top[:sources].join(', ')}"
80
+ puts " Pure sink nodes: #{top[:sinks].empty? ? "<none>" : top[:sinks].join(', ')}"
81
+
82
+ if !(top[:sources].count == 1 and top[:sinks].count <= 1) then
83
+ puts "Topology error: there must be exactly one source and zero or one sink"
84
+ exit 4
85
+ end
86
+
87
+ puts "Generating C functions for states: #{sm.states_list.join(", ")}."
88
+ puts " for transition: #{sm.transition_functions_list.join(", ")}."
89
+
90
+
91
+ if options[:header] then
92
+ name = sm.generate_h
93
+ puts "Generated header #{name}"
94
+ end
95
+ if options[:source] then
96
+ name = sm.generate_c
97
+ puts "Generated source #{name}"
86
98
  end
87
99
 
@@ -2,13 +2,14 @@
2
2
 
3
3
  require 'ruby-graphviz'
4
4
  require 'erb'
5
+ require 'matrix'
5
6
 
6
7
  require File.expand_path('../templates.rb', __FILE__)
7
8
  require File.expand_path("../version.rb", __FILE__)
8
9
 
9
10
  module GV_FSM
10
11
  class FSM
11
- attr_reader :states, :transitions, :dotfile, :prefix
12
+ attr_reader :states, :transitions, :dotfile, :prefix, :error
12
13
  attr_accessor :project_name, :description, :cname, :syslog, :ino
13
14
  include GV_FSM::Templates
14
15
 
@@ -16,6 +17,9 @@ module GV_FSM
16
17
  @prefix = ""
17
18
  @syslog = true
18
19
  @ino = false
20
+ @error = nil
21
+ @matrix = nil
22
+ @nodemap = {}
19
23
  parse(filename) if filename
20
24
  end
21
25
 
@@ -29,13 +33,37 @@ module GV_FSM
29
33
  @dotfile = filename
30
34
  @states = []
31
35
  @transitions = []
32
- GraphViz.parse(filename) do |g|
36
+ graph = GraphViz.parse(filename) do |g|
37
+ if g.graph_count > 1 then
38
+ @error = "Only one graph in the dot file is permitted"
39
+ return nil
40
+ end
41
+ unless g.type == "digraph" then
42
+ @error = "Graph is not directed"
43
+ return nil
44
+ end
45
+ n = g.node_count
46
+ if n == 0 then
47
+ @error = "Graph is empty"
48
+ return nil
49
+ end
50
+ @matrix = Matrix.zero(n, n)
51
+ @description = g.name
52
+ i = 0
33
53
  g.each_node do |id|
34
54
  n = g.get_node(id)
35
- label = n[:label].source.empty? ? "do_#{id}" : n[:label].source
55
+ if n[:label].source.empty? or
56
+ (n[:label].source == id and !n[:label].source.match(/^do_/)) then
57
+ label = "do_#{id}"
58
+ else
59
+ label = n[:label].source
60
+ end
61
+ @nodemap[id] = i
62
+ i += 1
36
63
  @states << {id: id, function: @prefix+label}
37
64
  end
38
65
  g.each_edge do |e|
66
+ @matrix[@nodemap[e.node_one], @nodemap[e.node_two]] += 1
39
67
  from = e.node_one
40
68
  to = e.node_two
41
69
  unless e[:label] then
@@ -53,6 +81,11 @@ module GV_FSM
53
81
  @transitions << {from: from, to: to, function: label ? @prefix+label : nil}
54
82
  end
55
83
  end
84
+ unless graph then
85
+ @error = "Parsing error"
86
+ return nil
87
+ end
88
+ return graph
56
89
  end
57
90
 
58
91
  def state_functions_list
@@ -103,27 +136,34 @@ module GV_FSM
103
136
  end
104
137
 
105
138
  def generate_c(filename = @cname)
106
- File.open("#{filename}.c", "w") do |f|
139
+ ext = @ino ? "cpp" : "c"
140
+ fname = "#{filename}.#{ext}"
141
+ File.open(fname, "w") do |f|
107
142
  f.puts ERB.new(HEADER, 0, "<>").result(binding)
108
143
  f.puts ERB.new(CC, 0, "<>").result(binding)
109
144
  end
145
+ return fname
110
146
  end
111
147
 
112
148
  def generate_h(filename = @cname)
113
- File.open("#{filename}.h", "w") do |f|
149
+ fname = "#{filename}.h"
150
+ File.open(fname, "w") do |f|
114
151
  f.puts ERB.new(HEADER, 0, "<>").result(binding)
115
152
  f.puts ERB.new(HH, 0, "<>").result(binding)
116
153
  end
154
+ return fname
117
155
  end
118
156
 
119
- def generate_ino(filename=@cname)
120
- @syslog = false
121
- File.open("#{filename}.ino", "w") do |f|
122
- f.puts ERB.new(HEADER, 0, "<>").result(binding)
123
- f.puts ERB.new(HH, 0, "<>").result(binding)
124
- f.puts ERB.new(CC, 0, "<>").result(binding)
125
- end
157
+ def topology
158
+ res = {matrix: @matrix}
159
+ # rows have the number of froms, columns the number of tos
160
+ res[:froms] = @matrix.row_vectors.map {|v| v.sum }
161
+ res[:tos] = @matrix.column_vectors.map {|v| v.sum }
162
+ res[:sinks] = res[:froms].each_index.select {|i| res[:froms][i] == 0}.map {|i| @nodemap.keys[i]}
163
+ res[:sources] = res[:tos].each_index.select {|i| res[:tos][i] == 0}.map {|i| @nodemap.keys[i]}
164
+ return res
126
165
  end
166
+
127
167
  end
128
168
 
129
169
  end
@@ -26,6 +26,8 @@ module GV_FSM
26
26
  #ifndef <%= @cname.upcase %>_H
27
27
  #define <%= @cname.upcase %>_H
28
28
  #include <stdlib.h>
29
+ <% else %>
30
+ #include <arduino.h>
29
31
  <% end %>
30
32
 
31
33
  // State data object
@@ -47,7 +49,7 @@ module GV_FSM
47
49
  } <%= @prefix %>state_t;
48
50
 
49
51
  // State human-readable names
50
- const char *state_names[] = {<%= states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
52
+ extern const char *state_names[];
51
53
 
52
54
  <% if transition_functions_list.count > 0 then %>
53
55
  // State function and state transition prototypes
@@ -73,12 +75,7 @@ module GV_FSM
73
75
 
74
76
 
75
77
  // List of state functions
76
- <% fw = state_functions_list.max {|a, b| a.length <=> b.length}.length %>
77
- state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES] = {
78
- <% @states.each do |s| %>
79
- <%= (s[:function] + ',').ljust(fw+1) %> // in state <%= s[:id] %>
80
- <% end %>
81
- };
78
+ extern state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES];
82
79
 
83
80
 
84
81
  <% if transition_functions_list.count > 0 then %>
@@ -89,21 +86,13 @@ module GV_FSM
89
86
  <% end %>
90
87
 
91
88
  // Table of transition functions
92
- transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES] = {
93
- <% sl = states_list %>
94
- <% fw = transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
95
- <% sw = states_list.max {|a, b| a.length <=> b.length}.length %>
96
- /* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
97
- <% transitions_map.each_with_index do |l, i| %>
98
- /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
99
- <% end %>
100
- };
89
+ extern transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES];
101
90
  <% else %>
102
91
  // No transition functions
103
92
  <% end %>
104
93
 
105
94
  // state manager
106
- <%= @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);
107
96
 
108
97
  <% if !@ino then %>
109
98
  #endif
@@ -113,14 +102,44 @@ module GV_FSM
113
102
  CC =<<~EOC
114
103
  <% if !@ino then %>
115
104
  <% if @syslog then %>
105
+ <% log = :syslog %>
116
106
  #include <syslog.h>
117
107
  <% end %>
118
- #include "<%= @cname %>.h"
108
+ <% else %>
109
+ <% if @syslog then log = :ino end %>
119
110
  <% end %>
111
+ #include "<%= @cname %>.h"
120
112
 
121
113
  <% placeholder = "Your Code Here" %>
122
114
  // SEARCH FOR <%= placeholder %> FOR CODE INSERTION POINTS!
123
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
+
124
143
  // ____ _ _
125
144
  // / ___|| |_ __ _| |_ ___
126
145
  // \\___ \\| __/ _` | __/ _ \\
@@ -145,8 +164,10 @@ module GV_FSM
145
164
  <%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data) {
146
165
  <%= @prefix %>state_t next_state = <%= dest[s[:id]].first %>;
147
166
 
148
- <% if @syslog then %>
167
+ <% if log == :syslog then %>
149
168
  syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
169
+ <% elsif log == :ino then %>
170
+ Serial.println("[FSM] In state <%= s[:id] %>");
150
171
  <% end %>
151
172
  /* <%= placeholder %> */
152
173
 
@@ -156,8 +177,12 @@ module GV_FSM
156
177
  <% end %>
157
178
  break;
158
179
  default:
159
- <% if @syslog then %>
180
+ <% if log == :syslog then %>
160
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");
161
186
  <% end %>
162
187
  next_state = <%= @prefix.upcase %>NO_CHANGE;
163
188
  }
@@ -188,8 +213,10 @@ module GV_FSM
188
213
  // <%= i+1 %>. from <%= e[:from] %> to <%= e[:to] %>
189
214
  <% end %>
190
215
  void <%= t %>(<%= @prefix %>state_data_t *data) {
191
- <% if @syslog then %>
216
+ <% if log == :syslog then %>
192
217
  syslog(LOG_INFO, "[FSM] State transition <%= t %>");
218
+ <% elsif log == :ino then %>
219
+ Serial.println("[FSM] State transition <%= t %>");
193
220
  <% end %>
194
221
  /* <%= placeholder %> */
195
222
  }
@@ -212,6 +239,7 @@ module GV_FSM
212
239
 
213
240
  <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, <%= @prefix %>state_data_t *data) {
214
241
  <%= @prefix %>state_t new_state = <%= @prefix %>state_table[cur_state](data);
242
+ if (new_state == <%= @prefix.upcase %>NO_CHANGE) new_state = cur_state;
215
243
  <% if transition_functions_list.count > 0 then %>
216
244
  transition_func_t *transition = <%= @prefix %>transition_table[cur_state][new_state];
217
245
  if (transition)
@@ -222,12 +250,19 @@ module GV_FSM
222
250
 
223
251
  <% if @ino then %>
224
252
  /* Example usage:
253
+ <%= @prefix %>state_data_t data = {count: 1};
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
+ */
225
260
  <% else %>
261
+ <% nsinks = topology[:sinks].count %>
226
262
  #ifdef TEST_MAIN
227
263
  #include <unistd.h>
228
264
  int main() {
229
- <% end %>
230
- <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
265
+ <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_<%= @states.first[:id].upcase %>;
231
266
  <% if @syslog then %>
232
267
  openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
233
268
  syslog(LOG_INFO, "Starting SM");
@@ -235,13 +270,14 @@ module GV_FSM
235
270
  do {
236
271
  cur_state = <%= @prefix %>run_state(cur_state, NULL);
237
272
  sleep(1);
238
- } while (cur_state != <%= @prefix.upcase %>STATE_STOP);
239
- <% if !@ino then %>
273
+ <% if nsinks == 1 %>
274
+ } while (cur_state != <%= @prefix.upcase %>STATE_<%= topology[:sinks][0].upcase %>);
275
+ <% else %>
276
+ } while (1);
277
+ <% end %>
240
278
  return 0;
241
279
  }
242
280
  #endif
243
- <% else %>
244
- */
245
281
  <% end %>
246
282
  EOC
247
283
  end
@@ -1,3 +1,3 @@
1
1
  module GV_FSM
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.7"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gv_fsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paolo Bosetti