rsmp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,292 @@
1
+ # Handles a site connection to a remote supervisor
2
+
3
+ module RSMP
4
+ class SupervisorProxy < Proxy
5
+
6
+ attr_reader :supervisor_id, :site
7
+
8
+ def initialize options
9
+ super options
10
+ @site = options[:site]
11
+ @site_settings = @site.site_settings.clone
12
+ @ip = options[:ip]
13
+ @port = options[:port]
14
+ @status_subscriptions = {}
15
+ @status_subscriptions_mutex = Mutex.new
16
+ end
17
+
18
+ def node
19
+ site
20
+ end
21
+
22
+ def start
23
+ log "Connecting to superviser at #{@ip}:#{@port}", level: :info
24
+ super
25
+ connect
26
+ @logger.unmute @ip, @port
27
+ start_reader
28
+ send_version @site_settings["rsmp_versions"]
29
+ rescue Errno::ECONNREFUSED
30
+ log "No connection to supervisor at #{@ip}:#{@port}", level: :error
31
+ unless @site.site_settings["reconnect_interval"] == :no
32
+ log "Will try to reconnect again every #{@site.site_settings["reconnect_interval"]} seconds..", level: :info
33
+ @logger.mute @ip, @port
34
+ end
35
+ end
36
+
37
+ def connect
38
+ return if @socket
39
+ @endpoint = Async::IO::Endpoint.tcp(@ip, @port)
40
+ @socket = @endpoint.connect
41
+ @stream = Async::IO::Stream.new(@socket)
42
+ @protocol = Async::IO::Protocol::Line.new(@stream,"\f") # rsmp messages are json terminated with a form-feed
43
+ end
44
+
45
+ def connection_complete
46
+ super
47
+ log "Connection to supervisor established", level: :info
48
+ start_watchdog
49
+ end
50
+
51
+ def process_message message
52
+ case message
53
+ when Alarm
54
+ when StatusResponse
55
+ when StatusUpdate
56
+ when AggregatedStatus
57
+ will_not_handle message
58
+ when CommandRequest
59
+ process_command_request message
60
+ when CommandResponse
61
+ process_command_response message
62
+ when StatusRequest
63
+ process_status_request message
64
+ when StatusSubscribe
65
+ process_status_subcribe message
66
+ when StatusUnsubscribe
67
+ process_status_unsubcribe message
68
+ else
69
+ super message
70
+ end
71
+ end
72
+
73
+ def acknowledged_first_ingoing message
74
+ # TODO
75
+ # aggregateds status should only be send for later version of rsmp
76
+ # to handle verison differences, we probably need inherited classes
77
+ case message.type
78
+ when "Watchdog"
79
+ if @site_settings['send_after_connect']
80
+ send_all_aggregated_status
81
+ end
82
+ end
83
+ end
84
+
85
+ def send_all_aggregated_status
86
+ @site.components.each_pair do |c_id,component|
87
+ if component.grouped
88
+ send_aggregated_status component
89
+ end
90
+ end
91
+ end
92
+
93
+ def reconnect_delay
94
+ interval = @site_settings["reconnect_interval"]
95
+ log "Waiting #{interval} seconds before trying to reconnect", level: :info
96
+ @task.sleep interval
97
+ end
98
+
99
+ def version_accepted message, rsmp_version
100
+ log "Received Version message, using RSMP #{rsmp_version}", message: message, level: :log
101
+ start_timer
102
+ acknowledge message
103
+ connection_complete
104
+ @version_determined = true
105
+ end
106
+
107
+ def send_aggregated_status component
108
+ message = AggregatedStatus.new({
109
+ "aSTS" => RSMP.now_string,
110
+ "cId" => component.c_id,
111
+ "fP" => nil,
112
+ "fS" => nil,
113
+ "se" => component.aggregated_status_bools
114
+ })
115
+ send_message message
116
+ end
117
+
118
+ def process_aggregated_status message
119
+ se = message.attribute("se")
120
+ validate_aggregated_status(message,se) == false
121
+ on = set_aggregated_status se
122
+ log "Received #{message.type} status [#{on.join(', ')}]", message: message, level: :log
123
+ acknowledge message
124
+ end
125
+
126
+ def process_alarm message
127
+ alarm_code = message.attribute("aCId")
128
+ asp = message.attribute("aSp")
129
+ status = ["ack","aS","sS"].map { |key| message.attribute(key) }.join(',')
130
+ log "Received #{message.type}, #{alarm_code} #{asp} [#{status}]", message: message, level: :log
131
+ acknowledge message
132
+ end
133
+
134
+ def process_command_request message
135
+ log "Received #{message.type}", message: message, level: :log
136
+ rvs = []
137
+ message.attributes["arg"].each do |arg|
138
+ unless arg['cCI'] && arg['n'] && arg['v']
139
+ dont_acknowledge message, '', 'bad arguments'
140
+ return
141
+ end
142
+ rvs << { "cCI" => arg["cCI"],
143
+ "n" => arg["n"],
144
+ "v" => arg["v"],
145
+ "age" => "recent" }
146
+ end
147
+
148
+ response = CommandResponse.new({
149
+ "cId"=>message.attributes["cId"],
150
+ "cTS"=>RSMP.now_string,
151
+ "rvs"=>rvs
152
+ })
153
+ acknowledge message
154
+ send_message response
155
+ end
156
+
157
+ def process_status_request message
158
+ log "Received #{message.type}", message: message, level: :log
159
+ sS = message.attributes["sS"].clone.map do |request|
160
+ request["s"] = rand(100).to_s
161
+ request["q"] = "recent"
162
+ request
163
+ end
164
+ response = StatusResponse.new({
165
+ "cId"=>message.attributes["cId"],
166
+ "sTs"=>RSMP.now_string,
167
+ "sS"=>sS
168
+ })
169
+ acknowledge message
170
+ send_message response
171
+ end
172
+
173
+ def process_status_subcribe message
174
+ log "Received #{message.type}", message: message, level: :log
175
+
176
+ # @status_subscriptions is organized by component/code/name, for example:
177
+ #
178
+ # {"AA+BBCCC=DDDEE002"=>{"S001"=>["number"]}}
179
+ #
180
+ # This is done to make it easy to send a single status update
181
+ # for each component, containing all the requested statuses
182
+
183
+ update_list = {}
184
+
185
+ component = message.attributes["cId"]
186
+
187
+ @status_subscriptions[component] ||= {}
188
+ update_list[component] ||= {}
189
+
190
+ message.attributes["sS"].each do |arg|
191
+ subcription = {interval: arg["uRt"].to_i, last_sent_at: nil}
192
+ @status_subscriptions[component][arg["sCI"]] ||= {}
193
+ @status_subscriptions[component][arg["sCI"]][arg["n"]] = subcription
194
+
195
+ update_list[component][arg["sCI"]] ||= []
196
+ update_list[component][arg["sCI"]] << arg["n"]
197
+ end
198
+ acknowledge message
199
+ send_status_updates update_list # send status after subscribing is accepted
200
+ end
201
+
202
+ def process_status_unsubcribe message
203
+ log "Received #{message.type}", message: message, level: :log
204
+ component = message.attributes["cId"]
205
+
206
+ if @status_subscriptions[component]
207
+ message.attributes["sS"].each do |arg|
208
+ if @status_subscriptions[component][arg["sCI"]]
209
+ @status_subscriptions[component][arg["sCI"]].delete arg["n"]
210
+ end
211
+ if @status_subscriptions[component][arg["sCI"]].empty?
212
+ @status_subscriptions[component].delete(arg["sCI"])
213
+ end
214
+ end
215
+ if @status_subscriptions[component].empty?
216
+ @status_subscriptions.delete(component)
217
+ end
218
+ end
219
+ acknowledge message
220
+ end
221
+
222
+ def timer now
223
+ super
224
+ status_update_timer now if ready?
225
+ end
226
+
227
+ def status_update_timer now
228
+ update_list = {}
229
+ # go through subscriptons and build a similarly organized list,
230
+ # that only contains what should be send
231
+
232
+ @status_subscriptions.each_pair do |component,by_code|
233
+ by_code.each_pair do |code,by_name|
234
+ by_name.each_pair do |name,subscription|
235
+ if subscription[:interval] == 0
236
+ # send as soon as the data changes
237
+ if rand(100) >= 90
238
+ should_send = true
239
+ end
240
+ else
241
+ # send at regular intervals
242
+ if subscription[:last_sent_at] == nil || (now - subscription[:last_sent_at]) >= subscription[:interval]
243
+ should_send = true
244
+ end
245
+ end
246
+ if should_send
247
+ subscription[:last_sent_at] = now
248
+ update_list[component] ||= {}
249
+ update_list[component][code] ||= []
250
+ update_list[component][code] << name
251
+ end
252
+ end
253
+ end
254
+ end
255
+ send_status_updates update_list
256
+ rescue StandardError => e
257
+ log ["Status update exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
258
+ end
259
+
260
+ def send_status_updates update_list
261
+ now = RSMP.now_string
262
+ update_list.each_pair do |component,by_code|
263
+ sS = []
264
+ by_code.each_pair do |code,names|
265
+ names.each do |name|
266
+ sS << { "sCI": code,
267
+ "n": name,
268
+ "s": rand(100),
269
+ "q": "recent" }
270
+ end
271
+ end
272
+ update = StatusUpdate.new({
273
+ "cId"=>component,
274
+ "sTs"=>now,
275
+ "sS"=>sS
276
+ })
277
+ send_message update
278
+ end
279
+ end
280
+
281
+ def send_alarm
282
+ message = Alarm.new({
283
+ "aSTS"=>RSMP.now_string,
284
+ "fP"=>nil,
285
+ "fS"=>nil,
286
+ "se"=>@site.aggregated_status_bools
287
+ })
288
+ send_message message
289
+ end
290
+
291
+ end
292
+ end
@@ -0,0 +1,3 @@
1
+ module RSMP
2
+ VERSION = "0.1.0"
3
+ end
data/lib/rsmp/wait.rb ADDED
@@ -0,0 +1,17 @@
1
+ # Helper for waiting for an Async condition using a block
2
+
3
+ module RSMP
4
+ class Wait
5
+
6
+ def self.wait_for task, condition, timeout, &block
7
+ task.with_timeout(timeout) do
8
+ loop do
9
+ value = condition.wait
10
+ result = yield value
11
+ return result if result # return result of check, if not nil
12
+ end
13
+ end
14
+ end
15
+
16
+ end
17
+ end
data/lib/rsmp.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'yaml'
2
+ require 'socket'
3
+ require 'time'
4
+ require 'async/io'
5
+ require 'async/io/protocol/line'
6
+ require 'colorize'
7
+ require 'json'
8
+ require 'securerandom'
9
+ require 'json_schemer'
10
+ require 'async/queue'
11
+
12
+ require 'rsmp/rsmp'
13
+ require 'rsmp/base'
14
+ require 'rsmp/wait'
15
+ require 'rsmp/version'
16
+ require 'rsmp/node'
17
+ require 'rsmp/supervisor'
18
+ require 'rsmp/component'
19
+ require 'rsmp/site_base'
20
+ require 'rsmp/site'
21
+ require 'rsmp/proxy'
22
+ require 'rsmp/supervisor_proxy'
23
+ require 'rsmp/site_proxy'
24
+ require 'rsmp/error'
25
+ require 'rsmp/probe'
26
+ require 'rsmp/probe_collection'
27
+ require 'rsmp/message'
28
+ require 'rsmp/logger'
29
+ require 'rsmp/archive'
data/rsmp.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "rsmp/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "rsmp"
7
+ spec.version = RSMP::VERSION
8
+ spec.authors = ["Emil Tin"]
9
+ spec.email = ["zf0f@kk.dk"]
10
+
11
+ spec.summary = %q{RoadSide Message Protocol (RSMP) library.}
12
+ spec.description = %q{Easy RSMP site and supervisor communication.}
13
+ spec.homepage = "https://github.com/rsmp-nordic/rsmp"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/rsmp-nordic/rsmp"
17
+ spec.metadata["changelog_uri"] = "https://github.com/rsmp-nordic/rsmp/blob/master/CHANGELOG.md"
18
+ spec.metadata["bug_tracker_uri"] = "https://github.com/rsmp-nordic/rsmp/issues"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.add_dependency "async", "~> 1.23.0"
30
+ spec.add_dependency "async-io", "~> 1.27.0"
31
+ spec.add_dependency "colorize", "~> 0.8.1"
32
+ spec.add_dependency "thor", "~> 0.20.3"
33
+ spec.add_dependency "json_schemer", "~> 0.2.8"
34
+
35
+ spec.add_development_dependency "bundler", "~> 2.0"
36
+ spec.add_development_dependency "rake", "~> 10.0"
37
+ spec.add_development_dependency "rspec", "~> 3.0"
38
+ spec.add_development_dependency "rspec-expectations", "~> 3.8.3"
39
+ spec.add_development_dependency "rspec-with_params", "~> 0.2.0"
40
+ spec.add_development_dependency "timecop", "~> 0.9.1"
41
+ spec.add_development_dependency "cucumber", "~> 3.1.2"
42
+ spec.add_development_dependency "aruba", "~> 0.14.11"
43
+ end
metadata ADDED
@@ -0,0 +1,266 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rsmp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Emil Tin
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-11-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: async
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.23.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.23.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: async-io
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.27.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.27.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: colorize
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.8.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.20.3
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.20.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: json_schemer
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.2.8
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.2.8
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '10.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '10.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec-expectations
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 3.8.3
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 3.8.3
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec-with_params
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.2.0
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.2.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: timecop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 0.9.1
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 0.9.1
167
+ - !ruby/object:Gem::Dependency
168
+ name: cucumber
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 3.1.2
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 3.1.2
181
+ - !ruby/object:Gem::Dependency
182
+ name: aruba
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 0.14.11
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.14.11
195
+ description: Easy RSMP site and supervisor communication.
196
+ email:
197
+ - zf0f@kk.dk
198
+ executables:
199
+ - rsmp
200
+ extensions: []
201
+ extra_rdoc_files: []
202
+ files:
203
+ - ".gitignore"
204
+ - ".gitmodules"
205
+ - ".rspec"
206
+ - CHANGELOG.md
207
+ - Gemfile
208
+ - Gemfile.lock
209
+ - LICENSE
210
+ - README.md
211
+ - Rakefile
212
+ - bin/console
213
+ - bin/setup
214
+ - config/site.yaml
215
+ - config/supervisor.yaml
216
+ - exe/rsmp
217
+ - lib/rsmp.rb
218
+ - lib/rsmp/alarm.rb
219
+ - lib/rsmp/archive.rb
220
+ - lib/rsmp/base.rb
221
+ - lib/rsmp/cli.rb
222
+ - lib/rsmp/component.rb
223
+ - lib/rsmp/error.rb
224
+ - lib/rsmp/logger.rb
225
+ - lib/rsmp/message.rb
226
+ - lib/rsmp/node.rb
227
+ - lib/rsmp/probe.rb
228
+ - lib/rsmp/probe_collection.rb
229
+ - lib/rsmp/proxy.rb
230
+ - lib/rsmp/rsmp.rb
231
+ - lib/rsmp/site.rb
232
+ - lib/rsmp/site_base.rb
233
+ - lib/rsmp/site_proxy.rb
234
+ - lib/rsmp/supervisor.rb
235
+ - lib/rsmp/supervisor_base.rb
236
+ - lib/rsmp/supervisor_proxy.rb
237
+ - lib/rsmp/version.rb
238
+ - lib/rsmp/wait.rb
239
+ - rsmp.gemspec
240
+ homepage: https://github.com/rsmp-nordic/rsmp
241
+ licenses: []
242
+ metadata:
243
+ homepage_uri: https://github.com/rsmp-nordic/rsmp
244
+ source_code_uri: https://github.com/rsmp-nordic/rsmp
245
+ changelog_uri: https://github.com/rsmp-nordic/rsmp/blob/master/CHANGELOG.md
246
+ bug_tracker_uri: https://github.com/rsmp-nordic/rsmp/issues
247
+ post_install_message:
248
+ rdoc_options: []
249
+ require_paths:
250
+ - lib
251
+ required_ruby_version: !ruby/object:Gem::Requirement
252
+ requirements:
253
+ - - ">="
254
+ - !ruby/object:Gem::Version
255
+ version: '0'
256
+ required_rubygems_version: !ruby/object:Gem::Requirement
257
+ requirements:
258
+ - - ">="
259
+ - !ruby/object:Gem::Version
260
+ version: '0'
261
+ requirements: []
262
+ rubygems_version: 3.0.3
263
+ signing_key:
264
+ specification_version: 4
265
+ summary: RoadSide Message Protocol (RSMP) library.
266
+ test_files: []