gv_fsm 0.2.3 → 0.2.8

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 +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