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,10 @@
1
+ class UserZone < ActiveRecord::Base
2
+ belongs_to :zone
3
+ belongs_to :group
4
+
5
+
6
+ protected
7
+
8
+
9
+ validates_presence_of :group, :zone
10
+ end
@@ -0,0 +1,16 @@
1
+ class Zone < ActiveRecord::Base
2
+ has_many :user_zones
3
+ has_many :controller_zones
4
+
5
+ has_many :groups, :through => :user_zones
6
+ has_many :control_systems, :through => :controller_zones
7
+
8
+ has_many :settings, :as => :object, :dependent => :destroy
9
+
10
+
11
+ protected
12
+
13
+
14
+ validates_presence_of :name
15
+ validates_uniqueness_of :name
16
+ end
@@ -0,0 +1,147 @@
1
+ class Init < ActiveRecord::Migration
2
+ def change
3
+ #
4
+ # These represent individual systems
5
+ #
6
+ create_table :control_systems do |t|
7
+ t.string :name, :allow_null => false
8
+ t.text :description
9
+ t.boolean :active, :default => true, :allow_null => false
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ #
15
+ # Describe modules that can be loaded and exist on the system
16
+ #
17
+ create_table :dependencies do |t|
18
+ t.references :dependency
19
+
20
+ t.string :classname, :allow_null => false
21
+ t.string :filename, :allow_null => false
22
+
23
+ t.string :module_name, :allow_null => false # Type name (Projector)
24
+ t.string :actual_name, :allow_null => false # Real name (All NEC Projectors)
25
+ t.text :description
26
+
27
+ t.datetime :version_loaded
28
+
29
+ t.timestamps
30
+ end
31
+
32
+ #
33
+ # Device and Logic instances
34
+ # => Priority provides load order (high priorities load first)
35
+ #
36
+ create_table :controller_devices do |t|
37
+ t.references :control_system,:allow_null => false
38
+ t.references :dependency, :allow_null => false
39
+
40
+ t.string :ip, :allow_null => false
41
+ t.integer :port,:allow_null => false
42
+
43
+ t.boolean :tls, :default => false, :allow_null => false
44
+ t.boolean :udp, :default => false, :allow_null => false
45
+
46
+ t.integer :priority, :default => 0
47
+
48
+ t.string :custom_name # projector_left
49
+
50
+ t.timestamps
51
+ end
52
+
53
+ create_table :controller_logics do |t|
54
+ t.references :control_system,:allow_null => false
55
+ t.references :dependency, :allow_null => false
56
+
57
+ t.integer :priority, :default => 0
58
+
59
+ t.string :custom_name
60
+
61
+ t.timestamps
62
+ end
63
+
64
+ #
65
+ # Settings relating to Dependencies, Devices instances, Interfaces and Interface Instances
66
+ #
67
+ create_table :settings do |t|
68
+ t.references :object, :polymorphic => true
69
+
70
+ t.string :name, :allow_null => false
71
+ t.text :description
72
+
73
+ t.integer :value_type, :allow_null => false
74
+
75
+ t.float :float_value
76
+ t.integer :integer_value # doubles as boolean (type -1)
77
+ t.text :text_value
78
+ t.datetime :datetime_value
79
+
80
+ t.timestamps
81
+ end
82
+
83
+ #
84
+ # Describes the interfaces avaliable
85
+ #
86
+ #create_table :guis do |t|
87
+ # t.string :name, :allow_null => false
88
+ # t.text :description
89
+
90
+ # t.datetime :version # Cache update indicator
91
+ # t.text :render_path # Path of file to render
92
+
93
+ # Location information here for
94
+ # access if the device is local
95
+
96
+ # t.timestamps
97
+ #end
98
+
99
+ #
100
+ # Zones can be used to classify the various control systems
101
+ # => General Type (meeting room, seminar room)
102
+ # => Floor, Building, Campus
103
+ # This allows fine grained access control for users
104
+ #
105
+ create_table :zones do |t|
106
+ t.string :name, :allow_null => false
107
+ t.text :description
108
+
109
+ t.timestamps
110
+ end
111
+
112
+ create_table :user_zones do |t|
113
+ t.references :group
114
+ t.references :zone
115
+
116
+ t.integer :privilege_map # if not null overrides user default privilege map (user zones OR'ed)
117
+
118
+ t.timestamps
119
+ end
120
+
121
+ create_table :controller_zones do |t|
122
+ t.references :control_system
123
+ t.references :zone
124
+
125
+ t.timestamps
126
+ end
127
+
128
+ #
129
+ # Trusted devices
130
+ # => System 0 cannot trust any interface as does not have an explicit controller id
131
+ #
132
+ create_table :trusted_devices do |t|
133
+ t.references :user, :allow_null => false
134
+ t.references :control_system,:allow_null => false
135
+
136
+ t.string :description, :allow_null => false
137
+ t.text :notes
138
+
139
+ t.string :one_time_key
140
+ t.string :next_key
141
+ t.datetime :expires # Expire devices (staff member leaving etc)
142
+ t.datetime :last_authenticated # Cache update indicator
143
+
144
+ t.timestamps
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,9 @@
1
+ class OneTimeKey < ActiveRecord::Migration
2
+ def up
3
+ rename_column :trusted_devices, :description, :reason
4
+ end
5
+
6
+ def down
7
+ rename_column :trusted_devices, :reason, :description
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class EncryptSetting < ActiveRecord::Migration
2
+ def up
3
+ add_column :settings, :encrypt_setting, :boolean, :default => false
4
+ end
5
+
6
+ def down
7
+ remove_column :settings, :encrypt_setting
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ class Servers < ActiveRecord::Migration
2
+ def up
3
+ create_table :servers do |t|
4
+ t.boolean :online, :allow_null => false, :default => true
5
+
6
+ t.string :hostname, :allow_null => false
7
+
8
+ t.text :notes
9
+ end
10
+ end
11
+
12
+ def down
13
+ #drop table
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ class DefaultPort < ActiveRecord::Migration
2
+ def up
3
+ add_column :dependencies, :default_port, :integer
4
+ end
5
+
6
+ def down
7
+ remove_column :dependencies, :default_port
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class Makebreak < ActiveRecord::Migration
2
+ def up
3
+ add_column :controller_devices, :makebreak, :boolean, :default => false, :allow_null => false
4
+ end
5
+
6
+ def down
7
+ remove_column :controller_devices, :makebreak
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ class CreateControllerHttpServices < ActiveRecord::Migration
2
+ def change
3
+ create_table :controller_http_services do |t|
4
+
5
+ t.references :control_system,:allow_null => false
6
+ t.references :dependency, :allow_null => false
7
+
8
+ t.string :uri, :allow_null => false
9
+
10
+ t.integer :priority, :default => 0, :allow_null => false
11
+ t.string :custom_name # projector_left
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ add_column :dependencies, :default_uri, :string
17
+ end
18
+ end
@@ -0,0 +1,155 @@
1
+ require "automate-em/engine"
2
+
3
+ #
4
+ # TODO:: Use autoload here to avoid loading these unless control is running!
5
+ #
6
+
7
+ #
8
+ # STD LIB
9
+ #
10
+ require 'observer'
11
+ require 'yaml'
12
+ require 'thread'
13
+ require 'monitor'
14
+ require 'Socket' # for DNS lookups
15
+ require 'Logger'
16
+
17
+
18
+ #
19
+ # Gems
20
+ #
21
+ require 'rubygems'
22
+ require 'eventmachine'
23
+ require 'em-priority-queue'
24
+ require 'em-http'
25
+ require 'rufus/scheduler'
26
+ require 'ipaddress'
27
+
28
+
29
+ #
30
+ # Library Files
31
+ #
32
+ require 'automate-em/constants.rb'
33
+ require 'automate-em/utilities.rb'
34
+ require 'automate-em/status.rb'
35
+
36
+ require 'automate-em/core/resolver_pool.rb'
37
+ require 'automate-em/core/modules.rb'
38
+ require 'automate-em/core/communicator.rb'
39
+ require 'automate-em/core/system.rb'
40
+
41
+ require 'automate-em/device/device.rb'
42
+ require 'automate-em/device/device_connection.rb'
43
+ require 'automate-em/device/datagram_server.rb'
44
+ require 'automate-em/device/tcp_control.rb'
45
+
46
+ require 'automate-em/service/service.rb'
47
+ require 'automate-em/service/http_service.rb'
48
+
49
+ require 'automate-em/logic/logic.rb'
50
+
51
+ require 'automate-em/interfaces/html5.rb'
52
+
53
+
54
+ module AutomateEm
55
+
56
+
57
+ def self.load_paths= (paths)
58
+ @@load_paths = ([] << paths) # TODO:: this doesn't work
59
+ end
60
+
61
+
62
+ def self.scheduler
63
+ @@scheduler
64
+ end
65
+
66
+
67
+ def self.resolver
68
+ @@resolver
69
+ end
70
+
71
+
72
+ def self.get_log_level(level)
73
+ if level.nil?
74
+ return Logger::INFO
75
+ else
76
+ return case level.downcase.to_sym
77
+ when :debug
78
+ Logger::DEBUG
79
+ when :warn
80
+ Logger::WARN
81
+ when :error
82
+ Logger::ERROR
83
+ else
84
+ Logger::INFO
85
+ end
86
+ end
87
+ end
88
+
89
+
90
+ def self.boot
91
+
92
+ #
93
+ # System level logger
94
+ #
95
+ if Rails.env.production?
96
+ System.logger = Logger.new(Rails.root.join('log/system.log').to_s, 10, 4194304)
97
+ else
98
+ System.logger = Logger.new(STDOUT)
99
+ end
100
+ System.logger.formatter = proc { |severity, datetime, progname, msg|
101
+ "#{datetime.strftime("%d/%m/%Y @ %I:%M%p")} #{severity}: #{System} - #{msg}\n"
102
+ }
103
+
104
+ @@resolver = ResolverPool.new
105
+
106
+ EventMachine.run do
107
+ #
108
+ # Enable the scheduling system
109
+ #
110
+ @@scheduler = Rufus::Scheduler.start_new
111
+
112
+ EM.defer do
113
+ System.logger.debug "Started with #{EM.get_max_timers} timers avaliable"
114
+ System.logger.debug "Started with #{EM.threadpool_size} threads in pool"
115
+ end
116
+
117
+ #
118
+ # Start the UDP server
119
+ #
120
+ EM.open_datagram_socket "0.0.0.0", 0, DatagramServer
121
+
122
+ #
123
+ # Load the system based on the database
124
+ #
125
+ ControlSystem.all.each do |controller|
126
+ EM.defer do
127
+ begin
128
+ System.logger.debug "Booting #{controller.name}"
129
+ System.new_system(controller, Rails.configuration.automate.log_level)
130
+ rescue => e
131
+ AutomateEm.print_error(AutomateEm::System.logger, e, {
132
+ :message => "Error during boot",
133
+ :level => Logger::FATAL
134
+ })
135
+ EventMachine::stop_event_loop
136
+ end
137
+ end
138
+ end
139
+
140
+ #
141
+ # Emit connection counts for logging
142
+ #
143
+ @@scheduler.every '10m' do
144
+ System.logger.info "There are #{EM.connection_count} connections to this server"
145
+ end
146
+
147
+ #
148
+ # We should AutoLoad the interfaces as plugins rather than having them in the core
149
+ #
150
+ EM.add_timer(20) do
151
+ System.start_websockets
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,6 @@
1
+ module AutomateEm
2
+ module Constants
3
+ On = true
4
+ Off = false
5
+ end
6
+ end
@@ -0,0 +1,318 @@
1
+ #
2
+ # There should be one communicator per system that handles the interface interfaces
3
+ # This will pass on registered status requirements and call functions as requested
4
+ #
5
+ # Interfaces will have to have a base class that abstracts the event machine code so that
6
+ # nothing runs on the reactor thread.
7
+ #
8
+ module AutomateEm
9
+ class Communicator
10
+
11
+ def initialize(system)
12
+ @system = system
13
+
14
+ @command_lock = Mutex.new
15
+ @status_lock = Mutex.new
16
+
17
+ @status_register = {}
18
+ @connected_interfaces = {}
19
+
20
+ @shutdown = true
21
+ end
22
+
23
+
24
+ def logger
25
+ @system.logger
26
+ end
27
+
28
+
29
+ def shutdown
30
+ @status_lock.synchronize {
31
+ @shutdown = true
32
+ @system.modules.each_value do |mod|
33
+ mod.instance.delete_observer(self)
34
+ end
35
+ @status_register = {}
36
+ @connected_interfaces.each_key do |soc|
37
+ soc.shutdown
38
+ end
39
+ }
40
+ logger.debug "-- Communicator shutdown"
41
+ end
42
+
43
+ def start(nolog = false) # Logging isn't active for the very first of these
44
+ @status_lock.synchronize {
45
+ @shutdown = false
46
+ }
47
+ logger.debug "-- Communicator started" unless nolog
48
+ end
49
+
50
+
51
+ #
52
+ # Systems avaliable to this user
53
+ #
54
+ def self.system_list(user)
55
+ response = {:ids => [], :names => []}
56
+
57
+ if user.class == User
58
+ user.control_systems.select('control_systems.id, control_systems.name').each do |controller|
59
+ if !!System[controller.name.to_sym]
60
+ response[:ids] << controller.id
61
+ response[:names] << controller.name
62
+ end
63
+ end # We ignore token requests here as they should know the system they can connect to
64
+ end
65
+ return response
66
+ end
67
+
68
+ #
69
+ # Set the system to communicate with
70
+ # Up to interfaces to maintain stability here (They should deal with errors)
71
+ #
72
+ def self.select(user, interface, system)
73
+ System.logger.debug "-- Interface #{interface.class} attempting to select system #{system}"
74
+ if system == 0
75
+ return nil unless user[:system_admin]
76
+ System.communicator.attach(interface)
77
+ else
78
+ sys = nil
79
+
80
+ if user.class == User
81
+ sys = user.control_systems.select('control_systems.name').where('control_systems.id = ? AND control_systems.active = ?', system.to_i, true).first
82
+
83
+ elsif user.class == TrustedDevice && user.control_system_id == system.to_i
84
+ sys = User.find(user.user_id).control_systems.select('control_systems.name, control_systems.active').where('control_systems.id = ?', system.to_i).first
85
+ if sys.nil?
86
+ #
87
+ # Kill comms, this key is not valid
88
+ # Invalidate key
89
+ #
90
+ user.expires = Time.now
91
+ user.save
92
+ interface.shutdown
93
+ return nil
94
+ elsif sys.active == false
95
+ #
96
+ # System offline... Disconnect
97
+ #
98
+ return false
99
+ end
100
+ end
101
+
102
+ system = sys.nil? ? nil : sys.name.to_sym
103
+ if System[system].nil?
104
+ interface.shutdown #kill comms
105
+ return nil
106
+ end
107
+
108
+ System.logger.debug "-- Interface #{interface.class} selected system #{system}"
109
+ return System[system].communicator.attach(interface)
110
+ end
111
+ end
112
+
113
+
114
+
115
+
116
+ #
117
+ # Keep track of connected systems
118
+ #
119
+ def disconnected(interface)
120
+ @status_lock.synchronize {
121
+ status_array = @connected_interfaces.delete(interface)
122
+ status_array.each do |status_hash|
123
+ status_hash.delete(interface)
124
+ end
125
+ #
126
+ # TODO::Refactor required
127
+ # This still isn't perfect as we could be observing modules we are not using...
128
+ #
129
+ }
130
+ logger.debug "-- Interface #{interface.class} disconnected" unless logger.nil?
131
+ end
132
+
133
+
134
+ #
135
+ # Keep track of status events
136
+ #
137
+ def register(interface, mod, status, &block)
138
+ mod_sym = mod.class == String ? mod.to_sym : mod # remember the symbol used by the interface to reference this module
139
+ status = status.to_sym if status.class == String
140
+
141
+ if @system.modules[mod_sym].present?
142
+ mod = @system.modules[mod_sym].instance # most efficient
143
+
144
+ theVal = nil
145
+ @status_lock.synchronize {
146
+ @status_register[mod] ||= {}
147
+ @status_register[mod][status] ||= {}
148
+ @status_register[mod][status][interface] = mod_sym
149
+ @connected_interfaces[interface] << @status_register[mod][status] unless @connected_interfaces[interface].nil?
150
+ theVal = mod[status]
151
+ }
152
+
153
+ mod.add_observer(self)
154
+ logger.debug "-- Interface #{interface.class} registered #{mod_sym}:#{status}"
155
+
156
+ #
157
+ # Send the status to this requestor!
158
+ # This is the same as in update
159
+ #
160
+ if !theVal.nil?
161
+ begin
162
+ function = "#{mod_sym.to_s.downcase}_#{status}_changed".to_sym
163
+
164
+ if interface.respond_to?(function)
165
+ interface.__send__(function, theVal)
166
+ else
167
+ interface.notify(mod_sym, status, theVal)
168
+ end
169
+ rescue => e
170
+ AutomateEm.print_error(logger, e, {
171
+ :message => "in communicator.rb, register : bad interface or user module code",
172
+ :level => Logger::ERROR
173
+ })
174
+ end
175
+ end
176
+ else
177
+ logger.warn "in communicator.rb, register : #{interface.class} called register on a bad module name"
178
+ block.call() unless block.nil? # Block will inform of any errors
179
+ end
180
+ rescue => e
181
+ begin
182
+ AutomateEm.print_error(logger, e, {
183
+ :message => "in communicator.rb, register : #{interface.class} failed to register #{mod.inspect}.#{status.inspect}",
184
+ :level => Logger::ERROR
185
+ })
186
+ block.call() unless block.nil? # Block will inform of any errors
187
+ rescue => x
188
+ AutomateEm.print_error(logger, x, {
189
+ :message => "in communicator.rb, register : #{interface.class} provided a bad block",
190
+ :level => Logger::WARN
191
+ })
192
+ end
193
+ ensure
194
+ ActiveRecord::Base.clear_active_connections!
195
+ end
196
+
197
+ def unregister(interface, mod, status, &block)
198
+ mod_sym = mod.to_sym if mod.class == String
199
+ status = status.to_sym if status.class == String
200
+
201
+ mod = @system.modules[mod_sym].instance
202
+ logger.debug "Interface #{interface.class} unregistered #{mod_sym}:#{status}"
203
+
204
+ @status_lock.synchronize {
205
+ @status_register[mod] ||= {}
206
+ @status_register[mod][status] ||= {}
207
+ @status_register[mod][status].delete(interface)
208
+ @connected_interfaces[interface].delete(@status_register[mod][status]) unless @connected_interfaces[interface].nil?
209
+
210
+ #
211
+ # TODO:: deleteing the observer will delete all status updates
212
+ # This needs to be more selective.
213
+ # We only delete the observer if all the mod[status]'s are empty
214
+ #
215
+ #if @status_register[mod][status].empty?
216
+ # mod.delete_observer(self)
217
+ #end
218
+ }
219
+ rescue => e
220
+ logger.warn "in communicator.rb, unregister : #{interface.class} called unregister when it was not needed"
221
+ #logger.warn e.message
222
+ #logger.warn e.backtrace
223
+ begin
224
+ block.call() if !block.nil? # Block will inform of any errors
225
+ rescue => x
226
+ AutomateEm.print_error(logger, x, {
227
+ :message => "in communicator.rb, unregister : #{interface.class} provided a bad block",
228
+ :level => Logger::WARN
229
+ })
230
+ end
231
+ end
232
+
233
+ def update(mod, status, data)
234
+ @status_lock.synchronize {
235
+ return if @status_register[mod].nil? || @status_register[mod][status].nil?
236
+
237
+ #
238
+ # Interfaces should implement the notify function
239
+ # Or a function for that particular event
240
+ #
241
+ @status_register[mod][status].each_pair do |interface, mod|
242
+ EM.defer do # to avoid deadlock
243
+ begin
244
+ function = "#{mod.to_s.downcase}_#{status}_changed".to_sym
245
+ if interface.respond_to?(function) # Can provide a function to deal with status updates
246
+ interface.__send__(function, data)
247
+ else
248
+ interface.notify(mod, status, data)
249
+ end
250
+ rescue => e
251
+ AutomateEm.print_error(logger, e, {
252
+ :message => "in communicator.rb, update : bad interface or user module code",
253
+ :level => Logger::ERROR
254
+ })
255
+ ensure
256
+ ActiveRecord::Base.clear_active_connections!
257
+ end
258
+ end
259
+ end
260
+ }
261
+ rescue => e
262
+ AutomateEm.print_error(logger, e, {
263
+ :message => "in communicator.rb, update : This should never happen",
264
+ :level => Logger::ERROR
265
+ })
266
+ end
267
+
268
+ #
269
+ # Pass commands to the selected system
270
+ #
271
+ def send_command(mod, command, *args, &block)
272
+ #
273
+ # Accept String, argument array
274
+ # String
275
+ #
276
+ mod = mod.to_sym if mod.class == String
277
+ logger.debug "-- Command requested #{mod}.#{command}(#{args})"
278
+
279
+ begin
280
+ @command_lock.synchronize {
281
+ @system.modules[mod].instance.public_send(command, *args) # Not send string however call function command
282
+ }
283
+ rescue => e
284
+ AutomateEm.print_error(logger, e, {
285
+ :message => "module #{mod} in communicator.rb, send_command : command unavaliable or bad module code",
286
+ :level => Logger::WARN
287
+ })
288
+ begin
289
+ block.call() unless block.nil? # Block will inform of any errors
290
+ rescue => x
291
+ AutomateEm.print_error(logger, x, {
292
+ :message => "in communicator.rb, send_command : interface provided bad block",
293
+ :level => Logger::ERROR
294
+ })
295
+ end
296
+ ensure
297
+ ActiveRecord::Base.clear_active_connections!
298
+ end
299
+ end
300
+
301
+
302
+ def attach(interface)
303
+ @status_lock.synchronize {
304
+ return nil if @shutdown || interface.nil?
305
+ @connected_interfaces[interface] = [] unless @connected_interfaces.include?(interface)
306
+ }
307
+ return self
308
+ end
309
+
310
+
311
+ protected
312
+
313
+
314
+ def unregister_all(interface)
315
+ # TODO:: Important to stop memory leaks
316
+ end
317
+ end
318
+ end