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.
@@ -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