automate-em 0.0.1
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.
- data/LGPL3-LICENSE +165 -0
- data/README.textile +48 -0
- data/Rakefile +40 -0
- data/app/models/control_system.rb +20 -0
- data/app/models/controller_device.rb +21 -0
- data/app/models/controller_http_service.rb +17 -0
- data/app/models/controller_logic.rb +5 -0
- data/app/models/controller_zone.rb +10 -0
- data/app/models/dependency.rb +20 -0
- data/app/models/server.rb +12 -0
- data/app/models/setting.rb +38 -0
- data/app/models/trusted_device.rb +63 -0
- data/app/models/user_zone.rb +10 -0
- data/app/models/zone.rb +16 -0
- data/db/migrate/20111001022500_init.rb +147 -0
- data/db/migrate/20111017213801_one_time_key.rb +9 -0
- data/db/migrate/20111021071632_encrypt_setting.rb +9 -0
- data/db/migrate/20111110075444_servers.rb +15 -0
- data/db/migrate/20111114074538_default_port.rb +9 -0
- data/db/migrate/20111122073055_makebreak.rb +9 -0
- data/db/migrate/20111211062846_create_controller_http_services.rb +18 -0
- data/lib/automate-em.rb +155 -0
- data/lib/automate-em/constants.rb +6 -0
- data/lib/automate-em/core/communicator.rb +318 -0
- data/lib/automate-em/core/modules.rb +373 -0
- data/lib/automate-em/core/resolver_pool.rb +76 -0
- data/lib/automate-em/core/system.rb +356 -0
- data/lib/automate-em/device/datagram_server.rb +111 -0
- data/lib/automate-em/device/device.rb +140 -0
- data/lib/automate-em/device/device_connection.rb +689 -0
- data/lib/automate-em/device/tcp_control.rb +210 -0
- data/lib/automate-em/engine.rb +36 -0
- data/lib/automate-em/interfaces/OLD CODE/deferred.rb +67 -0
- data/lib/automate-em/interfaces/OLD CODE/telnet/ansi.rb +137 -0
- data/lib/automate-em/interfaces/OLD CODE/telnet/telnet.rb +137 -0
- data/lib/automate-em/interfaces/html5.rb +302 -0
- data/lib/automate-em/logic/logic.rb +76 -0
- data/lib/automate-em/service/http_service.rb +584 -0
- data/lib/automate-em/service/service.rb +48 -0
- data/lib/automate-em/status.rb +89 -0
- data/lib/automate-em/utilities.rb +195 -0
- data/lib/automate-em/version.rb +3 -0
- data/lib/generators/module/USAGE +8 -0
- data/lib/generators/module/module_generator.rb +47 -0
- data/lib/tasks/automate-em_tasks.rake +5 -0
- data/test/automate-em_test.rb +7 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +56 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/test_helper.rb +15 -0
- metadata +328 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
#require 'rubygems'
|
2
|
+
#require 'eventmachine'
|
3
|
+
|
4
|
+
|
5
|
+
require "#{File.dirname(__FILE__)}/ansi.rb"
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Control
|
9
|
+
class TelnetServer < Deferred
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super
|
13
|
+
|
14
|
+
@input_lock = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.start
|
18
|
+
EventMachine::start_server "127.0.0.1", 23, TelnetServer
|
19
|
+
System.logger.info 'running telnet server on 23'
|
20
|
+
end
|
21
|
+
|
22
|
+
def received
|
23
|
+
@input_lock.synchronize {
|
24
|
+
data = @receive_queue.pop(true)
|
25
|
+
if data == "\b"
|
26
|
+
if @input.length > 0
|
27
|
+
send_data " \b"
|
28
|
+
@input.chop!
|
29
|
+
else
|
30
|
+
send_data " "
|
31
|
+
end
|
32
|
+
elsif data =~ /.*\r\n$/
|
33
|
+
|
34
|
+
#
|
35
|
+
# TODO:: For linux we need to chop! twice here
|
36
|
+
# Windows @input is already complete
|
37
|
+
#
|
38
|
+
|
39
|
+
if @input =~ /^(quit|exit)$/i
|
40
|
+
disconnect
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
if @input != "" && !@input.nil?
|
45
|
+
if @selected.nil?
|
46
|
+
if @input =~ /^\d+$/
|
47
|
+
@selected = Communicator.select(self, @input.to_i)
|
48
|
+
else
|
49
|
+
@selected = Communicator.select(self, @input)
|
50
|
+
end
|
51
|
+
send_line " system #{@input} selected...", :green
|
52
|
+
else
|
53
|
+
thecommand = @input.split(/\s|\./, 3)
|
54
|
+
@input = ""
|
55
|
+
on_fail = lambda {
|
56
|
+
send_line(" invalid command", :green)
|
57
|
+
send_prompt("> ", :green)
|
58
|
+
send_prompt(@input)
|
59
|
+
}
|
60
|
+
if thecommand[0] =~ /register/i
|
61
|
+
@selected.register(self, thecommand[1], thecommand[2], &on_fail)
|
62
|
+
elsif thecommand[0] =~ /unregister/i
|
63
|
+
@selected.unregister(self, thecommand[1], thecommand[2], &on_fail)
|
64
|
+
elsif thecommand[2].nil?
|
65
|
+
@selected.send_command(thecommand[0], thecommand[1], &on_fail)
|
66
|
+
elsif ['{','['].include?(thecommand[2][0])
|
67
|
+
@selected.send_command(thecommand[0], thecommand[1], JSON.parse(thecommand[2], {:symbolize_names => true}), &on_fail)
|
68
|
+
else
|
69
|
+
@selected.send_command(thecommand[0], thecommand[1], thecommand[2], &on_fail)
|
70
|
+
end
|
71
|
+
send_line " sent...", :green
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
send_prompt("> ", :green)
|
76
|
+
@input = ""
|
77
|
+
else #if data =~ /^[a-zA-Z0-9\., _-]*$/
|
78
|
+
@input << data
|
79
|
+
end
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def notify(mod_sym, stat_sym, data)
|
84
|
+
send_line("\r\nStatus: #{mod_sym}:#{stat_sym}==#{data}", :green)
|
85
|
+
send_prompt("> ", :green)
|
86
|
+
@input_lock.synchronize {
|
87
|
+
send(@input) if !@input.empty?
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def initiate_session
|
94
|
+
@input_lock.synchronize {
|
95
|
+
@input = ""
|
96
|
+
}
|
97
|
+
send_line("Please select from the following systems:", :green)
|
98
|
+
send_line("-----------------------------------------", :green)
|
99
|
+
system = Communicator.system_list
|
100
|
+
system.each_index { |x| send_line(" #{x}: #{system[x]}", :green) }
|
101
|
+
send_prompt("> ", :green)
|
102
|
+
end
|
103
|
+
|
104
|
+
def disconnect
|
105
|
+
send_line("Goodbye.", :green)
|
106
|
+
close_connection_after_writing
|
107
|
+
end
|
108
|
+
|
109
|
+
def send_line(data, color=nil)
|
110
|
+
send(data, color, true)
|
111
|
+
end
|
112
|
+
|
113
|
+
def send_prompt(data, color=nil)
|
114
|
+
send(data, color, false)
|
115
|
+
end
|
116
|
+
|
117
|
+
def send(data, color=nil, newline=false)
|
118
|
+
if newline
|
119
|
+
data = data + "\r\n"
|
120
|
+
end
|
121
|
+
|
122
|
+
case color
|
123
|
+
when :red
|
124
|
+
send_data(ANSI.red(data))
|
125
|
+
when :green
|
126
|
+
send_data(ANSI.green(data))
|
127
|
+
else
|
128
|
+
send_data(data)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
#EventMachine::run {
|
135
|
+
# EventMachine::start_server "127.0.0.1", 8080, TelnetServer
|
136
|
+
# puts 'running echo server on 8080'
|
137
|
+
#}
|
@@ -0,0 +1,302 @@
|
|
1
|
+
require 'em-websocket'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
|
5
|
+
#
|
6
|
+
# This system was designed based on the following articles
|
7
|
+
# https://gist.github.com/299789
|
8
|
+
# http://blog.new-bamboo.co.uk/2010/2/10/json-event-based-convention-websockets
|
9
|
+
#
|
10
|
+
|
11
|
+
|
12
|
+
class HTML5Monitor
|
13
|
+
@@clients = {}
|
14
|
+
@@special_events = {
|
15
|
+
"system" => :system,
|
16
|
+
"authenticate" => :authenticate,
|
17
|
+
"ls" => :ls,
|
18
|
+
"ping" => :ping
|
19
|
+
}
|
20
|
+
@@special_commands = {
|
21
|
+
"register" => :register,
|
22
|
+
"unregister" => :unregister
|
23
|
+
}
|
24
|
+
|
25
|
+
def self.register(id)
|
26
|
+
@@clients[id] = HTML5Monitor.new(id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.unregister(id)
|
30
|
+
client = @@clients.delete(id)
|
31
|
+
|
32
|
+
EM.defer do
|
33
|
+
begin
|
34
|
+
client.disconnected
|
35
|
+
AutomateEm::System.logger.debug "There are now #{HTML5Monitor.count} HTML5 clients connected"
|
36
|
+
rescue => e
|
37
|
+
AutomateEm.print_error(AutomateEm::System.logger, e, {
|
38
|
+
:message => "in html5.rb, onclose : unregistering client did not exist (we may have been shutting down)",
|
39
|
+
:level => Logger::ERROR
|
40
|
+
})
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.count
|
46
|
+
return @@clients.length
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.receive(id, data)
|
50
|
+
client = @@clients[id]
|
51
|
+
|
52
|
+
EM.defer do
|
53
|
+
begin
|
54
|
+
client.receive(data)
|
55
|
+
rescue => e
|
56
|
+
AutomateEm.print_error(AutomateEm::System.logger, e, {
|
57
|
+
:message => "in html5.rb, onmessage : client did not exist (we may have been shutting down)",
|
58
|
+
:level => Logger::ERROR
|
59
|
+
})
|
60
|
+
ensure
|
61
|
+
ActiveRecord::Base.clear_active_connections! # Clear any unused connections
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def initialize(socket)
|
68
|
+
@data_lock = Mutex.new
|
69
|
+
|
70
|
+
#
|
71
|
+
# Must authenticate before any system details will be sent
|
72
|
+
#
|
73
|
+
@socket = socket
|
74
|
+
@system = nil
|
75
|
+
@user = nil
|
76
|
+
|
77
|
+
|
78
|
+
@socket.send(JSON.generate({:event => "authenticate", :data => []}))
|
79
|
+
|
80
|
+
#
|
81
|
+
# TODO:: start a schedule here that sends a ping to the browser every so often
|
82
|
+
#
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
#
|
87
|
+
#
|
88
|
+
# Instance methods:
|
89
|
+
#
|
90
|
+
#
|
91
|
+
def try_auth(data = nil)
|
92
|
+
|
93
|
+
return false if @ignoreAuth
|
94
|
+
|
95
|
+
if !!@user
|
96
|
+
if data.nil?
|
97
|
+
return true
|
98
|
+
else
|
99
|
+
@user = nil
|
100
|
+
return try_auth(data)
|
101
|
+
end
|
102
|
+
else
|
103
|
+
if !data.nil? && data.class == Array
|
104
|
+
if data.length == 1 # one time key
|
105
|
+
@user = TrustedDevice.try_to_login(data[0])
|
106
|
+
elsif data.length == 3
|
107
|
+
#user, password, auth_source
|
108
|
+
source = AuthSource.where("name = ?", data[2]).first
|
109
|
+
@user = User.try_to_login(data[0], data[1], source)
|
110
|
+
end
|
111
|
+
|
112
|
+
return try_auth # no data
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Prevent DOS/brute force Attacks
|
117
|
+
#
|
118
|
+
@ignoreAuth = true
|
119
|
+
EM.add_timer(2) do
|
120
|
+
begin
|
121
|
+
@socket.send(JSON.generate({:event => "authenticate", :data => []}))
|
122
|
+
ensure
|
123
|
+
@ignoreAuth = false
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
return false
|
128
|
+
end
|
129
|
+
|
130
|
+
def send_system
|
131
|
+
return if @ignoreSys
|
132
|
+
@ignoreSys = true
|
133
|
+
|
134
|
+
EM.add_timer(2) do
|
135
|
+
begin
|
136
|
+
@socket.send(JSON.generate({:event => "system", :data => []}))
|
137
|
+
ensure
|
138
|
+
@ignoreSys = false
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def disconnected
|
144
|
+
@data_lock.synchronize {
|
145
|
+
@system.disconnected(self) if (!!@system) # System could be nil or false
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
def receive(data)
|
150
|
+
data = JSON.parse(data, {:symbolize_names => true})
|
151
|
+
return unless data[:command].class == String
|
152
|
+
data[:data] = [] unless data[:data].class == Array
|
153
|
+
|
154
|
+
@data_lock.synchronize {
|
155
|
+
#
|
156
|
+
# Ensure authenticated
|
157
|
+
#
|
158
|
+
if data[:command] == "authenticate"
|
159
|
+
return unless try_auth(data[:data])
|
160
|
+
send_system
|
161
|
+
return
|
162
|
+
else
|
163
|
+
return unless (try_auth || data[:command] == "ping")
|
164
|
+
end
|
165
|
+
|
166
|
+
#
|
167
|
+
# Ensure system is selected
|
168
|
+
# If a command is sent out of order
|
169
|
+
#
|
170
|
+
if @system.nil? && !@@special_events.has_key?(data[:command])
|
171
|
+
send_system
|
172
|
+
return
|
173
|
+
end
|
174
|
+
|
175
|
+
if @@special_events.has_key?(data[:command]) # system, auth, ls
|
176
|
+
case @@special_events[data[:command]]
|
177
|
+
when :system
|
178
|
+
@system.disconnected(self) unless @system.nil?
|
179
|
+
@system = nil
|
180
|
+
@system = AutomateEm::Communicator.select(@user, self, data[:data][0]) unless data[:data].empty?
|
181
|
+
if @system.nil?
|
182
|
+
send_system
|
183
|
+
elsif @system == false # System offline
|
184
|
+
EM.schedule do
|
185
|
+
@socket.send(JSON.generate({:event => "offline", :data => []}))
|
186
|
+
shutdown
|
187
|
+
end
|
188
|
+
else
|
189
|
+
EM.schedule do
|
190
|
+
@socket.send(JSON.generate({:event => "ready", :data => []}))
|
191
|
+
end
|
192
|
+
end
|
193
|
+
when :ping
|
194
|
+
EM.schedule do
|
195
|
+
@socket.send(JSON.generate({:event => "pong", :data => []}))
|
196
|
+
end
|
197
|
+
when :ls
|
198
|
+
systems = AutomateEm::Communicator.system_list(@user)
|
199
|
+
EM.schedule do
|
200
|
+
@socket.send(JSON.generate({:event => "ls", :data => systems}))
|
201
|
+
end
|
202
|
+
end
|
203
|
+
elsif @@special_commands.has_key?(data[:command]) # reg, unreg
|
204
|
+
array = data[:data]
|
205
|
+
array.insert(0, self)
|
206
|
+
@system.public_send(data[:command], *array)
|
207
|
+
else # All other commands
|
208
|
+
command = data[:command].split('.')
|
209
|
+
if command.length == 2
|
210
|
+
@system.send_command(command[0], command[1], *data[:data])
|
211
|
+
else
|
212
|
+
AutomateEm::System.logger.info "-- in html5.rb, recieve : invalid command received - #{data[:command]} --"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
}
|
216
|
+
rescue => e
|
217
|
+
logger = nil
|
218
|
+
@data_lock.synchronize {
|
219
|
+
logger = @system.nil? ? AutomateEm::System.logger : @system.logger
|
220
|
+
}
|
221
|
+
AutomateEm.print_error(logger, e, {
|
222
|
+
:message => "in html5.rb, recieve : probably malformed JSON data",
|
223
|
+
:level => Logger::ERROR
|
224
|
+
})
|
225
|
+
shutdown
|
226
|
+
end
|
227
|
+
|
228
|
+
def shutdown
|
229
|
+
EM.schedule do
|
230
|
+
@socket.close_websocket
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def notify(mod_sym, stat_sym, data)
|
235
|
+
#
|
236
|
+
# This should be re-entrant? So no need to protect
|
237
|
+
#
|
238
|
+
@system.logger.debug "#{mod_sym}.#{stat_sym} sent #{data.inspect}"
|
239
|
+
EM.schedule do
|
240
|
+
@socket.send(JSON.generate({"event" => "#{mod_sym}.#{stat_sym}", "data" => data}))
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
module AutomateEm
|
247
|
+
class System
|
248
|
+
@@socket_server = nil
|
249
|
+
def self.start_websockets
|
250
|
+
EM.schedule do
|
251
|
+
if @@socket_server.nil?
|
252
|
+
@@socket_server = EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 81) do |socket| # , :debug => true
|
253
|
+
socket.onopen {
|
254
|
+
#
|
255
|
+
# This socket represents a connected device
|
256
|
+
#
|
257
|
+
HTML5Monitor.register(socket)
|
258
|
+
}
|
259
|
+
|
260
|
+
socket.onmessage { |data|
|
261
|
+
#
|
262
|
+
# Attach socket here to system
|
263
|
+
# then process commands
|
264
|
+
#
|
265
|
+
HTML5Monitor.receive(socket, data)
|
266
|
+
}
|
267
|
+
|
268
|
+
socket.onclose {
|
269
|
+
HTML5Monitor.unregister(socket)
|
270
|
+
}
|
271
|
+
|
272
|
+
socket.onerror { |error|
|
273
|
+
if !error.kind_of?(EM::WebSocket::WebSocketError)
|
274
|
+
EM.defer do
|
275
|
+
AutomateEm.print_error(AutomateEm::System.logger, error, {
|
276
|
+
:message => "in html5.rb, onerror : issue with websocket data",
|
277
|
+
:level => Logger::ERROR
|
278
|
+
})
|
279
|
+
end
|
280
|
+
else
|
281
|
+
EM.defer do
|
282
|
+
AutomateEm::System.logger.info "in html5.rb, onerror : invalid handshake received - #{error.inspect}"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
}
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
end
|
290
|
+
EM.defer do
|
291
|
+
AutomateEm::System.logger.info 'running HTML5 socket server on port 81'
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def self.stop_websockets
|
296
|
+
EM.schedule do
|
297
|
+
EventMachine::stop_server(@@socket_server) unless @@socket_server.nil?
|
298
|
+
@@socket_server = nil
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module AutomateEm
|
2
|
+
|
3
|
+
#
|
4
|
+
# Base class for all control logic classes
|
5
|
+
#
|
6
|
+
class Logic
|
7
|
+
include Status
|
8
|
+
include Constants
|
9
|
+
include Utilities
|
10
|
+
|
11
|
+
def initialize(system)
|
12
|
+
@system = system
|
13
|
+
|
14
|
+
#
|
15
|
+
# Status variables
|
16
|
+
# NOTE:: if changed then change in device.rb
|
17
|
+
#
|
18
|
+
@status = {}
|
19
|
+
@status_lock = Mutex.new
|
20
|
+
@status_emit = {} # status => condition_variable
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def logger
|
25
|
+
@system.logger
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def clear_active_timers
|
30
|
+
@schedule.clear_jobs unless @schedule.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
|
37
|
+
def setting(name)
|
38
|
+
val = LogicModule.lookup(self).settings.where("name = ?", name).first
|
39
|
+
if val.nil?
|
40
|
+
val = LogicModule.lookup(self).control_system.zones.joins(:settings).where('settings.name = ?', name.to_s).first
|
41
|
+
val = val.settings.where("name = ?", name.to_s).first unless val.nil?
|
42
|
+
|
43
|
+
val = LogicModule.lookup(self).dependency.settings.where("name = ?", name).first if val.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
if val.present?
|
47
|
+
case val.value_type
|
48
|
+
when 0
|
49
|
+
return val.text_value
|
50
|
+
when 1
|
51
|
+
return val.integer_value
|
52
|
+
when 2
|
53
|
+
return val.float_value
|
54
|
+
when 3
|
55
|
+
return val.datetime_value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
return nil
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
attr_reader :system
|
64
|
+
|
65
|
+
|
66
|
+
def register(mod, status, &block)
|
67
|
+
@system.communicator.register(self, mod, status, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def unregister(mod, status, &block)
|
71
|
+
@system.communicator.unregister(self, mod, status, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|