rsmp 0.1.21 → 0.1.32

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,21 +1,35 @@
1
- # A probe checks incoming messages and store matches
2
- # Once it has collected what it needs, it triggers a condition variable
3
- # and the client wakes up.
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
- attr_reader :condition, :items, :done
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
- @items = []
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
- siphon do
48
+ listen do
35
49
  task.with_timeout(@options[:timeout]) do
36
50
  @condition.wait
37
51
  end
38
52
  end
39
53
 
40
- #if @num == 1
41
- # @items = @items.first # if one item was requested, return item instead of array
42
- #else
43
- # @items = @items.first @num # return array, but ensure we never return more than requested
44
- #end
45
- #@items
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
- @items.clear
63
+ @message.clear
50
64
  @done = false
51
65
  end
52
66
 
53
- def notify item
54
- raise ArgumentError unless item
67
+ def notify message
68
+ raise ArgumentError unless message
55
69
  return true if @done
56
- return if item[:message].direction == :in && @ingoing == false
57
- return if item[:message].direction == :out && @outgoing == false
58
- if matches? item
59
- @items << item
60
- if @num && @items.size >= @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? item
69
- raise ArgumentError unless item
82
+ def matches? message
83
+ raise ArgumentError unless message
70
84
 
71
85
  if @options[:type]
72
- return false if item[:message] == nil
86
+ return false if message == nil
73
87
  if @options[:type].is_a? Array
74
- return false unless @options[:type].include? item[:message].type
88
+ return false unless @options[:type].include? message.type
75
89
  else
76
- return false unless item[:message].type == @options[:type]
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 item[:message].attributes['cId'] && item[:message].attributes['cId'] != @options[:component]
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(item) == false
97
+ return false if @block.call(message) == false
86
98
  end
87
99
  true
88
100
  end
@@ -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,
@@ -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: true
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
@@ -0,0 +1,11 @@
1
+ class Hash
2
+ def deep_merge(other_hash)
3
+ self.merge(other_hash) do |key, old, fresh|
4
+ if old.is_a?(Hash) && fresh.is_a?(Hash)
5
+ old.deep_merge(fresh)
6
+ else
7
+ fresh
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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 messages from a Notifier, as long as it's
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 ingoing?
15
- ingoing == true
12
+ def notify message
16
13
  end
17
14
 
18
- def outgoing?
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