rsmp 0.1.17 → 0.1.30
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.gitmodules +0 -4
- data/Gemfile.lock +23 -19
- data/README.md +1 -1
- data/config/supervisor.yaml +9 -15
- data/config/tlc.yaml +12 -10
- data/documentation/classes_and_modules.md +65 -0
- data/documentation/message_distribution.md +23 -0
- data/lib/rsmp.rb +14 -5
- data/lib/rsmp/archive.rb +23 -22
- data/lib/rsmp/cli.rb +133 -102
- data/lib/rsmp/collector.rb +102 -0
- data/lib/rsmp/component.rb +3 -0
- data/lib/rsmp/{site_base.rb → components.rb} +3 -3
- 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/error.rb +1 -1
- data/lib/rsmp/inspect.rb +46 -0
- data/lib/rsmp/listener.rb +23 -0
- data/lib/rsmp/logger.rb +6 -4
- data/lib/rsmp/{base.rb → logging.rb} +3 -3
- data/lib/rsmp/message.rb +46 -32
- data/lib/rsmp/node.rb +47 -12
- data/lib/rsmp/notifier.rb +29 -0
- data/lib/rsmp/proxy.rb +143 -71
- data/lib/rsmp/rsmp.rb +34 -23
- data/lib/rsmp/site.rb +35 -34
- data/lib/rsmp/site_proxy.rb +180 -98
- data/lib/rsmp/site_proxy_wait.rb +206 -0
- data/lib/rsmp/supervisor.rb +85 -49
- data/lib/rsmp/supervisor_proxy.rb +70 -27
- data/lib/rsmp/tlc.rb +252 -90
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp/wait.rb +7 -8
- data/rsmp.gemspec +4 -15
- metadata +27 -118
- data/config/site.yaml +0 -40
- data/lib/rsmp/probe.rb +0 -114
- data/lib/rsmp/probe_collection.rb +0 -28
- data/lib/rsmp/supervisor_base.rb +0 -10
@@ -0,0 +1,102 @@
|
|
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
|
+
|
5
|
+
module RSMP
|
6
|
+
class Collector < Listener
|
7
|
+
|
8
|
+
attr_reader :condition, :messages, :done
|
9
|
+
|
10
|
+
def initialize proxy, options={}
|
11
|
+
super proxy, options
|
12
|
+
@ingoing = options[:ingoing] == nil ? true : options[:ingoing]
|
13
|
+
@outgoing = options[:outgoing] == nil ? false : options[:outgoing]
|
14
|
+
@messages = []
|
15
|
+
@condition = Async::Notification.new
|
16
|
+
@done = false
|
17
|
+
@options = options
|
18
|
+
@num = options[:num]
|
19
|
+
end
|
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
|
+
|
33
|
+
def wait
|
34
|
+
@condition.wait
|
35
|
+
end
|
36
|
+
|
37
|
+
def collect_for task, duration
|
38
|
+
siphon do
|
39
|
+
task.sleep duration
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def collect task, options={}, &block
|
44
|
+
@num = options[:num] if options[:num]
|
45
|
+
@options[:timeout] = options[:timeout] if options[:timeout]
|
46
|
+
@block = block
|
47
|
+
|
48
|
+
listen do
|
49
|
+
task.with_timeout(@options[:timeout]) do
|
50
|
+
@condition.wait
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
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
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset
|
63
|
+
@message.clear
|
64
|
+
@done = false
|
65
|
+
end
|
66
|
+
|
67
|
+
def notify message
|
68
|
+
raise ArgumentError unless message
|
69
|
+
return true if @done
|
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
|
75
|
+
@done = true
|
76
|
+
@proxy.remove_listener self
|
77
|
+
@condition.signal
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def matches? message
|
83
|
+
raise ArgumentError unless message
|
84
|
+
|
85
|
+
if @options[:type]
|
86
|
+
return false if message == nil
|
87
|
+
if @options[:type].is_a? Array
|
88
|
+
return false unless @options[:type].include? message.type
|
89
|
+
else
|
90
|
+
return false unless message.type == @options[:type]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
if @options[:component]
|
94
|
+
return false if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
|
95
|
+
end
|
96
|
+
if @block
|
97
|
+
return false if @block.call(message) == false
|
98
|
+
end
|
99
|
+
true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/rsmp/component.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module RSMP
|
2
2
|
class Component
|
3
|
+
include Inspect
|
4
|
+
|
3
5
|
attr_reader :c_id, :node, :alarms, :statuses, :aggregated_status, :aggregated_status_bools, :grouped
|
4
6
|
|
5
7
|
AGGREGATED_STATUS_KEYS = [ :local_control,
|
@@ -23,6 +25,7 @@ module RSMP
|
|
23
25
|
def clear_aggregated_status
|
24
26
|
@aggregated_status = []
|
25
27
|
@aggregated_status_bools = Array.new(8,false)
|
28
|
+
@aggregated_status_bools[5] = true
|
26
29
|
end
|
27
30
|
|
28
31
|
def set_aggregated_status status
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# Things shared between sites and site proxies
|
2
2
|
|
3
3
|
module RSMP
|
4
|
-
module
|
4
|
+
module Components
|
5
5
|
attr_reader :components
|
6
6
|
|
7
|
-
def
|
7
|
+
def initialize_components
|
8
8
|
@components = {}
|
9
9
|
end
|
10
10
|
|
@@ -25,7 +25,7 @@ module RSMP
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def build_component id:, type:, settings:{}
|
28
|
-
Component.new id:id, node: self, grouped:
|
28
|
+
Component.new id:id, node: self, grouped: type=='main'
|
29
29
|
end
|
30
30
|
|
31
31
|
def find_component component_id
|
@@ -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/error.rb
CHANGED
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
|