rsmp 0.1.17 → 0.1.30

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,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 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
 
@@ -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
data/lib/rsmp/error.rb CHANGED
@@ -23,7 +23,7 @@ module RSMP
23
23
  class MissingWatchdog < Error
24
24
  end
25
25
 
26
- class MissingAcknowledgment < Error
26
+ class MessageRejected < Error
27
27
  end
28
28
 
29
29
  class MissingAttribute < InvalidMessage
@@ -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