gv_fsm 0.2.3 → 0.2.8

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 +53 -14
  4. data/lib/templates.rb +33 -10
  5. data/lib/version.rb +1 -1
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48a490d68d563a441424cfdd79c6c6051d30d4b6e55765239936aed794a898a5
4
- data.tar.gz: fc16d72b89d439ae9b18f021e46a9cbf1c5caf4b48ed71a45bad2db72998005b
3
+ metadata.gz: 3b5c409a59baa14a81f425617228e69a99e08c6914aba00156516bb6d58f4b01
4
+ data.tar.gz: 88f01660fdc03e5b7e439e8eb5c471990de71f0ab32cd5fd6d6a6e93879294ec
5
5
  SHA512:
6
- metadata.gz: ff52872fa8b822d0b63085cf1721bff88123d64112d3ba1103bdaf6da7e5eb1e801f5fed4526d029128b0853ab1527c45f1fc625d61cc37a9350c81709c5a578
7
- data.tar.gz: 2d4c28d766e9c34bbe379af163f3302f53501870497df608142c7b67e9f02059846944d17ecac780c4d732a68be00eb790625ffbbd6d2d90b4040c764ea6dcea
6
+ metadata.gz: 68c7c63845e630d6b17e5d9ec7a7dac42bbd4045c9e6bd41f25bfcd8d1edc12e3b6dea3518179fe1bea72400a865e4a1fd0112980ea4625b3df3e2185cb651dd
7
+ data.tar.gz: 8c47fdce73aafb70871ad87cddf1a5a359021b2e1cd1f619212186d1fda7b552b30858f958ad818ab4b6a491d47e27501bb7ccd50777d1d5686be17d9c9062af
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 'pry'
5
+ require 'matrix'
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,14 +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
- binding.pry
36
- 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
37
63
  @states << {id: id, function: @prefix+label}
38
64
  end
39
65
  g.each_edge do |e|
66
+ @matrix[@nodemap[e.node_one], @nodemap[e.node_two]] += 1
40
67
  from = e.node_one
41
68
  to = e.node_two
42
69
  unless e[:label] then
@@ -54,6 +81,11 @@ module GV_FSM
54
81
  @transitions << {from: from, to: to, function: label ? @prefix+label : nil}
55
82
  end
56
83
  end
84
+ unless graph then
85
+ @error = "Parsing error"
86
+ return nil
87
+ end
88
+ return graph
57
89
  end
58
90
 
59
91
  def state_functions_list
@@ -104,27 +136,34 @@ module GV_FSM
104
136
  end
105
137
 
106
138
  def generate_c(filename = @cname)
107
- File.open("#{filename}.c", "w") do |f|
139
+ ext = @ino ? "cpp" : "c"
140
+ fname = "#{filename}.#{ext}"
141
+ File.open(fname, "w") do |f|
108
142
  f.puts ERB.new(HEADER, 0, "<>").result(binding)
109
143
  f.puts ERB.new(CC, 0, "<>").result(binding)
110
144
  end
145
+ return fname
111
146
  end
112
147
 
113
148
  def generate_h(filename = @cname)
114
- File.open("#{filename}.h", "w") do |f|
149
+ fname = "#{filename}.h"
150
+ File.open(fname, "w") do |f|
115
151
  f.puts ERB.new(HEADER, 0, "<>").result(binding)
116
152
  f.puts ERB.new(HH, 0, "<>").result(binding)
117
153
  end
154
+ return fname
118
155
  end
119
156
 
120
- def generate_ino(filename=@cname)
121
- @syslog = false
122
- File.open("#{filename}.ino", "w") do |f|
123
- f.puts ERB.new(HEADER, 0, "<>").result(binding)
124
- f.puts ERB.new(HH, 0, "<>").result(binding)
125
- f.puts ERB.new(CC, 0, "<>").result(binding)
126
- 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
127
165
  end
166
+
128
167
  end
129
168
 
130
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
@@ -100,10 +102,13 @@ module GV_FSM
100
102
  CC =<<~EOC
101
103
  <% if !@ino then %>
102
104
  <% if @syslog then %>
