gv_fsm 0.2.1 → 0.2.6
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 +37 -11
- data/lib/gv_fsm.rb +43 -8
- data/lib/templates.rb +92 -20
- data/lib/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61d5d6c1ef593ce7d516c48ab905c210d3005081fdd241b47298c55c5340236c
|
4
|
+
data.tar.gz: d8bc69220e149140bd150ad3890dbf11e33651352bf22df483b7941c9a9911c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c92f9ebb2275bfb8fd4fa23060cc22c8d34fe2e7eb219e9833f60517417ca11a420758c99fb0bee82cc58795f184a44d230bf03ae947072e1a72271bad34b2c9
|
7
|
+
data.tar.gz: 7a2898d7f153fe7c80a9eb046c54bd4076ffa15bf69987f069d94a70708a17085641fc7dff765fd7816df839f469ce363e7793122e5d09538e882667d5e83650
|
data/bin/gv_fsm
CHANGED
@@ -6,7 +6,7 @@ require 'optparse'
|
|
6
6
|
sm = GV_FSM::FSM.new
|
7
7
|
|
8
8
|
options = {header: true, source: true}
|
9
|
-
OptionParser.new do |parser|
|
9
|
+
op = OptionParser.new do |parser|
|
10
10
|
parser.banner =<<~EOB
|
11
11
|
Graphviz to Finite State Machine generator
|
12
12
|
Version: #{GV_FSM::VERSION}
|
@@ -28,7 +28,7 @@ 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
|
|
@@ -40,21 +40,47 @@ OptionParser.new do |parser|
|
|
40
40
|
sm.prefix = p
|
41
41
|
end
|
42
42
|
|
43
|
-
|
43
|
+
parser.on("-i", "--ino", "Generate a single .ino file (for Arduino)") do
|
44
|
+
sm.ino = true
|
45
|
+
end
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
+
parser.on("-l", "--no-log", "Omit log calls in stub functions") do
|
48
|
+
sm.syslog = false
|
49
|
+
end
|
47
50
|
|
48
|
-
|
51
|
+
parser.on("-h", "--help", "Prints this help") do
|
52
|
+
puts parser
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
op.parse!
|
59
|
+
|
60
|
+
unless ARGV[0]
|
61
|
+
STDERR.puts "ERROR: I need the path to a Graphviz file!\n\n"
|
62
|
+
STDERR.puts op
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
if !File.extname(ARGV[0]) == ".dot" or !File.exist? ARGV[0] then
|
66
|
+
STDERR.puts "ERROR: #{ARGV[0]} does not look like a Graphviz file!\n\n"
|
67
|
+
STDERR.puts op
|
68
|
+
exit 2
|
69
|
+
end
|
70
|
+
|
71
|
+
unless sm.parse(ARGV[0]) then
|
72
|
+
puts "Error parsing the file #{ARGV[0]}: #{sm.error}"
|
73
|
+
exit 3
|
74
|
+
end
|
49
75
|
|
50
76
|
puts "Parsed #{sm.dotfile}.\nGenerating C stub for states: #{sm.states_list.join(", ")}."
|
77
|
+
|
51
78
|
if options[:header] then
|
52
|
-
sm.generate_h
|
53
|
-
puts "Generated header #{
|
79
|
+
name = sm.generate_h
|
80
|
+
puts "Generated header #{name}"
|
54
81
|
end
|
55
82
|
if options[:source] then
|
56
|
-
sm.generate_c
|
57
|
-
puts "Generated source #{
|
83
|
+
name = sm.generate_c
|
84
|
+
puts "Generated source #{name}"
|
58
85
|
end
|
59
86
|
|
60
|
-
|
data/lib/gv_fsm.rb
CHANGED
@@ -8,12 +8,15 @@ require File.expand_path("../version.rb", __FILE__)
|
|
8
8
|
|
9
9
|
module GV_FSM
|
10
10
|
class FSM
|
11
|
-
attr_reader :states, :transitions, :dotfile, :prefix
|
12
|
-
attr_accessor :project_name, :description, :cname
|
11
|
+
attr_reader :states, :transitions, :dotfile, :prefix, :error
|
12
|
+
attr_accessor :project_name, :description, :cname, :syslog, :ino
|
13
13
|
include GV_FSM::Templates
|
14
14
|
|
15
15
|
def initialize(filename = nil)
|
16
16
|
@prefix = ""
|
17
|
+
@syslog = true
|
18
|
+
@ino = false
|
19
|
+
@error = nil
|
17
20
|
parse(filename) if filename
|
18
21
|
end
|
19
22
|
|
@@ -27,16 +30,37 @@ module GV_FSM
|
|
27
30
|
@dotfile = filename
|
28
31
|
@states = []
|
29
32
|
@transitions = []
|
30
|
-
GraphViz.parse(filename) do |g|
|
33
|
+
graph = GraphViz.parse(filename) do |g|
|
34
|
+
if g.graph_count > 1 then
|
35
|
+
@error = "Only one graph in the dot file is permitted"
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
unless g.type == "digraph" then
|
39
|
+
@error = "Graph is not directed"
|
40
|
+
return nil
|
41
|
+
end
|
42
|
+
if g.node_count == 0 then
|
43
|
+
@error = "Graph is empty"
|
44
|
+
return nil
|
45
|
+
end
|
46
|
+
@description = g.name
|
31
47
|
g.each_node do |id|
|
32
48
|
n = g.get_node(id)
|
33
|
-
|
49
|
+
if n[:label].source.empty? or
|
50
|
+
(n[:label].source == id and !n[:label].source.match(/^do_/)) then
|
51
|
+
label = "do_#{id}"
|
52
|
+
else
|
53
|
+
label = n[:label].source
|
54
|
+
end
|
34
55
|
@states << {id: id, function: @prefix+label}
|
35
56
|
end
|
36
57
|
g.each_edge do |e|
|
37
58
|
from = e.node_one
|
38
59
|
to = e.node_two
|
39
|
-
|
60
|
+
unless e[:label] then
|
61
|
+
@transitions << {from: from, to: to, function: nil}
|
62
|
+
next
|
63
|
+
end
|
40
64
|
case e[:label].source
|
41
65
|
when ""
|
42
66
|
label = nil
|
@@ -48,6 +72,11 @@ module GV_FSM
|
|
48
72
|
@transitions << {from: from, to: to, function: label ? @prefix+label : nil}
|
49
73
|
end
|
50
74
|
end
|
75
|
+
unless graph then
|
76
|
+
@error = "Parsing error"
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
return graph
|
51
80
|
end
|
52
81
|
|
53
82
|
def state_functions_list
|
@@ -61,7 +90,7 @@ module GV_FSM
|
|
61
90
|
def transition_functions_list
|
62
91
|
lst = []
|
63
92
|
@transitions.each do |t|
|
64
|
-
if !lst.include? t[:function] then
|
93
|
+
if t[:function] and !lst.include? t[:function] then
|
65
94
|
lst << (t[:function] or "NULL")
|
66
95
|
end
|
67
96
|
end
|
@@ -98,18 +127,24 @@ module GV_FSM
|
|
98
127
|
end
|
99
128
|
|
100
129
|
def generate_c(filename = @cname)
|
101
|
-
|
130
|
+
ext = @ino ? "cpp" : "c"
|
131
|
+
fname = "#{filename}.#{ext}"
|
132
|
+
File.open(fname, "w") do |f|
|
102
133
|
f.puts ERB.new(HEADER, 0, "<>").result(binding)
|
103
134
|
f.puts ERB.new(CC, 0, "<>").result(binding)
|
104
135
|
end
|
136
|
+
return fname
|
105
137
|
end
|
106
138
|
|
107
139
|
def generate_h(filename = @cname)
|
108
|
-
|
140
|
+
fname = "#{filename}.h"
|
141
|
+
File.open(fname, "w") do |f|
|
109
142
|
f.puts ERB.new(HEADER, 0, "<>").result(binding)
|
110
143
|
f.puts ERB.new(HH, 0, "<>").result(binding)
|
111
144
|
end
|
145
|
+
return fname
|
112
146
|
end
|
147
|
+
|
113
148
|
end
|
114
149
|
|
115
150
|
end
|
data/lib/templates.rb
CHANGED
@@ -13,7 +13,6 @@ module GV_FSM
|
|
13
13
|
Generated from: <%= @dotfile %>
|
14
14
|
The finite state machine has:
|
15
15
|
<%= @states.count %> states
|
16
|
-
<%= @transitions.count %> transitions
|
17
16
|
<%= transition_functions_list.select {|e| e != 'NULL'}.count %> transition functions
|
18
17
|
<% if @prefix != '' %>
|
19
18
|
Functions and types have been generated with prefix "<%= @prefix %>"
|
@@ -23,16 +22,22 @@ module GV_FSM
|
|
23
22
|
EOHEADER
|
24
23
|
|
25
24
|
HH =<<~EOH
|
25
|
+
<% if !@ino then %>
|
26
26
|
#ifndef <%= @cname.upcase %>_H
|
27
27
|
#define <%= @cname.upcase %>_H
|
28
28
|
#include <stdlib.h>
|
29
|
+
<% else %>
|
30
|
+
#include <arduino.h>
|
31
|
+
<% end %>
|
29
32
|
|
30
33
|
// State data object
|
31
34
|
// By default set to void; override this typedef or load the proper
|
32
35
|
// header if you need
|
33
36
|
typedef void <%= @prefix %>state_data_t;
|
34
|
-
|
37
|
+
<% if !@ino then %>
|
38
|
+
|
35
39
|
// NOTHING SHALL BE CHANGED AFTER THIS LINE!
|
40
|
+
<% end %>
|
36
41
|
|
37
42
|
// List of states
|
38
43
|
typedef enum {
|
@@ -43,24 +48,36 @@ module GV_FSM
|
|
43
48
|
<%= @prefix.upcase %>NO_CHANGE
|
44
49
|
} <%= @prefix %>state_t;
|
45
50
|
|
46
|
-
|
47
|
-
|
51
|
+
// State human-readable names
|
52
|
+
extern const char *state_names[];
|
53
|
+
|
54
|
+
<% if transition_functions_list.count > 0 then %>
|
48
55
|
// State function and state transition prototypes
|
49
56
|
typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
|
50
57
|
typedef void transition_func_t(<%= @prefix %>state_data_t *data);
|
58
|
+
<% else %>
|
59
|
+
// State function prototype
|
60
|
+
typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
|
61
|
+
<%end %>
|
51
62
|
|
52
63
|
// State functions
|
64
|
+
<% dest = destinations.dup %>
|
53
65
|
<% @states.each do |s| %>
|
66
|
+
<% stable = true if dest[s[:id]].include? s[:id] %>
|
67
|
+
<% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} %>
|
68
|
+
<% if dest[s[:id]].empty? or stable then
|
69
|
+
dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
|
70
|
+
end %>
|
71
|
+
// Function to be executed in state <%= s[:id] %>
|
72
|
+
// valid return states: <%= dest[s[:id]].join(", ") %>
|
54
73
|
<%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data);
|
55
74
|
<% end %>
|
56
75
|
|
76
|
+
|
57
77
|
// List of state functions
|
58
|
-
state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES]
|
59
|
-
<% @states.each do |s| %>
|
60
|
-
<%= s[:function] %>, // in state <%= s[:id] %>
|
61
|
-
<% end %>
|
62
|
-
};
|
78
|
+
extern state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES];
|
63
79
|
|
80
|
+
|
64
81
|
<% if transition_functions_list.count > 0 then %>
|
65
82
|
// Transition functions
|
66
83
|
<% transition_functions_list.each do |t| %>
|
@@ -69,32 +86,60 @@ module GV_FSM
|
|
69
86
|
<% end %>
|
70
87
|
|
71
88
|
// Table of transition functions
|
72
|
-
transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES]
|
73
|
-
<% sl = states_list %>
|
74
|
-
<% fw = transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
|
75
|
-
<% sw = states_list.max {|a, b| a.length <=> b.length}.length %>
|
76
|
-
/* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
|
77
|
-
<% transitions_map.each_with_index do |l, i| %>
|
78
|
-
/* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
|
79
|
-
<% end %>
|
80
|
-
};
|
89
|
+
extern transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES];
|
81
90
|
<% else %>
|
82
91
|
// No transition functions
|
83
92
|
<% end %>
|
84
93
|
|
85
94
|
// state manager
|
86
|
-
<%= @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);
|
87
96
|
|
97
|
+
<% if !@ino then %>
|
88
98
|
#endif
|
99
|
+
<% end %>
|
89
100
|
EOH
|
90
101
|
|
91
102
|
CC =<<~EOC
|
103
|
+
<% if !@ino then %>
|
104
|
+
<% if @syslog then %>
|
105
|
+
<% log = :syslog %>
|
92
106
|
#include <syslog.h>
|
107
|
+
<% end %>
|
108
|
+
<% else %>
|
109
|
+
<% if @syslog then log = :ino end %>
|
110
|
+
<% end %>
|
93
111
|
#include "<%= @cname %>.h"
|
94
112
|
|
95
113
|
<% placeholder = "Your Code Here" %>
|
96
114
|
// SEARCH FOR <%= placeholder %> FOR CODE INSERTION POINTS!
|
97
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
|
+
|
98
143
|
// ____ _ _
|
99
144
|
// / ___|| |_ __ _| |_ ___
|
100
145
|
// \\___ \\| __/ _` | __/ _ \\
|
@@ -115,20 +160,30 @@ module GV_FSM
|
|
115
160
|
dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
|
116
161
|
end %>
|
117
162
|
// Function to be executed in state <%= s[:id] %>
|
163
|
+
// valid return states: <%= dest[s[:id]].join(", ") %>
|
118
164
|
<%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data) {
|
119
165
|
<%= @prefix %>state_t next_state = <%= dest[s[:id]].first %>;
|
120
166
|
|
167
|
+
<% if log == :syslog then %>
|
121
168
|
syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
|
169
|
+
<% elsif log == :ino then %>
|
170
|
+
Serial.println("[FSM] In state <%= s[:id] %>");
|
171
|
+
<% end %>
|
122
172
|
/* <%= placeholder %> */
|
123
173
|
|
124
|
-
// valid return states: <%= dest[s[:id]].join(", ") %>
|
125
174
|
switch (next_state) {
|
126
175
|
<% dest[s[:id]].each do |str| %>
|
127
176
|
case <%= str %>:
|
128
177
|
<% end %>
|
129
178
|
break;
|
130
179
|
default:
|
180
|
+
<% if log == :syslog then %>
|
131
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");
|
186
|
+
<% end %>
|
132
187
|
next_state = <%= @prefix.upcase %>NO_CHANGE;
|
133
188
|
}
|
134
189
|
return next_state;
|
@@ -158,7 +213,11 @@ module GV_FSM
|
|
158
213
|
// <%= i+1 %>. from <%= e[:from] %> to <%= e[:to] %>
|
159
214
|
<% end %>
|
160
215
|
void <%= t %>(<%= @prefix %>state_data_t *data) {
|
216
|
+
<% if log == :syslog then %>
|
161
217
|
syslog(LOG_INFO, "[FSM] State transition <%= t %>");
|
218
|
+
<% elsif log == :ino then %>
|
219
|
+
Serial.println("[FSM] State transition <%= t %>");
|
220
|
+
<% end %>
|
162
221
|
/* <%= placeholder %> */
|
163
222
|
}
|
164
223
|
|
@@ -180,6 +239,7 @@ module GV_FSM
|
|
180
239
|
|
181
240
|
<%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, <%= @prefix %>state_data_t *data) {
|
182
241
|
<%= @prefix %>state_t new_state = <%= @prefix %>state_table[cur_state](data);
|
242
|
+
if (new_state == <%= @prefix.upcase %>NO_CHANGE) new_state = cur_state;
|
183
243
|
<% if transition_functions_list.count > 0 then %>
|
184
244
|
transition_func_t *transition = <%= @prefix %>transition_table[cur_state][new_state];
|
185
245
|
if (transition)
|
@@ -188,13 +248,24 @@ module GV_FSM
|
|
188
248
|
return new_state == <%= @prefix.upcase %>NO_CHANGE ? cur_state : new_state;
|
189
249
|
};
|
190
250
|
|
251
|
+
<% if @ino then %>
|
252
|
+
/* Example usage:
|
253
|
+
<%= @prefix %>state_data_t data = {count: 1};
|
191
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
|
+
*/
|
260
|
+
<% else %>
|
192
261
|
#ifdef TEST_MAIN
|
193
262
|
#include <unistd.h>
|
194
263
|
int main() {
|
195
264
|
<%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
|
265
|
+
<% if @syslog then %>
|
196
266
|
openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
|
197
267
|
syslog(LOG_INFO, "Starting SM");
|
268
|
+
<% end %>
|
198
269
|
do {
|
199
270
|
cur_state = <%= @prefix %>run_state(cur_state, NULL);
|
200
271
|
sleep(1);
|
@@ -202,6 +273,7 @@ module GV_FSM
|
|
202
273
|
return 0;
|
203
274
|
}
|
204
275
|
#endif
|
276
|
+
<% end %>
|
205
277
|
EOC
|
206
278
|
end
|
207
279
|
end
|
data/lib/version.rb
CHANGED
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.
|
4
|
+
version: 0.2.6
|
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-
|
11
|
+
date: 2020-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-graphviz
|