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.
- checksums.yaml +4 -4
- data/bin/gv_fsm +31 -19
- data/lib/gv_fsm.rb +52 -12
- data/lib/templates.rb +63 -27
- data/lib/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1706418c51873674098f304fe267d662d211728675db2ca78687efcb49b2ba95
|
4
|
+
data.tar.gz: 991b5f7b6d1173d4e08a9635ebbce25f654c88521a913ce7514a51e09cd1d3fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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("-
|
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-
|
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
|
-
|
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}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
|
data/lib/gv_fsm.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
data/lib/templates.rb
CHANGED
@@ -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[]
|
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
|
-
|
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,
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
239
|
-
|
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
|
data/lib/version.rb
CHANGED