automate-em 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/LGPL3-LICENSE +165 -0
  2. data/README.textile +48 -0
  3. data/Rakefile +40 -0
  4. data/app/models/control_system.rb +20 -0
  5. data/app/models/controller_device.rb +21 -0
  6. data/app/models/controller_http_service.rb +17 -0
  7. data/app/models/controller_logic.rb +5 -0
  8. data/app/models/controller_zone.rb +10 -0
  9. data/app/models/dependency.rb +20 -0
  10. data/app/models/server.rb +12 -0
  11. data/app/models/setting.rb +38 -0
  12. data/app/models/trusted_device.rb +63 -0
  13. data/app/models/user_zone.rb +10 -0
  14. data/app/models/zone.rb +16 -0
  15. data/db/migrate/20111001022500_init.rb +147 -0
  16. data/db/migrate/20111017213801_one_time_key.rb +9 -0
  17. data/db/migrate/20111021071632_encrypt_setting.rb +9 -0
  18. data/db/migrate/20111110075444_servers.rb +15 -0
  19. data/db/migrate/20111114074538_default_port.rb +9 -0
  20. data/db/migrate/20111122073055_makebreak.rb +9 -0
  21. data/db/migrate/20111211062846_create_controller_http_services.rb +18 -0
  22. data/lib/automate-em.rb +155 -0
  23. data/lib/automate-em/constants.rb +6 -0
  24. data/lib/automate-em/core/communicator.rb +318 -0
  25. data/lib/automate-em/core/modules.rb +373 -0
  26. data/lib/automate-em/core/resolver_pool.rb +76 -0
  27. data/lib/automate-em/core/system.rb +356 -0
  28. data/lib/automate-em/device/datagram_server.rb +111 -0
  29. data/lib/automate-em/device/device.rb +140 -0
  30. data/lib/automate-em/device/device_connection.rb +689 -0
  31. data/lib/automate-em/device/tcp_control.rb +210 -0
  32. data/lib/automate-em/engine.rb +36 -0
  33. data/lib/automate-em/interfaces/OLD CODE/deferred.rb +67 -0
  34. data/lib/automate-em/interfaces/OLD CODE/telnet/ansi.rb +137 -0
  35. data/lib/automate-em/interfaces/OLD CODE/telnet/telnet.rb +137 -0
  36. data/lib/automate-em/interfaces/html5.rb +302 -0
  37. data/lib/automate-em/logic/logic.rb +76 -0
  38. data/lib/automate-em/service/http_service.rb +584 -0
  39. data/lib/automate-em/service/service.rb +48 -0
  40. data/lib/automate-em/status.rb +89 -0
  41. data/lib/automate-em/utilities.rb +195 -0
  42. data/lib/automate-em/version.rb +3 -0
  43. data/lib/generators/module/USAGE +8 -0
  44. data/lib/generators/module/module_generator.rb +47 -0
  45. data/lib/tasks/automate-em_tasks.rake +5 -0
  46. data/test/automate-em_test.rb +7 -0
  47. data/test/dummy/README.rdoc +261 -0
  48. data/test/dummy/Rakefile +7 -0
  49. data/test/dummy/app/assets/javascripts/application.js +15 -0
  50. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  51. data/test/dummy/app/controllers/application_controller.rb +3 -0
  52. data/test/dummy/app/helpers/application_helper.rb +2 -0
  53. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/test/dummy/config.ru +4 -0
  55. data/test/dummy/config/application.rb +56 -0
  56. data/test/dummy/config/boot.rb +10 -0
  57. data/test/dummy/config/database.yml +25 -0
  58. data/test/dummy/config/environment.rb +5 -0
  59. data/test/dummy/config/environments/development.rb +37 -0
  60. data/test/dummy/config/environments/production.rb +67 -0
  61. data/test/dummy/config/environments/test.rb +37 -0
  62. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  63. data/test/dummy/config/initializers/inflections.rb +15 -0
  64. data/test/dummy/config/initializers/mime_types.rb +5 -0
  65. data/test/dummy/config/initializers/secret_token.rb +7 -0
  66. data/test/dummy/config/initializers/session_store.rb +8 -0
  67. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  68. data/test/dummy/config/locales/en.yml +5 -0
  69. data/test/dummy/config/routes.rb +4 -0
  70. data/test/dummy/public/404.html +26 -0
  71. data/test/dummy/public/422.html +26 -0
  72. data/test/dummy/public/500.html +25 -0
  73. data/test/dummy/public/favicon.ico +0 -0
  74. data/test/dummy/script/rails +6 -0
  75. data/test/integration/navigation_test.rb +10 -0
  76. data/test/test_helper.rb +15 -0
  77. metadata +328 -0
