rsmp 0.1.13 → 0.1.29
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/.gitignore +1 -0
- data/.gitmodules +0 -4
- data/Gemfile.lock +52 -52
- data/README.md +7 -1
- data/config/supervisor.yaml +9 -15
- data/config/tlc.yaml +26 -28
- 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 +13 -4
- data/lib/rsmp/{site_base.rb → components.rb} +8 -6
- 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 -42
- data/lib/rsmp/site_proxy.rb +182 -78
- data/lib/rsmp/site_proxy_wait.rb +206 -0
- data/lib/rsmp/supervisor.rb +85 -49
- data/lib/rsmp/supervisor_proxy.rb +80 -34
- data/lib/rsmp/tlc.rb +761 -86
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp/wait.rb +7 -8
- data/rsmp.gemspec +9 -21
- metadata +37 -142
- 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
data/lib/rsmp/cli.rb
CHANGED
@@ -2,106 +2,137 @@ require 'thor'
|
|
2
2
|
require 'rsmp'
|
3
3
|
|
4
4
|
module RSMP
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
5
|
+
class CLI < Thor
|
6
|
+
|
7
|
+
desc "site", "Run RSMP site"
|
8
|
+
method_option :config, :type => :string, :aliases => "-c", banner: 'Path to .yaml config file'
|
9
|
+
method_option :id, :type => :string, :aliases => "-i", banner: 'RSMP site id'
|
10
|
+
method_option :supervisors, :type => :string, :aliases => "-s", banner: 'ip:port,... list of supervisor to connect to'
|
11
|
+
method_option :log, :type => :string, :aliases => "-l", banner: 'Path to log file'
|
12
|
+
method_option :json, :type => :boolean, :aliases => "-j", banner: 'Show JSON messages in log'
|
13
|
+
method_option :type, :type => :string, :aliases => "-t", banner: 'Type of site: [tlc]'
|
14
|
+
def site
|
15
|
+
settings = {}
|
16
|
+
log_settings = { 'active' => true }
|
17
|
+
|
18
|
+
if options[:config]
|
19
|
+
if File.exist? options[:config]
|
20
|
+
settings = YAML.load_file options[:config]
|
21
|
+
log_settings = settings.delete('log') || {}
|
22
|
+
else
|
23
|
+
puts "Error: Config #{options[:config]} not found"
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if options[:id]
|
29
|
+
settings['site_id'] = options[:id]
|
30
|
+
end
|
31
|
+
|
32
|
+
if options[:supervisors]
|
33
|
+
options[:supervisors].split(',').each do |supervisor|
|
34
|
+
settings['supervisors'] ||= []
|
35
|
+
ip, port = supervisor.split ':'
|
36
|
+
ip = '127.0.0.1' if ip.empty?
|
37
|
+
port = '12111' if port.empty?
|
38
|
+
settings['supervisors'] << {"ip"=>ip, "port"=>port}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if options[:log]
|
43
|
+
log_settings['path'] = options[:log]
|
44
|
+
end
|
45
|
+
|
46
|
+
if options[:json]
|
47
|
+
log_settings['json'] = options[:json]
|
48
|
+
end
|
49
|
+
|
50
|
+
site_class = RSMP::Site
|
51
|
+
if options[:type]
|
52
|
+
case options[:type]
|
53
|
+
when 'tlc'
|
54
|
+
site_class = RSMP::Tlc
|
55
|
+
else
|
56
|
+
site_class = RSMP::Site
|
57
|
+
end
|
58
|
+
end
|
59
|
+
site_class.new(site_settings:settings, log_settings: log_settings).start
|
60
|
+
rescue RSMP::Schemer::UnknownSchemaTypeError => e
|
61
|
+
puts "Cannot start site: #{e}"
|
62
|
+
rescue RSMP::Schemer::UnknownSchemaVersionError => e
|
63
|
+
puts "Cannot start site: #{e}"
|
64
|
+
rescue Psych::SyntaxError => e
|
65
|
+
puts "Cannot read config file #{e}"
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "supervisor", "Run RSMP supervisor"
|
69
|
+
method_option :config, :type => :string, :aliases => "-c", banner: 'Path to .yaml config file'
|
70
|
+
method_option :id, :type => :string, :aliases => "-i", banner: 'RSMP site id'
|
71
|
+
method_option :ip, :type => :numeric, banner: 'IP address to listen on'
|
72
|
+
method_option :port, :type => :string, :aliases => "-p", banner: 'Port to listen on'
|
73
|
+
method_option :log, :type => :string, :aliases => "-l", banner: 'Path to log file'
|
74
|
+
method_option :json, :type => :boolean, :aliases => "-j", banner: 'Show JSON messages in log'
|
75
|
+
def supervisor
|
76
|
+
settings = {}
|
77
|
+
log_settings = { 'active' => true }
|
78
|
+
|
79
|
+
if options[:config]
|
80
|
+
if File.exist? options[:config]
|
81
|
+
settings = YAML.load_file options[:config]
|
82
|
+
log_settings = settings.delete 'log'
|
83
|
+
else
|
84
|
+
puts "Error: Config #{options[:config]} not found"
|
85
|
+
exit
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if options[:id]
|
90
|
+
settings['site_id'] = options[:id]
|
91
|
+
end
|
92
|
+
|
93
|
+
if options[:ip]
|
94
|
+
settings['ip'] = options[:ip]
|
95
|
+
end
|
96
|
+
|
97
|
+
if options[:port]
|
98
|
+
settings['port'] = options[:port]
|
99
|
+
end
|
100
|
+
|
101
|
+
if options[:log]
|
102
|
+
log_settings['path'] = options[:log]
|
103
|
+
end
|
104
|
+
|
105
|
+
if options[:json]
|
106
|
+
log_settings['json'] = options[:json]
|
107
|
+
end
|
108
|
+
|
109
|
+
RSMP::Supervisor.new(supervisor_settings:settings,log_settings:log_settings).start
|
110
|
+
rescue RSMP::ConfigurationError => e
|
111
|
+
puts "Cannot start supervisor: #{e}"
|
112
|
+
end
|
113
|
+
|
114
|
+
desc "convert", "Convert SXL from YAML to JSON Schema"
|
115
|
+
method_option :in, :type => :string, :aliases => "-i", banner: 'Path to YAML input file'
|
116
|
+
method_option :out, :type => :string, :aliases => "-o", banner: 'Path to JSON Schema output file'
|
117
|
+
def convert
|
118
|
+
unless options[:in]
|
119
|
+
puts "Error: Input option missing"
|
120
|
+
exit
|
121
|
+
end
|
122
|
+
|
123
|
+
unless options[:out]
|
124
|
+
puts "Error: Output option missing"
|
125
|
+
exit
|
126
|
+
end
|
127
|
+
|
128
|
+
unless File.exist? options[:in]
|
129
|
+
puts "Error: Input path file #{options[:in]} not found"
|
130
|
+
exit
|
131
|
+
end
|
132
|
+
|
133
|
+
sxl = RSMP::Convert::Import::YAML.read options[:in]
|
134
|
+
RSMP::Convert::Export::JSONSchema.write sxl, options[:out]
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
107
138
|
end
|
@@ -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,6 +1,8 @@
|
|
1
1
|
module RSMP
|
2
2
|
class Component
|
3
|
-
|
3
|
+
include Inspect
|
4
|
+
|
5
|
+
attr_reader :c_id, :node, :alarms, :statuses, :aggregated_status, :aggregated_status_bools, :grouped
|
4
6
|
|
5
7
|
AGGREGATED_STATUS_KEYS = [ :local_control,
|
6
8
|
:communication_distruption,
|
@@ -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
|
@@ -58,11 +61,17 @@ module RSMP
|
|
58
61
|
def alarm code:, status:
|
59
62
|
end
|
60
63
|
|
61
|
-
def status code:, value:
|
62
|
-
end
|
63
|
-
|
64
64
|
def log str, options
|
65
65
|
@node.log str, options
|
66
66
|
end
|
67
|
+
|
68
|
+
def handle_command command_code, arg
|
69
|
+
raise UnknownCommand.new "Command #{command_code} not implemented by #{self.class}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_status status_code, status_name=nil
|
73
|
+
raise UnknownStatus.new "Status #{status_code}/#{status_name} not implemented by #{self.class}"
|
74
|
+
end
|
75
|
+
|
67
76
|
end
|
68
77
|
end
|
@@ -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
|
|
@@ -13,8 +13,10 @@ module RSMP
|
|
13
13
|
|
14
14
|
def setup_components settings
|
15
15
|
return unless settings
|
16
|
-
settings.each_pair do |
|
17
|
-
|
16
|
+
settings.each_pair do |type,components_by_type|
|
17
|
+
components_by_type.each_pair do |id,settings|
|
18
|
+
@components[id] = build_component(id:id, type:type, settings:settings)
|
19
|
+
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
@@ -22,8 +24,8 @@ module RSMP
|
|
22
24
|
@components[component.c_id] = component
|
23
25
|
end
|
24
26
|
|
25
|
-
def build_component id
|
26
|
-
Component.new id:
|
27
|
+
def build_component id:, type:, settings:{}
|
28
|
+
Component.new id:id, node: self, grouped: type=='main'
|
27
29
|
end
|
28
30
|
|
29
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
|