gv_fsm 0.2.2 → 0.2.7

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