rsmp 0.1.0

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.
@@ -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: []