rsmp 0.1.21 → 0.1.32
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/.gitmodules +0 -4
- data/Gemfile.lock +13 -11
- data/config/supervisor.yaml +9 -15
- data/config/tlc.yaml +9 -8
- data/documentation/message_distribution.md +8 -8
- data/lib/rsmp.rb +5 -0
- data/lib/rsmp/archive.rb +7 -4
- data/lib/rsmp/cli.rb +133 -102
- data/lib/rsmp/collector.rb +42 -30
- data/lib/rsmp/component.rb +2 -0
- data/lib/rsmp/components.rb +1 -1
- data/lib/rsmp/convert/export/json_schema.rb +204 -0
- data/lib/rsmp/convert/import/yaml.rb +38 -0
- data/lib/rsmp/deep_merge.rb +11 -0
- data/lib/rsmp/inspect.rb +46 -0
- data/lib/rsmp/listener.rb +4 -14
- data/lib/rsmp/logger.rb +3 -1
- data/lib/rsmp/logging.rb +1 -1
- data/lib/rsmp/message.rb +22 -31
- data/lib/rsmp/node.rb +11 -1
- data/lib/rsmp/notifier.rb +7 -2
- data/lib/rsmp/proxy.rb +95 -39
- data/lib/rsmp/rsmp.rb +34 -23
- data/lib/rsmp/site.rb +30 -32
- data/lib/rsmp/site_proxy.rb +103 -22
- data/lib/rsmp/site_proxy_wait.rb +63 -38
- data/lib/rsmp/supervisor.rb +82 -47
- data/lib/rsmp/supervisor_proxy.rb +21 -13
- data/lib/rsmp/tlc.rb +61 -35
- data/lib/rsmp/version.rb +1 -1
- data/rsmp.gemspec +3 -14
- metadata +13 -113
- data/config/site.yaml +0 -40
data/lib/rsmp/collector.rb
CHANGED
@@ -1,21 +1,35 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
#
|
1
|
+
# Collects matching ingoing and/or outgoing messages and
|
2
|
+
# wakes up the client once the desired amount has been collected.
|
3
|
+
# Can listen for ingoing and/or outgoing messages.
|
4
4
|
|
5
5
|
module RSMP
|
6
6
|
class Collector < Listener
|
7
|
-
|
7
|
+
|
8
|
+
attr_reader :condition, :messages, :done
|
8
9
|
|
9
10
|
def initialize proxy, options={}
|
10
|
-
#raise ArgumentError.new("timeout option is missing") unless options[:timeout]
|
11
11
|
super proxy, options
|
12
|
-
@
|
12
|
+
@ingoing = options[:ingoing] == nil ? true : options[:ingoing]
|
13
|
+
@outgoing = options[:outgoing] == nil ? false : options[:outgoing]
|
14
|
+
@messages = []
|
13
15
|
@condition = Async::Notification.new
|
14
16
|
@done = false
|
15
17
|
@options = options
|
16
18
|
@num = options[:num]
|
17
19
|
end
|
18
20
|
|
21
|
+
def inspect
|
22
|
+
"#<#{self.class.name}:#{self.object_id}, #{inspector(:@messages)}>"
|
23
|
+
end
|
24
|
+
|
25
|
+
def ingoing?
|
26
|
+
@ingoing == true
|
27
|
+
end
|
28
|
+
|
29
|
+
def outgoing?
|
30
|
+
@outgoing == true
|
31
|
+
end
|
32
|
+
|
19
33
|
def wait
|
20
34
|
@condition.wait
|
21
35
|
end
|
@@ -31,33 +45,33 @@ module RSMP
|
|
31
45
|
@options[:timeout] = options[:timeout] if options[:timeout]
|
32
46
|
@block = block
|
33
47
|
|
34
|
-
|
48
|
+
listen do
|
35
49
|
task.with_timeout(@options[:timeout]) do
|
36
50
|
@condition.wait
|
37
51
|
end
|
38
52
|
end
|
39
53
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
54
|
+
if @num == 1
|
55
|
+
@messages = @messages.first # if one message was requested, return it instead of array
|
56
|
+
else
|
57
|
+
@messages = @messages.first @num # return array, but ensure we never return more than requested
|
58
|
+
end
|
59
|
+
@messages
|
46
60
|
end
|
47
61
|
|
48
62
|
def reset
|
49
|
-
@
|
63
|
+
@message.clear
|
50
64
|
@done = false
|
51
65
|
end
|
52
66
|
|
53
|
-
def notify
|
54
|
-
raise ArgumentError unless
|
67
|
+
def notify message
|
68
|
+
raise ArgumentError unless message
|
55
69
|
return true if @done
|
56
|
-
return if
|
57
|
-
return if
|
58
|
-
if matches?
|
59
|
-
@
|
60
|
-
if @num && @
|
70
|
+
return if message.direction == :in && @ingoing == false
|
71
|
+
return if message.direction == :out && @outgoing == false
|
72
|
+
if matches? message
|
73
|
+
@messages << message
|
74
|
+
if @num && @messages.size >= @num
|
61
75
|
@done = true
|
62
76
|
@proxy.remove_listener self
|
63
77
|
@condition.signal
|
@@ -65,24 +79,22 @@ module RSMP
|
|
65
79
|
end
|
66
80
|
end
|
67
81
|
|
68
|
-
def matches?
|
69
|
-
raise ArgumentError unless
|
82
|
+
def matches? message
|
83
|
+
raise ArgumentError unless message
|
70
84
|
|
71
85
|
if @options[:type]
|
72
|
-
return false if
|
86
|
+
return false if message == nil
|
73
87
|
if @options[:type].is_a? Array
|
74
|
-
return false unless @options[:type].include?
|
88
|
+
return false unless @options[:type].include? message.type
|
75
89
|
else
|
76
|
-
return false unless
|
90
|
+
return false unless message.type == @options[:type]
|
77
91
|
end
|
78
92
|
end
|
79
|
-
return if @options[:level] && item[:level] != @options[:level]
|
80
|
-
return false if @options[:with_message] && !(item[:direction] && item[:message])
|
81
93
|
if @options[:component]
|
82
|
-
return false if
|
94
|
+
return false if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
|
83
95
|
end
|
84
96
|
if @block
|
85
|
-
return false if @block.call(
|
97
|
+
return false if @block.call(message) == false
|
86
98
|
end
|
87
99
|
true
|
88
100
|
end
|
data/lib/rsmp/component.rb
CHANGED
data/lib/rsmp/components.rb
CHANGED
@@ -0,0 +1,204 @@
|
|
1
|
+
# Import SXL from YAML format
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'json'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module RSMP
|
8
|
+
module Convert
|
9
|
+
module Export
|
10
|
+
module JSONSchema
|
11
|
+
|
12
|
+
@@json_options = {
|
13
|
+
array_nl: "\n",
|
14
|
+
object_nl: "\n",
|
15
|
+
indent: ' ',
|
16
|
+
space_before: ' ',
|
17
|
+
space: ' '
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.output_json item
|
21
|
+
JSON.generate(item,@@json_options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.build_value item
|
25
|
+
out = {}
|
26
|
+
|
27
|
+
if item['description']
|
28
|
+
out["description"] = item['description']
|
29
|
+
end
|
30
|
+
|
31
|
+
if item['list']
|
32
|
+
case item['type']
|
33
|
+
when "boolean"
|
34
|
+
out["$ref"] = "../../core/definitions.json#/boolean_list"
|
35
|
+
when "integer", "ordinal", "unit", "scale", "long"
|
36
|
+
out["$ref"] = "../../core/definitions.json#/integer_list"
|
37
|
+
else
|
38
|
+
raise "Error: List of #{item['type']} is not supported: #{item.inspect}"
|
39
|
+
end
|
40
|
+
|
41
|
+
if item["values"]
|
42
|
+
value_list = item["values"].keys.join('|')
|
43
|
+
out['pattern'] = /(?-mix:^(#{value_list})(?:,(#{value_list}))*$)/
|
44
|
+
end
|
45
|
+
|
46
|
+
if item["pattern"]
|
47
|
+
puts "Warning: Pattern not support for lists: #{item.inspect}"
|
48
|
+
end
|
49
|
+
else
|
50
|
+
case item['type']
|
51
|
+
when "string", "base64"
|
52
|
+
out["type"] = "string"
|
53
|
+
when "boolean"
|
54
|
+
out["$ref"] = "../../core/definitions.json#/boolean"
|
55
|
+
when "timestamp"
|
56
|
+
out["$ref"] = "../../core/definitions.json#/timestamp"
|
57
|
+
when "integer", "ordinal", "unit", "scale", "long"
|
58
|
+
out["$ref"] = "../../core/definitions.json#/integer"
|
59
|
+
else
|
60
|
+
out["type"] = "string"
|
61
|
+
end
|
62
|
+
|
63
|
+
if item["values"]
|
64
|
+
out["enum"] = item["values"].keys.sort
|
65
|
+
end
|
66
|
+
|
67
|
+
if item["pattern"]
|
68
|
+
out["pattern"] = item["pattern"]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
out
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.build_item item, property_key: 'v'
|
77
|
+
json = { "allOf" => [ { "description" => item['description'] } ] }
|
78
|
+
if item['arguments']
|
79
|
+
json["allOf"].first["properties"] = { "n" => { "enum" => item['arguments'].keys.sort } }
|
80
|
+
item['arguments'].each_pair do |key,argument|
|
81
|
+
json["allOf"] << {
|
82
|
+
"if" => { "required" => ["n"], "properties" => { "n" => { "const" => key }}},
|
83
|
+
"then" => { "properties" => { property_key => build_value(argument) } }
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
json
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.output_alarms out, items
|
91
|
+
list = items.keys.sort.map do |key|
|
92
|
+
{
|
93
|
+
"if" => { "required" => ["aCId"], "properties" => { "aCId" => { "const" => key }}},
|
94
|
+
"then" => { "$ref" => "#{key}.json" }
|
95
|
+
}
|
96
|
+
end
|
97
|
+
json = {
|
98
|
+
"properties" => {
|
99
|
+
"aCId" => { "enum" => items.keys.sort },
|
100
|
+
"rvs" => { "items" => { "allOf" => list } }
|
101
|
+
}
|
102
|
+
}
|
103
|
+
out['alarms/alarms.json'] = output_json json
|
104
|
+
items.each_pair { |key,item| output_alarm out, key, item }
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.output_alarm out, key, item
|
108
|
+
json = build_item item
|
109
|
+
out["alarms/#{key}.json"] = output_json json
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.output_statuses out, items
|
113
|
+
list = [ { "properties" => { "sCI" => { "enum"=> items.keys.sort }}} ]
|
114
|
+
items.keys.sort.each do |key|
|
115
|
+
list << {
|
116
|
+
"if"=> { "required" => ["sCI"], "properties" => { "sCI"=> { "const"=> key }}},
|
117
|
+
"then" => { "$ref" => "#{key}.json" }
|
118
|
+
}
|
119
|
+
end
|
120
|
+
json = { "properties" => { "sS" => { "items" => { "allOf" => list }}}}
|
121
|
+
out['statuses/statuses.json'] = output_json json
|
122
|
+
items.each_pair { |key,item| output_status out, key, item }
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.output_status out, key, item
|
126
|
+
json = build_item item, property_key:'s'
|
127
|
+
out["statuses/#{key}.json"] = output_json json
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.output_commands out, items
|
131
|
+
list = [ { "properties" => { "cCI" => { "enum"=> items.keys.sort }}} ]
|
132
|
+
items.keys.sort.each do |key|
|
133
|
+
list << {
|
134
|
+
"if" => { "required" => ["cCI"], "properties" => { "cCI"=> { "const"=> key }}},
|
135
|
+
"then" => { "$ref" => "#{key}.json" }
|
136
|
+
}
|
137
|
+
end
|
138
|
+
json = { "items" => { "allOf" => list }}
|
139
|
+
out['commands/commands.json'] = output_json json
|
140
|
+
|
141
|
+
json = { "properties" => { "arg" => { "$ref" => "commands.json" }}}
|
142
|
+
out['commands/command_requests.json'] = output_json json
|
143
|
+
|
144
|
+
json = { "properties" => { "rvs" => { "$ref" => "commands.json" }}}
|
145
|
+
out['commands/command_responses.json'] = output_json json
|
146
|
+
|
147
|
+
items.each_pair { |key,item| output_command out, key, item }
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.output_command out, key, item
|
151
|
+
json = build_item item
|
152
|
+
json["allOf"].first["properties"]['cO'] = { "const" => item['command'] }
|
153
|
+
out["commands/#{key}.json"] = output_json json
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.output_root out
|
157
|
+
json = {
|
158
|
+
"description"=> "A schema validatating message against the RSMP SXL for Traffic Light Controllers",
|
159
|
+
"allOf" => [
|
160
|
+
{
|
161
|
+
"if" => { "required" => ["type"], "properties" => { "type" => { "const" => "CommandRequest" }}},
|
162
|
+
"then" => { "$ref" => "commands/command_requests.json" }
|
163
|
+
},
|
164
|
+
{
|
165
|
+
"if" => { "required" => ["type"], "properties" => { "type" => { "const" => "CommandResponse" }}},
|
166
|
+
"then" => { "$ref" => "commands/command_responses.json" }
|
167
|
+
},
|
168
|
+
{
|
169
|
+
"if" => { "required" => ["type"], "properties" => { "type" => { "enum" => ["StatusRequest","StatusResponse","StatusSubscribe","StatusUnsubscribe","StatusUpdate"] }}},
|
170
|
+
"then" => { "$ref" => "statuses/statuses.json" }
|
171
|
+
},
|
172
|
+
{
|
173
|
+
"if" => { "required" => ["type"], "properties" => { "type" => { "const" => "Alarm" }}},
|
174
|
+
"then" => { "$ref" => "alarms/alarms.json" }
|
175
|
+
}
|
176
|
+
]
|
177
|
+
}
|
178
|
+
out["sxl.json"] = output_json json
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.generate sxl
|
182
|
+
out = {}
|
183
|
+
output_root out
|
184
|
+
output_alarms out, sxl[:alarms]
|
185
|
+
output_statuses out, sxl[:statuses]
|
186
|
+
output_commands out, sxl[:commands]
|
187
|
+
out
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.write sxl, folder
|
191
|
+
out = generate sxl
|
192
|
+
out.each_pair do |relative_path,str|
|
193
|
+
path = File.join(folder, relative_path)
|
194
|
+
FileUtils.mkdir_p File.dirname(path) # create folders if needed
|
195
|
+
file = File.open(path, 'w+') # w+ means truncate or create new file
|
196
|
+
file.puts str
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Import SXL from YAML format
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'json'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module RSMP
|
8
|
+
module Convert
|
9
|
+
module Import
|
10
|
+
module YAML
|
11
|
+
|
12
|
+
def self.read path
|
13
|
+
convert ::YAML.load_file(path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse str
|
17
|
+
convert ::YAML.load(str)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.convert yaml
|
21
|
+
sxl = {
|
22
|
+
alarms: {},
|
23
|
+
statuses: {},
|
24
|
+
commands: {}
|
25
|
+
}
|
26
|
+
|
27
|
+
yaml['objects'].each_pair do |type,object|
|
28
|
+
object["alarms"].each { |id,item| sxl[:alarms][id] = item }
|
29
|
+
object["statuses"].each { |id,item| sxl[:statuses][id] = item }
|
30
|
+
object["commands"].each { |id,item| sxl[:commands][id] = item }
|
31
|
+
end
|
32
|
+
sxl
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/rsmp/inspect.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Costume inspect, to reduce noise
|
2
|
+
#
|
3
|
+
# Instance variables of classes starting with Async or RSMP are shown
|
4
|
+
# with only their class name and object id, which reduces output,
|
5
|
+
# especially for deep object structures.
|
6
|
+
# Additionally, a list of variables to shown in short format can be passed.
|
7
|
+
#
|
8
|
+
# The short form is generated by using to_s() insted of inspect()
|
9
|
+
#
|
10
|
+
# Array#to_s and Hash#to_s usually show items, but here we show just number
|
11
|
+
# of items, when the short form is requested.
|
12
|
+
|
13
|
+
module RSMP
|
14
|
+
module Inspect
|
15
|
+
|
16
|
+
def inspector *short_items
|
17
|
+
instance_variables.map do |var_name|
|
18
|
+
var = instance_variable_get(var_name)
|
19
|
+
class_name = var.class.name
|
20
|
+
|
21
|
+
short = short_items.include?(var_name) ||
|
22
|
+
class_name.start_with?('Async') ||
|
23
|
+
class_name.start_with?('RSMP')
|
24
|
+
|
25
|
+
if short
|
26
|
+
if var.is_a? Array
|
27
|
+
"#{var_name}: #<#{class_name}:#{class_name.object_id}, #{var.size} items>"
|
28
|
+
elsif var.is_a? Hash
|
29
|
+
"#{var_name}: #<#{class_name}:#{class_name.object_id}, #{var.size} items>"
|
30
|
+
else
|
31
|
+
"#{var_name}: #{var.to_s}"
|
32
|
+
end
|
33
|
+
else
|
34
|
+
"#{var_name}: #{var.inspect}"
|
35
|
+
end
|
36
|
+
end.join(', ')
|
37
|
+
end
|
38
|
+
|
39
|
+
# override this if you want additional variable to be shown in the short format,
|
40
|
+
# or ottherwise change the inspect format
|
41
|
+
def inspect
|
42
|
+
"#<#{self.class.name}:#{self.object_id}, #{inspector}>"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/rsmp/listener.rb
CHANGED
@@ -1,28 +1,18 @@
|
|
1
|
-
# Receives
|
1
|
+
# Receives items from a Notifier, as long as it's
|
2
2
|
# installed as a listener.
|
3
|
-
# Can listen for ingoing and/or outgoing messages.
|
4
3
|
|
5
4
|
module RSMP
|
6
5
|
class Listener
|
6
|
+
include Inspect
|
7
7
|
|
8
8
|
def initialize proxy, options={}
|
9
9
|
@proxy = proxy
|
10
|
-
@ingoing = options[:ingoing] == nil ? true : options[:ingoing]
|
11
|
-
@outgoing = options[:outgoing] == nil ? false : options[:outgoing]
|
12
10
|
end
|
13
11
|
|
14
|
-
def
|
15
|
-
ingoing == true
|
12
|
+
def notify message
|
16
13
|
end
|
17
14
|
|
18
|
-
def
|
19
|
-
outgoing == true
|
20
|
-
end
|
21
|
-
|
22
|
-
def notify item
|
23
|
-
end
|
24
|
-
|
25
|
-
def siphon &block
|
15
|
+
def listen &block
|
26
16
|
@proxy.add_listener self
|
27
17
|
yield
|
28
18
|
ensure
|