rsmp 0.1.12 → 0.1.27

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rsmp/cli.rb CHANGED
@@ -2,96 +2,135 @@ require 'thor'
2
2
  require 'rsmp'
3
3
 
4
4
  module RSMP
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
- def site
14
- settings = {}
15
- log_settings = { 'active' => true }
16
-
17
- if options[:config]
18
- if File.exist? options[:config]
19
- settings = YAML.load_file options[:config]
20
- log_settings = settings.delete 'log'
21
- else
22
- puts "Error: Config #{options[:config]} not found"
23
- exit
24
- end
25
- end
26
-
27
- if options[:id]
28
- settings['site_id'] = options[:id]
29
- end
30
-
31
- if options[:supervisors]
32
- options[:supervisors].split(',').each do |supervisor|
33
- settings[:supervisors] ||= []
34
- ip, port = supervisor.split ':'
35
- ip = '127.0.0.1' if ip.empty?
36
- port = '12111' if port.empty?
37
- settings[:supervisors] << {"ip"=>ip, "port"=>port}
38
- end
39
- end
40
-
41
- if options[:log]
42
- log_settings['path'] = options[:log]
43
- end
44
-
45
- if options[:json]
46
- log_settings['json'] = options[:json]
47
- end
48
-
49
- RSMP::Site.new(site_settings:settings, log_settings: log_settings).start
50
- end
51
-
52
- desc "supervisor", "Run RSMP supervisor"
53
- method_option :config, :type => :string, :aliases => "-c", banner: 'Path to .yaml config file'
54
- method_option :id, :type => :string, :aliases => "-i", banner: 'RSMP site id'
55
- method_option :ip, :type => :numeric, banner: 'IP address to listen on'
56
- method_option :port, :type => :string, :aliases => "-p", banner: 'Port to listen on'
57
- method_option :log, :type => :string, :aliases => "-l", banner: 'Path to log file'
58
- method_option :json, :type => :boolean, :aliases => "-j", banner: 'Show JSON messages in log'
59
- def supervisor
60
- settings = {}
61
- log_settings = { 'active' => true }
62
-
63
- if options[:config]
64
- if File.exist? options[:config]
65
- settings = YAML.load_file options[:config]
66
- log_settings = settings.delete 'log'
67
- else
68
- puts "Error: Config #{options[:config]} not found"
69
- exit
70
- end
71
- end
72
-
73
- if options[:id]
74
- settings['site_id'] = options[:id]
75
- end
76
-
77
- if options[:ip]
78
- settings['ip'] = options[:ip]
79
- end
80
-
81
- if options[:port]
82
- settings['port'] = options[:port]
83
- end
84
-
85
- if options[:log]
86
- log_settings['path'] = options[:log]
87
- end
88
-
89
- if options[:json]
90
- log_settings['json'] = options[:json]
91
- end
92
-
93
- RSMP::Supervisor.new(supervisor_settings:settings,log_settings:log_settings).start
94
- end
95
-
96
- end
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
+ end
65
+
66
+ desc "supervisor", "Run RSMP supervisor"
67
+ method_option :config, :type => :string, :aliases => "-c", banner: 'Path to .yaml config file'
68
+ method_option :id, :type => :string, :aliases => "-i", banner: 'RSMP site id'
69
+ method_option :ip, :type => :numeric, banner: 'IP address to listen on'
70
+ method_option :port, :type => :string, :aliases => "-p", banner: 'Port to listen on'
71
+ method_option :log, :type => :string, :aliases => "-l", banner: 'Path to log file'
72
+ method_option :json, :type => :boolean, :aliases => "-j", banner: 'Show JSON messages in log'
73
+ def supervisor
74
+ settings = {}
75
+ log_settings = { 'active' => true }
76
+
77
+ if options[:config]
78
+ if File.exist? options[:config]
79
+ settings = YAML.load_file options[:config]
80
+ log_settings = settings.delete 'log'
81
+ else
82
+ puts "Error: Config #{options[:config]} not found"
83
+ exit
84
+ end
85
+ end
86
+
87
+ if options[:id]
88
+ settings['site_id'] = options[:id]
89
+ end
90
+
91
+ if options[:ip]
92
+ settings['ip'] = options[:ip]
93
+ end
94
+
95
+ if options[:port]
96
+ settings['port'] = options[:port]
97
+ end
98
+
99
+ if options[:log]
100
+ log_settings['path'] = options[:log]
101
+ end
102
+
103
+ if options[:json]
104
+ log_settings['json'] = options[:json]
105
+ end
106
+
107
+ RSMP::Supervisor.new(supervisor_settings:settings,log_settings:log_settings).start
108
+ rescue RSMP::ConfigurationError => e
109
+ puts "Cannot start supervisor: #{e}"
110
+ end
111
+
112
+ desc "convert", "Convert SXL from YAML to JSON Schema"
113
+ method_option :in, :type => :string, :aliases => "-i", banner: 'Path to YAML input file'
114
+ method_option :out, :type => :string, :aliases => "-o", banner: 'Path to JSON Schema output file'
115
+ def convert
116
+ unless options[:in]
117
+ puts "Error: Input option missing"
118
+ exit
119
+ end
120
+
121
+ unless options[:out]
122
+ puts "Error: Output option missing"
123
+ exit
124
+ end
125
+
126
+ unless File.exist? options[:in]
127
+ puts "Error: Input path file #{options[:in]} not found"
128
+ exit
129
+ end
130
+
131
+ sxl = RSMP::Convert::Import::YAML.read options[:in]
132
+ RSMP::Convert::Export::JSONSchema.write sxl, options[:out]
133
+ end
134
+
135
+ end
97
136
  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
@@ -1,6 +1,8 @@
1
1
  module RSMP
2
2
  class Component
3
- attr_reader :c_id, :alarms, :statuses, :aggregated_status, :aggregated_status_bools, :grouped
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,7 +61,16 @@ module RSMP
58
61
  def alarm code:, status:
59
62
  end
60
63
 
61
- def status code:, value:
64
+ def log str, options
65
+ @node.log str, options
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}"
62
74
  end
63
75
 
64
76
  end
@@ -1,10 +1,10 @@
1
1
  # Things shared between sites and site proxies
2
2
 
3
3
  module RSMP
4
- module SiteBase
4
+ module Components
5
5
  attr_reader :components
6
6
 
7
- def initialize_site
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 |id,settings|
17
- @components[id] = build_component(id,settings)
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, settings={}
26
- Component.new id: id, node: self, grouped: true
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