@@ -0,0 +1,111 @@
1
+ require 'socket'
2
+
3
+ module AutomateEm
4
+
5
+ $datagramServer = nil
6
+
7
+ class DatagramBase
8
+ include Utilities
9
+ include DeviceConnection
10
+
11
+ def do_send_data(data)
12
+ $datagramServer.do_send_data(DeviceModule.lookup(@parent), data)
13
+ end
14
+ end
15
+
16
+ module DatagramServer
17
+ def initialize *args
18
+ super
19
+
20
+ if !$datagramServer.nil?
21
+ return
22
+ end
23
+
24
+ $datagramServer = self
25
+ @devices = {}
26
+ @ips = {}
27
+
28
+ EM.defer do
29
+ System.logger.info 'running datagram server on an ephemeral port'
30
+ end
31
+ end
32
+
33
+
34
+ #
35
+ # Eventmachine callbacks
36
+ #
37
+ def receive_data(data)
38
+ #ip = get_peername[2,6].unpack "nC4"
39
+ port, ip = Socket.unpack_sockaddr_in(get_peername)
40
+ begin
41
+ @devices["#{ip}:#{port}"].do_receive_data(data)
42
+ rescue
43
+ #
44
+ # TODO:: error messages here if device is null ect
45
+ #
46
+ end
47
+ end
48
+
49
+
50
+ #
51
+ # Additional controls
52
+ #
53
+ def do_send_data(scheme, data)
54
+ res = ResolverJob.new(scheme.ip)
55
+ res.callback {|ip|
56
+
57
+ #
58
+ # Just in case the address is a domain name we want to ensure the
59
+ # IP lookups are always correct and we are always sending to the
60
+ # specified device
61
+ #
62
+ text = "#{scheme.ip}:#{scheme.port}"
63
+ old_ip = @ips[text]
64
+ if old_ip != ip
65
+ EM.schedule do # All modifications are on the reactor thread instead of locking
66
+ device = @devices.delete("#{old_ip}:#{scheme.port}")
67
+ @ips[text] = ip
68
+ @devices["#{ip}:#{scheme.port}"] = device
69
+ end
70
+ end
71
+ send_datagram(data, ip, scheme.port)
72
+ }
73
+ res.errback {|error|
74
+ EM.defer do
75
+ System.logger.info e.message + " calling UDP send for #{scheme.dependency.actual_name} @ #{scheme.ip} in #{scheme.control_system.name}"
76
+ end
77
+ }
78
+ end
79
+
80
+ def add_device(scheme, device)
81
+ EM.schedule do
82
+ res = ResolverJob.new(scheme.ip)
83
+ res.callback {|ip|
84
+ @devices["#{ip}:#{scheme.port}"] = device
85
+ @ips["#{scheme.ip}:#{scheme.port}"] = ip
86
+ }
87
+ res.errback {|error|
88
+ @devices["#{scheme.ip}:#{scheme.port}"] = device
89
+ @ips["#{scheme.ip}:#{scheme.port}"] = scheme.ip
90
+ }
91
+
92
+ EM.defer do
93
+ System.logger.info e.message + " adding UDP #{scheme.dependency.actual_name} @ #{scheme.ip} in #{scheme.control_system.name}"
94
+ end
95
+ end
96
+ end
97
+
98
+ def remove_device(scheme)
99
+ EM.schedule do
100
+ begin
101
+ ip = @ips.delete("#{scheme.ip}:#{scheme.port}")
102
+ @devices.delete("#{ip}:#{scheme.port}")
103
+ rescue
104
+ EM.defer do
105
+ System.logger.info e.message + " removing UDP #{scheme.dependency.actual_name} @ #{scheme.ip} in #{scheme.control_system.name}"
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,140 @@
1
+ module AutomateEm
2
+ module ModuleCore
3
+ include Status # The observable pattern (Should not be called directly)
4
+ include Constants
5
+ include Utilities
6
+
7
+ #
8
+ # Sets up a link for the user code to the eventmachine class
9
+ # This way the namespace is clean.
10
+ #
11
+ def setbase(base)
12
+ @base = base
13
+ end
14
+
15
+
16
+ def join_system(system)
17
+ @system_lock.synchronize {
18
+ @systems << system
19
+ }
20
+ end
21
+
22
+ def leave_system(system)
23
+ @system_lock.synchronize {
24
+ @systems.delete(system)
25
+ return @systems.length
26
+ }
27
+ end
28
+
29
+ def clear_active_timers
30
+ @schedule.clear_jobs unless @schedule.nil?
31
+ end
32
+
33
+
34
+ #def command_successful(result) # TODO:: needs a re-think
35
+ # @base.process_data_result(result)
36
+ #end
37
+
38
+
39
+ def logger
40
+ @system_lock.synchronize {
41
+ return @systems[0].logger unless @systems.empty?
42
+ }
43
+ System.logger
44
+ end
45
+
46
+ attr_reader :systems
47
+ attr_reader :base
48
+
49
+
50
+ protected
51
+
52
+
53
+ #
54
+ # Configuration and settings
55
+ # => TODO:: We should get all zones that the pod is in with the setting set and select the first setting (vs first zone)
56
+ #
57
+ def setting(name)
58
+ val = config.settings.where("name = ?", name.to_s).first
59
+ if val.nil?
60
+ val = config.control_system.zones.joins(:settings).where('settings.name = ?', name.to_s).first
61
+ val = val.settings.where("name = ?", name.to_s).first unless val.nil?
62
+
63
+ val = config.dependency.settings.where("name = ?", name.to_s).first if val.nil?
64
+ end
65
+
66
+ if val.present?
67
+ case val.value_type
68
+ when 0
69
+ return val.text_value
70
+ when 1
71
+ return val.integer_value
72
+ when 2
73
+ return val.float_value
74
+ when 3
75
+ return val.datetime_value
76
+ end
77
+ end
78
+
79
+ return nil
80
+ end
81
+ end
82
+
83
+ class Device
84
+ include ModuleCore
85
+
86
+ def initialize(tls, makebreak)
87
+ @systems = []
88
+
89
+ #
90
+ # Status variables
91
+ # NOTE:: if changed then change in logic.rb
92
+ #
93
+ @secure_connection = tls
94
+ @makebreak_connection = makebreak
95
+ @status = {}
96
+ @status_lock = Mutex.new
97
+ @system_lock = Mutex.new
98
+ @status_waiting = false
99
+ end
100
+
101
+
102
+
103
+
104
+ #
105
+ # required by base for send logic
106
+ #
107
+ attr_reader :secure_connection
108
+ attr_reader :makebreak_connection
109
+
110
+
111
+ protected
112
+
113
+
114
+ def config
115
+ DeviceModule.lookup(self)
116
+ end
117
+
118
+
119
+ def send(data, options = {}, *args, &block)
120
+ error = true
121
+
122
+ begin
123
+ error = @base.do_send_command(data, options, *args, &block)
124
+ rescue => e
125
+ AutomateEm.print_error(logger, e, {
126
+ :message => "module #{self.class} in send",
127
+ :level => Logger::ERROR
128
+ })
129
+ ensure
130
+ if error
131
+ begin
132
+ logger.warn "Command send failed for: #{data.inspect}"
133
+ rescue
134
+ logger.error "Command send failed, unable to print data"
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,689 @@
1
+ require 'atomic'
2
+
3
+ #
4
+ # This contains the basic constructs required for
5
+ # serialised comms over TCP and UDP
6
+ #
7
+ module AutomateEm
8
+ module DeviceConnection
9
+ def initialize( parent )
10
+ super
11
+
12
+ @default_send_options = {
13
+ :wait => true, # Wait for response
14
+ :delay => 0, # Delay next send by x.y seconds
15
+ :delay_on_recieve => 0, # Delay next send after a recieve by x.y seconds (only works when we are waiting for responses)
16
+ #:emit
17
+ :max_waits => 3,
18
+ :callback => nil, # Alternative to the received function
19
+ :retries => 2,
20
+ :hex_string => false,
21
+ :timeout => 5, # Timeout in seconds
22
+ :priority => 50,
23
+ :retry_on_disconnect => true,
24
+ :force_disconnect => false # part of make and break options
25
+ }
26
+
27
+ @config = {
28
+ :max_buffer => 524288, # 512kb
29
+ :clear_queue_on_disconnect => false,
30
+ :flush_buffer_on_disconnect => false,
31
+ :priority_bonus => 20
32
+ }
33
+
34
+
35
+ #
36
+ # Queues
37
+ #
38
+ @task_queue = EM::Queue.new # basically we add tasks here that we want to run in a strict order (connect, disconnect)
39
+ @receive_queue = EM::Queue.new # So we can process responses in different ways
40
+ @wait_queue = EM::Queue.new
41
+ @send_queue = EM::PriorityQueue.new(:fifo => true) {|x,y| x < y} # regular priority
42
+
43
+ #
44
+ # Named commands
45
+ # Allowing for state control
46
+ #
47
+ @named_commands = {}
48
+
49
+ #
50
+ # Locks
51
+ #
52
+ @received_lock = Mutex.new
53
+ @task_lock = Mutex.new
54
+ @status_lock = Mutex.new
55
+ @send_monitor = Object.new.extend(MonitorMixin)
56
+
57
+
58
+ #
59
+ # State
60
+ #
61
+ @connected = false
62
+ @connecting = false
63
+ @disconnecting = false
64
+ @com_paused = true
65
+
66
+ @command = nil
67
+ @waiting = false
68
+ @processing = false
69
+ @last_sent_at = 0.0
70
+ @last_recieve_at = 0.0
71
+ @timeout = nil
72
+
73
+
74
+ #
75
+ # Configure links between objects (This is a very loose tie)
76
+ # Relies on serial loading of modules
77
+ #
78
+ @parent = parent
79
+ @parent.setbase(self)
80
+
81
+ @tls_enabled = @parent.secure_connection
82
+ if @parent.makebreak_connection
83
+ @make_break = true
84
+ @first_connect = true
85
+ else
86
+ @make_break = false
87
+ end
88
+ @make_occured = false
89
+
90
+ @shutting_down = Atomic.new(false)
91
+
92
+
93
+ #
94
+ # Task event loop
95
+ #
96
+ @task_queue_proc = Proc.new do |task|
97
+ if !@shutting_down.value
98
+ EM.defer do
99
+ begin
100
+ @task_lock.synchronize {
101
+ task.call
102
+ }
103
+ rescue => e
104
+ AutomateEm.print_error(logger, e, {
105
+ :message => "module #{@parent.class} in device_connection.rb, base : error in task loop",
106
+ :level => Logger::ERROR
107
+ })
108
+ ensure
109
+ ActiveRecord::Base.clear_active_connections!
110
+ @task_queue.pop &@task_queue_proc
111
+ end
112
+ end
113
+ end
114
+ end
115
+ @task_queue.pop &@task_queue_proc # First task is ready
116
+
117
+
118
+ #
119
+ # send loop
120
+ #
121
+ @wait_queue_proc = Proc.new do |ignore|
122
+ if ignore != :shutdown
123
+
124
+ @send_queue.pop {|command|
125
+ if command != :shutdown
126
+ begin
127
+
128
+ process = true
129
+ if command[:name].present?
130
+ begin
131
+ name = command[:name]
132
+ @named_commands[name][0].pop # Extract the command data
133
+ command = @named_commands[name][1]
134
+
135
+ if @named_commands[name][0].empty? # See if any more of these commands are queued
136
+ @named_commands.delete(name) # Delete if there are not
137
+ else
138
+ @named_commands[name][1] = nil # Reset if there are
139
+ end
140
+
141
+ if command.nil? # decide if to continue or not
142
+ command = {}
143
+ process = false
144
+ end
145
+ rescue
146
+ #
147
+ # Retry (pop empty, lets let it have it)
148
+ #
149
+ end
150
+ end
151
+
152
+ if process
153
+ if command[:delay] > 0.0
154
+ delay = @last_sent_at + command[:delay] - Time.now.to_f
155
+ if delay > 0.0
156
+ EM.add_timer delay do
157
+ process_send(command)
158
+ end
159
+ else
160
+ process_send(command)
161
+ end
162
+ else
163
+ process_send(command)
164
+ end
165
+ else
166
+ process_next_send(command)
167
+ end
168
+ rescue => e
169
+ EM.defer do
170
+ AutomateEm.print_error(logger, e, {
171
+ :message => "module #{@parent.class} in device_connection.rb, base : error in send loop",
172
+ :level => Logger::ERROR
173
+ })
174
+ end
175
+ ensure
176
+ ActiveRecord::Base.clear_active_connections!
177
+ @wait_queue.pop &@wait_queue_proc
178
+ end
179
+ end
180
+ }
181
+ end
182
+ end
183
+
184
+ #@wait_queue.push(nil) Start paused
185
+ @wait_queue.pop &@wait_queue_proc
186
+ end
187
+
188
+
189
+ def process_send(command) # this is on the reactor thread
190
+ begin
191
+ if !error? && @connected
192
+ do_send_data(command[:data])
193
+
194
+ @last_sent_at = Time.now.to_f
195
+ @waiting = command[:wait]
196
+
197
+ if @waiting
198
+ @command = command
199
+ @timeout = EM::Timer.new(command[:timeout]) {
200
+ sending_timeout
201
+ }
202
+ else
203
+ process_next_send(command)
204
+ end
205
+ else
206
+ if @connected
207
+ process_next_send(command)
208
+ else
209
+ if command[:retry_on_disconnect] || @make_break
210
+ @send_queue.push(command, command[:priority] - (2 * @config[:priority_bonus])) # Double bonus
211
+ end
212
+ @com_paused = true
213
+ end
214
+ end
215
+ rescue => e
216
+ #
217
+ # Save the thread in case of bad data in that send
218
+ #
219
+ EM.defer do
220
+ AutomateEm.print_error(logger, e, {
221
+ :message => "module #{@parent.class} in device_connection.rb, process_send : possible bad data",
222
+ :level => Logger::ERROR
223
+ })
224
+ end
225
+ if @connected
226
+ process_next_send(command)
227
+ else
228
+ @com_paused = true
229
+ end
230
+ end
231
+ end
232
+
233
+ def process_next_send(command)
234
+ if command[:force_disconnect] # Allow connection control
235
+ close_connection_after_writing
236
+ @disconnecting = true
237
+ @com_paused = true
238
+ else
239
+ EM.next_tick do
240
+ @wait_queue.push(nil) # Allows next response to process
241
+ end
242
+ end
243
+ end
244
+
245
+ #
246
+ # Data received
247
+ # Allow modules to set message delimiters for auto-buffering
248
+ # Default max buffer length == 1mb (setting can be overwritten)
249
+ # NOTE: The buffer cannot be defered otherwise there are concurrency issues
250
+ #
251
+ def do_receive_data(data)
252
+ @last_recieve_at = Time.now.to_f
253
+
254
+ if @parent.respond_to?(:response_delimiter)
255
+ begin
256
+ if @buf.nil?
257
+ del = @parent.response_delimiter
258
+ if del.class == Array
259
+ del = array_to_str(del)
260
+ elsif del.class == Fixnum
261
+ del = "" << del #array_to_str([del & 0xFF])
262
+ end
263
+ @buf = BufferedTokenizer.new(del, @config[:max_buffer]) # Call back for character
264
+ end
265
+ data = @buf.extract(data)
266
+ rescue => e
267
+ @buf = nil # clear the buffer
268
+ EM.defer do # Error in a thread
269
+ AutomateEm.print_error(logger, e, {
270
+ :message => "module #{@parent.class} error whilst setting delimiter",
271
+ :level => Logger::ERROR
272
+ })
273
+ end
274
+ data = [data]
275
+ end
276
+ else
277
+ data = [data]
278
+ end
279
+
280
+ if @waiting && data.length > 0
281
+ if @processing
282
+ @receive_queue.push(*data)
283
+ else
284
+ @processing = true
285
+ @timeout.cancel
286
+ process_response(data.shift, @command)
287
+ if data.length > 0
288
+ @receive_queue.push(*data)
289
+ end
290
+ end
291
+ else
292
+ data.each do |result|
293
+ process_response(result, nil)
294
+ end
295
+ end
296
+ end
297
+
298
+
299
+ #
300
+ # Caled from recieve
301
+ #
302
+ def process_response(response, command)
303
+ EM.defer do
304
+ do_process_response(response, command)
305
+ end
306
+ end
307
+
308
+ def do_process_response(response, command)
309
+ return if @shutting_down.value
310
+
311
+ @received_lock.synchronize { # This lock protects the send queue lock when we are emiting status
312
+ @send_monitor.mon_synchronize {
313
+ result = :abort
314
+ begin
315
+ if @parent.respond_to?(:received)
316
+ if command.present?
317
+ @parent.mark_emit_start(command[:emit]) if command[:emit].present?
318
+ if command[:callback].present?
319
+ result = command[:callback].call(response, command)
320
+
321
+ #
322
+ # The data may still be usefull
323
+ #
324
+ if [nil, :ignore].include?(result)
325
+ @parent.received(response, nil)
326
+ end
327
+ else
328
+ result = @parent.received(response, command)
329
+ end
330
+ else
331
+ # logger.debug "Out of order response received for: #{@parent.class}"
332
+ result = @parent.received(response, nil)
333
+ end
334
+ else
335
+ if command.present?
336
+ @parent.mark_emit_start(command[:emit]) if command[:emit].present?
337
+ if command[:callback].present?
338
+ result = command[:callback].call(response, command)
339
+ else
340
+ result = true
341
+ end
342
+ else
343
+ result = true
344
+ end
345
+ end
346
+ rescue => e
347
+ #
348
+ # save from bad user code (don't want to deplete thread pool)
349
+ # This error should be logged in some consistent manner
350
+ #
351
+ AutomateEm.print_error(logger, e, {
352
+ :message => "module #{@parent.class} error whilst calling: received",
353
+ :level => Logger::ERROR
354
+ })
355
+ ensure
356
+ if command.present?
357
+ @parent.mark_emit_end if command[:emit].present?
358
+ end
359
+ ActiveRecord::Base.clear_active_connections!
360
+ end
361
+
362
+ if command.present? && command[:wait]
363
+ EM.schedule do
364
+ process_result(result)
365
+ end
366
+ end
367
+ }
368
+ }
369
+ end
370
+
371
+
372
+ def sending_timeout
373
+ @timeout = true
374
+ if !@processing && @connected && @command.present? # Probably not needed...
375
+ @processing = true # Ensure responses go into the queue
376
+
377
+ command = @command[:data]
378
+ process_result(:failed)
379
+
380
+ EM.defer do
381
+ logger.info "module #{@parent.class} timeout"
382
+ logger.info "A response was not received for the command: #{command}" unless command.nil?
383
+ end
384
+ elsif !@connected && @command.present? && @command[:wait]
385
+ if @command[:retry_on_disconnect] || @make_break
386
+ @send_queue.push(@command, @command[:priority] - (2 * @config[:priority_bonus])) # Double bonus
387
+ end
388
+ @com_paused = true
389
+ end
390
+ end
391
+
392
+
393
+ def process_result(result)
394
+ if [nil, :ignore].include?(result) && @command[:max_waits] > 0
395
+ @command[:max_waits] -= 1
396
+
397
+ if @receive_queue.size() > 0
398
+ @receive_queue.pop { |response|
399
+ process_response(response, @command)
400
+ }
401
+ else
402
+ @timeout = EM::Timer.new(@command[:timeout]) {
403
+ sending_timeout
404
+ }
405
+ @processing = false
406
+ end
407
+ else
408
+ if [false, :failed].include?(result) && @command[:retries] > 0 # assume command failed, we need to retry
409
+ @command[:retries] -= 1
410
+ @send_queue.push(@command, @command[:priority] - @config[:priority_bonus])
411
+ end
412
+
413
+ #else result == :abort || result == :success || result == true || waits and retries exceeded
414
+
415
+ @receive_queue.size().times do
416
+ @receive_queue.pop { |response|
417
+ process_response(response, nil)
418
+ }
419
+ end
420
+
421
+ @processing = false
422
+ @waiting = false
423
+
424
+ if @command[:delay_on_recieve] > 0.0
425
+ delay_for = (@last_recieve_at + @command[:delay_on_recieve] - Time.now.to_f)
426
+
427
+ if delay_for > 0.0
428
+ EM.add_timer delay_for do
429
+ process_response_complete
430
+ end
431
+ else
432
+ process_response_complete
433
+ end
434
+ else
435
+ process_response_complete
436
+ end
437
+ end
438
+ end
439
+
440
+ def process_response_complete
441
+ if (@make_break && @send_queue.empty?) || @command[:force_disconnect]
442
+ if @connected
443
+ close_connection_after_writing
444
+ @disconnecting = true
445
+ end
446
+ @com_paused = true
447
+ else
448
+ EM.next_tick do
449
+ @wait_queue.push(nil)
450
+ end
451
+ end
452
+
453
+ @command = nil # free memory
454
+ end
455
+
456
+
457
+
458
+
459
+ #
460
+ # ----------------------------------------------------------------
461
+ # Everything below here is called from a deferred thread
462
+ #
463
+ #
464
+ def logger
465
+ @parent.logger
466
+ end
467
+
468
+ def received_lock
469
+ @send_monitor # for monitor use
470
+ end
471
+
472
+
473
+ #
474
+ # Processes sends in strict order
475
+ #
476
+ def do_send_command(data, options = {}, *args, &block)
477
+
478
+ begin
479
+ @status_lock.synchronize {
480
+ options = @default_send_options.merge(options)
481
+ }
482
+
483
+ #
484
+ # Make sure we are sending appropriately formatted data
485
+ #
486
+ if data.class == Array
487
+ data = array_to_str(data)
488
+ elsif options[:hex_string] == true
489
+ data = hex_to_byte(data)
490
+ end
491
+
492
+ options[:data] = data
493
+ options[:retries] = 0 if options[:wait] == false
494
+
495
+ if options[:callback].nil? && (args.length > 0 || block.present?)
496
+ options[:callback] = args[0] unless args.empty? || args[0].class != Proc
497
+ options[:callback] = block unless block.nil?
498
+ end
499
+
500
+ if options[:name].present?
501
+ options[:name] = options[:name].to_sym
502
+ end
503
+ rescue => e
504
+ AutomateEm.print_error(logger, e, {
505
+ :message => "module #{@parent.class} in device_connection.rb, send : possible bad data or options hash",
506
+ :level => Logger::ERROR
507
+ })
508
+
509
+ return true
510
+ end
511
+
512
+
513
+ #
514
+ # Use a monitor here to allow for re-entrant locking
515
+ # This allows for a priority queue and we guarentee order of operations
516
+ #
517
+ bonus = false
518
+ begin
519
+ @send_monitor.mon_exit
520
+ @send_monitor.mon_enter
521
+ bonus = true
522
+ rescue
523
+ end
524
+
525
+ EM.schedule do
526
+ if bonus
527
+ options[:priority] -= @config[:priority_bonus]
528
+ end
529
+ add_to_queue(options)
530
+ end
531
+
532
+ return false
533
+ rescue => e
534
+ #
535
+ # Save from a fatal error
536
+ #
537
+ AutomateEm.print_error(logger, e, {
538
+ :message => "module #{@parent.class} in device_connection.rb, send : something went terribly wrong to get here",
539
+ :level => Logger::ERROR
540
+ })
541
+ return true
542
+ end
543
+
544
+ def add_to_queue(command)
545
+ begin
546
+ if @connected || @make_break
547
+ if @com_paused && !@make_break # We are calling from connected function (and we are connected)
548
+ command[:priority] -= (2 * @config[:priority_bonus]) # Double bonus
549
+ elsif @make_break
550
+ if !@connected && !@connecting
551
+ EM.next_tick do
552
+ do_connect
553
+ end
554
+ elsif @connected && @disconnecting
555
+ EM.next_tick do
556
+ add_to_queue(command)
557
+ end
558
+ return # Don't add to queue yet
559
+ end
560
+ end
561
+
562
+ add = true
563
+ if command[:name].present?
564
+ name = command[:name]
565
+ if @named_commands[name].nil?
566
+ @named_commands[name] = [[command[:priority]], command] #TODO:: we need to deal with the old commands emit values!
567
+ elsif @named_commands[name][0][-1] > command[:priority]
568
+ @named_commands[name][0].push(command[:priority])
569
+ @named_commands[name][1] = command #TODO:: we need to deal with the old commands emit values!
570
+ else
571
+ @named_commands[name][1] = command #TODO:: we need to deal with the old commands emit values!
572
+ add = false
573
+ end
574
+ end
575
+
576
+ @send_queue.push(command, command[:priority]) if add
577
+ end
578
+ rescue => e
579
+ EM.defer do
580
+ AutomateEm.print_error(logger, e, {
581
+ :message => "module #{@parent.class} in device_connection.rb, send : something went terribly wrong to get here",
582
+ :level => Logger::ERROR
583
+ })
584
+ end
585
+ end
586
+ end
587
+
588
+
589
+
590
+
591
+ #
592
+ # Connection state
593
+ #
594
+ def call_connected(*args) # Called from a deferred thread
595
+ #
596
+ # NOTE:: Same as add parent in device module!!!
597
+ # TODO:: Should break into a module and include it
598
+ #
599
+ @task_queue.push lambda {
600
+ @parent[:connected] = true
601
+
602
+ begin
603
+ @send_monitor.mon_synchronize { # Any sends in here are high priority (no emits as this function must return)
604
+ @parent.connected(*args) if @parent.respond_to?(:connected)
605
+ }
606
+ rescue => e
607
+ #
608
+ # save from bad user code (don't want to deplete thread pool)
609
+ #
610
+ AutomateEm.print_error(logger, e, {
611
+ :message => "module #{@parent.class} error whilst calling: connect",
612
+ :level => Logger::ERROR
613
+ })
614
+ ensure
615
+ EM.schedule do
616
+ #
617
+ # First connect if no commands pushed then we disconnect asap
618
+ #
619
+ if @make_break && @first_connect && @send_queue.size == 0
620
+ close_connection_after_writing
621
+ @disconnecting = true
622
+ @com_paused = true
623
+ @first_connect = false
624
+ elsif @com_paused
625
+ @com_paused = false
626
+ @wait_queue.push(nil)
627
+ else
628
+ EM.defer do
629
+ logger.info "Reconnected, communications not paused."
630
+ end
631
+ end
632
+ end
633
+ end
634
+ }
635
+ end
636
+
637
+
638
+
639
+ def default_send_options= (options)
640
+ @status_lock.synchronize {
641
+ @default_send_options.merge!(options)
642
+ }
643
+ end
644
+
645
+ def config= (options)
646
+ EM.schedule do
647
+ @config.merge!(options)
648
+ end
649
+ end
650
+
651
+
652
+
653
+
654
+ def shutdown(system)
655
+ if @parent.leave_system(system) == 0
656
+ @shutting_down.value = true
657
+
658
+ close_connection
659
+
660
+ @wait_queue.push(:shutdown)
661
+ @send_queue.push(:shutdown, -32768)
662
+ @task_queue.push(nil)
663
+
664
+ EM.defer do
665
+ begin
666
+ @parent[:connected] = false # Communicator off at this point
667
+ if @parent.respond_to?(:disconnected)
668
+ @task_lock.synchronize {
669
+ @parent.disconnected
670
+ }
671
+ end
672
+ rescue => e
673
+ #
674
+ # save from bad user code (don't want to deplete thread pool)
675
+ #
676
+ AutomateEm.print_error(logger, e, {
677
+ :message => "module #{@parent.class} error whilst calling: disconnected on shutdown",
678
+ :level => Logger::ERROR
679
+ })
680
+ ensure
681
+ @parent.clear_active_timers
682
+ ActiveRecord::Base.clear_active_connections!
683
+ end
684
+ end
685
+ end
686
+ end
687
+
688
+ end
689
+ end