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.
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