105
+ <% log = :syslog %>
103
106
  #include <syslog.h>
104
107
  <% end %>
105
- #include "<%= @cname %>.h"
108
+ <% else %>
109
+ <% if @syslog then log = :ino end %>
106
110
  <% end %>
111
+ #include "<%= @cname %>.h"
107
112
 
108
113
  <% placeholder = "Your Code Here" %>
109
114
  // SEARCH FOR <%= placeholder %> FOR CODE INSERTION POINTS!
@@ -159,8 +164,10 @@ module GV_FSM
159
164
  <%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data) {
160
165
  <%= @prefix %>state_t next_state = <%= dest[s[:id]].first %>;
161
166
 
162
- <% if @syslog then %>
167
+ <% if log == :syslog then %>
163
168
  syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
169
+ <% elsif log == :ino then %>
170
+ Serial.println("[FSM] In state <%= s[:id] %>");
164
171
  <% end %>
165
172
  /* <%= placeholder %> */
166
173
 
@@ -170,8 +177,12 @@ module GV_FSM
170
177
  <% end %>
171
178
  break;
172
179
  default:
173
- <% if @syslog then %>
180
+ <% if log == :syslog then %>
174
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");
175
186
  <% end %>
176
187
  next_state = <%= @prefix.upcase %>NO_CHANGE;
177
188
  }
@@ -202,8 +213,10 @@ module GV_FSM
202
213
  // <%= i+1 %>. from <%= e[:from] %> to <%= e[:to] %>
203
214
  <% end %>
204
215
  void <%= t %>(<%= @prefix %>state_data_t *data) {
205
- <% if @syslog then %>
216
+ <% if log == :syslog then %>
206
217
  syslog(LOG_INFO, "[FSM] State transition <%= t %>");
218
+ <% elsif log == :ino then %>
219
+ Serial.println("[FSM] State transition <%= t %>");
207
220
  <% end %>
208
221
  /* <%= placeholder %> */
209
222
  }
@@ -226,6 +239,7 @@ module GV_FSM
226
239
 
227
240
  <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, <%= @prefix %>state_data_t *data) {
228
241
  <%= @prefix %>state_t new_state = <%= @prefix %>state_table[cur_state](data);
242
+ if (new_state == <%= @prefix.upcase %>NO_CHANGE) new_state = cur_state;
229
243
  <% if transition_functions_list.count > 0 then %>
230
244
  transition_func_t *transition = <%= @prefix %>transition_table[cur_state][new_state];
231
245
  if (transition)
@@ -236,12 +250,19 @@ module GV_FSM
236
250
 
237
251
  <% if @ino then %>
238
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
+ */
239
260
  <% else %>
261
+ <% nsinks = topology[:sinks].count %>
240
262
  #ifdef TEST_MAIN
241
263
  #include <unistd.h>
242
264
  int main() {
243
- <% end %>
244
- <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
265
+ <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_<%= @states.first[:id].upcase %>;
245
266
  <% if @syslog then %>
246
267
  openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
247
268
  syslog(LOG_INFO, "Starting SM");
@@ -249,13 +270,15 @@ module GV_FSM
249
270
  do {
250
271
  cur_state = <%= @prefix %>run_state(cur_state, NULL);
251
272
  sleep(1);
252
- } while (cur_state != <%= @prefix.upcase %>STATE_STOP);
253
- <% if !@ino then %>
273
+ <% if nsinks == 1 %>
274
+ } while (cur_state != <%= @prefix.upcase %>STATE_<%= topology[:sinks][0].upcase %>);
275
+ <%= @prefix %>run_state(cur_state, NULL);
276
+ <% else %>
277
+ } while (1);
278
+ <% end %>
254
279
  return 0;
255
280
  }
256
281
  #endif
257
- <% else %>
258
- */
259
282
  <% end %>
260
283
  EOC
261
284
  end
@@ -1,3 +1,3 @@
1
1
  module GV_FSM
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.8"
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.3
4
+ version: 0.2.8
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-09-09 00:00:00.000000000 Z
11
+ date: 2020-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-graphviz