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,373 @@
1
+ module AutomateEm
2
+ class Modules
3
+ @@modules = {} # modules (dependency_id => module class)
4
+ @@load_lock = Object.new.extend(MonitorMixin) #Mutex.new
5
+ @@loading = nil
6
+
7
+ def self.[] (dep_id)
8
+ @@load_lock.mon_synchronize {
9
+ @@modules[dep_id]
10
+ }
11
+ end
12
+
13
+ def self.load_module(dep)
14
+ @@load_lock.mon_synchronize {
15
+ begin
16
+ found = false
17
+
18
+ Rails.configuration.automate.module_paths.each do |path|
19
+ if File.exists?("#{path}/#{dep.filename}")
20
+ load "#{path}/#{dep.filename}"
21
+ found = true
22
+ break
23
+ end
24
+ end
25
+
26
+ if not found
27
+ raise "File not found!"
28
+ end
29
+
30
+ @@modules[dep.id] = dep.classname.classify.constantize
31
+ rescue => e
32
+ AutomateEm.print_error(System.logger, e, {
33
+ :message => "device module #{dep.actual_name} error whilst loading",
34
+ :level => Logger::ERROR
35
+ })
36
+ end
37
+
38
+ }
39
+ end
40
+ end
41
+
42
+ #
43
+ # TODO:: Consider allowing different dependancies use the same connection
44
+ # Means only the first will call received - others must use recieve blocks
45
+ #
46
+ class DeviceModule
47
+ @@instances = {} # db id => @instance
48
+ @@dbentry = {} # db id => db instance
49
+ @@devices = {} # ip:port:udp => @instance
50
+ @@lookup = {} # @instance => db id array
51
+ @@lookup_lock = Mutex.new
52
+
53
+ def initialize(system, controllerDevice)
54
+ @@lookup_lock.synchronize {
55
+ if @@instances[controllerDevice.id].nil?
56
+ @system = system
57
+ @device = controllerDevice.id
58
+ @@dbentry[controllerDevice.id] = controllerDevice
59
+ end
60
+ }
61
+ instantiate_module(controllerDevice)
62
+ end
63
+
64
+
65
+ def unload # should never be called on the reactor thread so no need to defer
66
+
67
+ @instance.base.shutdown(@system)
68
+
69
+ @@lookup_lock.synchronize {
70
+ db = @@lookup[@instance].delete(@device)
71
+ @@instances.delete(db)
72
+ db = @@dbentry.delete(db)
73
+ dev = "#{db.ip}:#{db.port}:#{db.udp}"
74
+
75
+ if @@lookup[@instance].empty?
76
+ @@lookup.delete(@instance)
77
+ if db.udp
78
+ $datagramServer.remove_device(db)
79
+ end
80
+ @@devices.delete(dev)
81
+ end
82
+ }
83
+ end
84
+
85
+
86
+ def self.lookup(instance)
87
+ @@lookup_lock.synchronize {
88
+ return @@dbentry[@@lookup[instance][0]]
89
+ }
90
+ end
91
+
92
+ def self.instance_of(db_id)
93
+ @@lookup_lock.synchronize {
94
+ return @@instances[db_id]
95
+ }
96
+ end
97
+
98
+
99
+ attr_reader :instance
100
+
101
+
102
+ protected
103
+
104
+
105
+ def instantiate_module(controllerDevice)
106
+ if Modules[controllerDevice.dependency_id].nil?
107
+ Modules.load_module(controllerDevice.dependency) # This is the re-load code function (live bug fixing - removing functions does not work)
108
+ end
109
+
110
+
111
+ baselookup = "#{controllerDevice.ip}:#{controllerDevice.port}:#{controllerDevice.udp}"
112
+
113
+ @@lookup_lock.lock
114
+ if @@devices[baselookup].nil?
115
+
116
+ #
117
+ # Instance of a user module
118
+ #
119
+ @instance = Modules[controllerDevice.dependency_id].new(controllerDevice.tls, controllerDevice.makebreak)
120
+ @instance.join_system(@system)
121
+ @@instances[@device] = @instance
122
+ @@devices[baselookup] = @instance
123
+ @@lookup[@instance] = [@device]
124
+ @@lookup_lock.unlock #UNLOCK!! so we can lookup settings in on_load
125
+
126
+ devBase = nil
127
+
128
+ loaded = Proc.new {
129
+ EM.defer do
130
+ if @instance.respond_to?(:on_load)
131
+ begin
132
+ @instance.on_load
133
+ rescue => e
134
+ AutomateEm.print_error(System.logger, e, {
135
+ :message => "device module #{@instance.class} error whilst calling: on_load",
136
+ :level => Logger::ERROR
137
+ })
138
+ ensure
139
+ ActiveRecord::Base.clear_active_connections!
140
+ end
141
+ end
142
+
143
+ if controllerDevice.udp
144
+
145
+ devBase.call_connected # UDP is stateless (always connected)
146
+
147
+ end
148
+ end
149
+ }
150
+
151
+ if !controllerDevice.udp
152
+ res = ResolverJob.new(controllerDevice.ip)
153
+ res.callback {|ip|
154
+ EM.connect ip, controllerDevice.port, Device::Base, @instance
155
+ loaded.call
156
+ }
157
+ res.errback {|error|
158
+ EM.defer do
159
+ System.logger.info error.message + " connecting to #{controllerDevice.dependency.actual_name} @ #{controllerDevice.ip} in #{controllerDevice.control_system.name}"
160
+ end
161
+ EM.connect "127.0.0.1", 10, Device::Base, @instance # Connect to a nothing port until the device name is found or updated
162
+ loaded.call
163
+ }
164
+ else
165
+ #
166
+ # Load UDP device here
167
+ # Create UDP base
168
+ # Add device to server
169
+ # => TODO::test!!
170
+ # Call connected
171
+ #
172
+ devBase = DatagramBase.new(@instance)
173
+ $datagramServer.add_device(controllerDevice, devBase)
174
+ loaded.call
175
+ end
176
+
177
+ #@@devices[baselookup] = Modules.loading # set in device_connection (see todo above)
178
+ else
179
+ #
180
+ # add parent may lock at this point!
181
+ #
182
+ @instance = @@devices[baselookup]
183
+ @@lookup[@instance] << @device
184
+ @@instances[@device] = @instance
185
+ EM.defer do
186
+ @instance.join_system(@system)
187
+ end
188
+ @@lookup_lock.unlock #UNLOCK!!
189
+ end
190
+ end
191
+ end
192
+
193
+
194
+ class ServiceModule
195
+ @@instances = {} # db id => @instance
196
+ @@dbentry = {} # db id => db instance
197
+ @@services = {} # uri => @instance
198
+ @@lookup = {} # @instance => db id array
199
+ @@lookup_lock = Mutex.new
200
+
201
+ def initialize(system, controllerService)
202
+ @@lookup_lock.synchronize {
203
+ if @@instances[controllerService.id].nil?
204
+ @system = system
205
+ @service = controllerService.id
206
+ @@dbentry[controllerService.id] = controllerService
207
+ end
208
+ }
209
+ instantiate_module(controllerService)
210
+ end
211
+
212
+
213
+ def unload # should never be called on the reactor thread so no need to defer
214
+
215
+ @instance.base.shutdown(@system)
216
+
217
+ @@lookup_lock.synchronize {
218
+ db = @@lookup[@instance].delete(@service)
219
+ @@instances.delete(db)
220
+ db = @@dbentry.delete(db)
221
+
222
+ if @@lookup[@instance].empty?
223
+ @@lookup.delete(@instance)
224
+ @@services.delete(db.uri)
225
+ end
226
+ }
227
+ end
228
+
229
+
230
+ def self.lookup(instance)
231
+ @@lookup_lock.synchronize {
232
+ return @@dbentry[@@lookup[instance][0]]
233
+ }
234
+ end
235
+
236
+ def self.instance_of(db_id)
237
+ @@lookup_lock.synchronize {
238
+ return @@instances[db_id]
239
+ }
240
+ end
241
+
242
+
243
+ attr_reader :instance
244
+
245
+
246
+ protected
247
+
248
+
249
+ def instantiate_module(controllerService)
250
+ if Modules[controllerService.dependency_id].nil?
251
+ Modules.load_module(controllerService.dependency) # This is the re-load code function (live bug fixing - removing functions does not work)
252
+ end
253
+
254
+ @@lookup_lock.lock
255
+ if @@services[controllerService.uri].nil?
256
+
257
+ #
258
+ # Instance of a user module
259
+ #
260
+ @instance = Modules[controllerService.dependency_id].new
261
+ @instance.join_system(@system)
262
+ @@instances[@service] = @instance
263
+ @@services[controllerService.uri] = @instance
264
+ @@lookup[@instance] = [@service]
265
+ @@lookup_lock.unlock #UNLOCK
266
+
267
+ HttpService.new(@instance, controllerService)
268
+
269
+
270
+ if @instance.respond_to?(:on_load)
271
+ begin
272
+ @instance.on_load
273
+ rescue => e
274
+ AutomateEm.print_error(System.logger, e, {
275
+ :message => "service module #{@instance.class} error whilst calling: on_load",
276
+ :level => Logger::ERROR
277
+ })
278
+ ensure
279
+ ActiveRecord::Base.clear_active_connections!
280
+ end
281
+ end
282
+ else
283
+ #
284
+ # add parent may lock at this point!
285
+ #
286
+ @instance = @@services[controllerService.uri]
287
+ @@lookup[@instance] << @service
288
+ @@instances[@service] = @instance
289
+ EM.defer do
290
+ @instance.join_system(@system)
291
+ end
292
+ @@lookup_lock.unlock #UNLOCK
293
+ end
294
+ end
295
+ end
296
+
297
+
298
+ class LogicModule
299
+ @@instances = {} # id => @instance
300
+ @@lookup = {} # @instance => DB Record
301
+ @@lookup_lock = Mutex.new
302
+
303
+
304
+ def initialize(system, controllerLogic)
305
+ @@lookup_lock.synchronize {
306
+ if @@instances[controllerLogic.id].nil?
307
+ instantiate_module(controllerLogic, system)
308
+ end
309
+ }
310
+ if @instance.respond_to?(:on_load)
311
+ begin
312
+ @instance.on_load
313
+ rescue => e
314
+ AutomateEm.print_error(System.logger, e, {
315
+ :message => "logic module #{@instance.class} error whilst calling: on_load",
316
+ :level => Logger::ERROR
317
+ })
318
+ ensure
319
+ ActiveRecord::Base.clear_active_connections!
320
+ end
321
+ end
322
+ end
323
+
324
+ def unload
325
+ if @instance.respond_to?(:on_unload)
326
+ begin
327
+ @instance.on_unload
328
+ rescue => e
329
+ AutomateEm.print_error(System.logger, e, {
330
+ :message => "logic module #{@instance.class} error whilst calling: on_unload",
331
+ :level => Logger::ERROR
332
+ })
333
+ ensure
334
+ ActiveRecord::Base.clear_active_connections!
335
+ end
336
+ end
337
+
338
+ @instance.clear_active_timers
339
+
340
+ @@lookup_lock.synchronize {
341
+ db = @@lookup.delete(@instance)
342
+ @@instances.delete(db.id)
343
+ }
344
+ end
345
+
346
+ def self.lookup(instance)
347
+ @@lookup_lock.synchronize {
348
+ return @@lookup[instance]
349
+ }
350
+ end
351
+
352
+ def self.instance_of(db_id)
353
+ @@lookup_lock.synchronize {
354
+ return @@instances[db_id]
355
+ }
356
+ end
357
+
358
+ attr_reader :instance
359
+
360
+
361
+ protected
362
+
363
+
364
+ def instantiate_module(controllerLogic, system)
365
+ if Modules[controllerLogic.dependency_id].nil?
366
+ Modules.load_module(controllerLogic.dependency) # This is the re-load code function (live bug fixing - removing functions does not work)
367
+ end
368
+ @instance = Modules[controllerLogic.dependency_id].new(system)
369
+ @@instances[controllerLogic.id] = @instance
370
+ @@lookup[@instance] = controllerLogic
371
+ end
372
+ end
373
+ end
@@ -0,0 +1,76 @@
1
+ module AutomateEm
2
+
3
+ class ResolverPool
4
+
5
+ def initialize(size = 30)
6
+
7
+ @size = size
8
+ @jobs = Queue.new
9
+
10
+ @pool = Array.new(@size) do |i|
11
+
12
+ Thread.new do
13
+ #Thread.current[:id] = i
14
+ Thread.current.priority = Thread.current.priority - 1
15
+ loop do
16
+ begin
17
+ job = @jobs.pop
18
+ job.resolve
19
+ rescue => e
20
+ #
21
+ # Print error here
22
+ #
23
+ end
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ def schedule(job)
32
+ @jobs << job
33
+ end
34
+
35
+ end
36
+
37
+
38
+ class ResolverJob
39
+
40
+ include EM::Deferrable
41
+
42
+ def initialize(hostname)
43
+ if IPAddress.valid? hostname
44
+ self.succeed(hostname)
45
+ else
46
+ @hostname = hostname
47
+
48
+ #
49
+ # Enter self into resolver queue
50
+ #
51
+ if EM.reactor_thread?
52
+ EM.defer do
53
+ AutomateEm.resolver.schedule(self)
54
+ end
55
+ else
56
+ AutomateEm.resolver.schedule(self)
57
+ end
58
+ end
59
+ end
60
+
61
+ def resolve
62
+ begin
63
+ ip = Resolv.getaddress(@hostname)
64
+ EM.schedule do
65
+ self.succeed(ip)
66
+ end
67
+ rescue => e
68
+ EM.schedule do
69
+ self.fail(e)
70
+ end
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,356 @@
1
+ module AutomateEm
2
+ class System
3
+
4
+ @@systems = {:'---GOD---' => self} # system_name => system instance
5
+ @@controllers = {} # controller_id => system instance
6
+ @@logger = nil
7
+ @@communicator = AutomateEm::Communicator.new(self)
8
+ @@communicator.start(true)
9
+ @@god_lock = Mutex.new
10
+
11
+
12
+ def self.new_system(controller, log_level = Logger::INFO)
13
+ begin
14
+ if controller.class == Fixnum
15
+ controller = ControlSystem.find(controller)
16
+ elsif controller.class == String
17
+ controller = ControlSystem.where('name = ?', controller).first
18
+ end
19
+
20
+ if controller.class != ControlSystem
21
+ raise 'invalid controller identifier'
22
+ end
23
+
24
+ @@god_lock.lock
25
+ if @@controllers[controller.id].nil?
26
+ @@god_lock.unlock
27
+ sys = System.new(controller, log_level)
28
+ if controller.active
29
+ begin
30
+ sys.start(true) # as this is loading the first time we ignore controller active
31
+ rescue => e
32
+ AutomateEm.print_error(@@logger, e, {
33
+ :message => "Error starting system in new_system, stopping..",
34
+ :level => Logger::ERROR
35
+ })
36
+ begin
37
+ sys.stop
38
+ EM.schedule do
39
+ EM.add_timer(60) do # Attempt a single restart
40
+ EM.defer do
41
+ begin
42
+ sys.start(true)
43
+ rescue => e
44
+ begin
45
+ AutomateEm.print_error(@@logger, e, {
46
+ :message => "Second start attempt failed..",
47
+ :level => Logger::ERROR
48
+ })
49
+ sys.stop
50
+ rescue
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ rescue => e
57
+ AutomateEm.print_error(@@logger, e, {
58
+ :message => "Error stopping system after problems starting..",
59
+ :level => Logger::ERROR
60
+ })
61
+ end
62
+ end
63
+ end
64
+ else
65
+ @@god_lock.unlock
66
+ end
67
+ ensure
68
+ ActiveRecord::Base.clear_active_connections! # Clear any unused connections
69
+ end
70
+ end
71
+
72
+
73
+ #
74
+ # Reloads a dependency live
75
+ # This is the re-load code function (live bug fixing - removing functions does not work)
76
+ #
77
+ def self.reload(dep)
78
+ System.logger.info "reloading dependency: #{dep}"
79
+
80
+ dep = Dependency.find(dep)
81
+ Modules.load_module(dep)
82
+
83
+ updated = {}
84
+ dep.devices.select('id').each do |dev|
85
+ begin
86
+ inst = DeviceModule.instance_of(dev.id)
87
+ inst.on_update if (!!!updated[inst]) && inst.respond_to?(:on_update)
88
+ ensure
89
+ updated[inst] = true
90
+ end
91
+ end
92
+
93
+ updated = {}
94
+ dep.logics.select('id').each do |log|
95
+ begin
96
+ inst = LogicModule.instance_of(log.id)
97
+ inst.on_update if (!!!updated[inst]) && inst.respond_to?(:on_update)
98
+ ensure
99
+ updated[inst] = true
100
+ end
101
+ end
102
+
103
+ ActiveRecord::Base.clear_active_connections! # Clear any unused connections
104
+ end
105
+
106
+ #
107
+ # Allows for system updates on the fly
108
+ # Dangerous (Could be used to add on the fly interfaces)
109
+ #
110
+ def self.force_load_file(path)
111
+ load path if File.exists?(path) && File.extname(path) == '.rb'
112
+ end
113
+
114
+
115
+ #
116
+ # System Logger
117
+ #
118
+ def self.logger
119
+ @@logger
120
+ end
121
+
122
+ def self.logger=(log)
123
+ @@logger = log
124
+ end
125
+
126
+ #def self.controllers
127
+ # @@controllers
128
+ #end
129
+
130
+ #def self.systems
131
+ # @@systems
132
+ #end
133
+
134
+ def self.communicator
135
+ @@communicator
136
+ end
137
+
138
+ def self.[] (system)
139
+ system = system.to_sym if system.class == String
140
+ @@god_lock.synchronize {
141
+ @@systems[system]
142
+ }
143
+ end
144
+
145
+
146
+
147
+ #
148
+ # For access via communicator as a super user
149
+ #
150
+ def self.modules
151
+ self
152
+ end
153
+ def self.instance
154
+ self
155
+ end
156
+ def instance
157
+ self
158
+ end
159
+ # ---------------------------------
160
+
161
+
162
+
163
+ #
164
+ # Module accessor
165
+ #
166
+ def [] (mod)
167
+ mod = mod.to_sym if mod.class == String
168
+ @modules[mod].instance
169
+ end
170
+
171
+ attr_reader :modules
172
+ attr_reader :communicator
173
+ attr_reader :controller
174
+ attr_accessor :logger
175
+
176
+
177
+
178
+ #
179
+ # Starts the control system if not running
180
+ #
181
+ def start(force = false)
182
+ System.logger.info "starting #{@controller.name}"
183
+ @sys_lock.synchronize {
184
+ @@god_lock.synchronize {
185
+ @@systems.delete(@controller.name.to_sym)
186
+ @controller.reload #(:lock => true)
187
+ @@systems[@controller.name.to_sym] = self
188
+ }
189
+
190
+ if !@controller.active || force
191
+ if @logger.nil?
192
+ if Rails.env.production?
193
+ @logger = Logger.new("#{ROOT_DIR}/interface/log/system_#{@controller.id}.log", 10, 4194304)
194
+ else
195
+ @logger = Logger.new(STDOUT)
196
+ end
197
+ @logger.formatter = proc { |severity, datetime, progname, msg|
198
+ "#{datetime.strftime("%d/%m/%Y @ %I:%M%p")} #{severity}: #{@controller.name} - #{msg}\n"
199
+ }
200
+ end
201
+
202
+ @controller.devices.includes(:dependency).each do |device|
203
+ load_hooks(device, DeviceModule.new(self, device))
204
+ end
205
+
206
+ @controller.services.includes(:dependency).each do |service|
207
+ load_hooks(service, ServiceModule.new(self, service))
208
+ end
209
+
210
+ @controller.logics.includes(:dependency).each do |logic|
211
+ load_hooks(logic, LogicModule.new(self, logic))
212
+ end
213
+ end
214
+
215
+ @controller.active = true
216
+ @controller.save
217
+
218
+ @communicator.start
219
+ }
220
+ end
221
+
222
+ #
223
+ # Stops the current control system
224
+ # Loops through the module instances.
225
+ #
226
+ def stop
227
+ System.logger.info "stopping #{@controller.name}"
228
+ @sys_lock.synchronize {
229
+ stop_nolock
230
+ }
231
+ end
232
+
233
+ #
234
+ # Unload and then destroy self
235
+ #
236
+ def delete
237
+ System.logger.info "deleting #{@controller.name}"
238
+ @sys_lock.synchronize {
239
+ stop_nolock
240
+
241
+ @@god_lock.synchronize {
242
+ @@systems.delete(@controller.name.to_sym)
243
+ @@controllers.delete(@controller.id)
244
+ }
245
+
246
+ begin
247
+ @controller.destroy!
248
+ rescue
249
+ # Controller may already be deleted
250
+ end
251
+ @modules = nil
252
+ }
253
+ end
254
+
255
+
256
+ #
257
+ # Log level changing on the fly
258
+ #
259
+ def log_level(level)
260
+ @sys_lock.synchronize {
261
+ @log_level = AutomateEm::get_log_level(level)
262
+ if @controller.active
263
+ @logger.level = @log_level
264
+ end
265
+ }
266
+ end
267
+
268
+
269
+ protected
270
+
271
+
272
+ def stop_nolock
273
+
274
+ begin
275
+ @@god_lock.synchronize {
276
+ @@systems.delete(@controller.name.to_sym)
277
+ @controller.reload(:lock => true)
278
+ @@systems[@controller.name.to_sym] = self
279
+ }
280
+ rescue
281
+ # Assume controller may have been deleted
282
+ end
283
+
284
+ if @controller.active
285
+ @communicator.shutdown
286
+ modules_unloaded = {}
287
+ @modules.each_value do |mod|
288
+
289
+ if modules_unloaded[mod] == nil
290
+ modules_unloaded[mod] = :unloaded
291
+ mod.unload
292
+ end
293
+
294
+ end
295
+ @modules = {} # Modules no longer referenced. Cleanup time!
296
+ @logger.close if Rails.env.production?
297
+ @logger = nil
298
+ end
299
+
300
+ @controller.active = false
301
+ begin
302
+ @controller.save
303
+ rescue
304
+ # Assume controller may have been deleted
305
+ end
306
+ end
307
+
308
+
309
+ def load_hooks(device, mod)
310
+ module_name = device.dependency.module_name
311
+ count = 2 # 2 is correct
312
+
313
+ #
314
+ # Loads the modules and auto-names them (display_1, display_2)
315
+ # The first module of a type has two names (display and display_1 for example)
316
+ # Load order is controlled by the control_system model based on the ordinal
317
+ #
318
+ if not @modules[module_name.to_sym].nil?
319
+ while @modules["#{module_name}_#{count}".to_sym].present?
320
+ count += 1
321
+ end
322
+ module_name = "#{module_name}_#{count}"
323
+ else
324
+ @modules["#{module_name}_1".to_sym] = mod
325
+ end
326
+ @modules[module_name.to_sym] = mod
327
+
328
+ #
329
+ # Allow for system specific custom names
330
+ #
331
+ if !device.custom_name.nil?
332
+ @modules[device.custom_name.to_sym] = mod
333
+ end
334
+ end
335
+
336
+
337
+ def initialize(controller, log_level)
338
+
339
+
340
+ @modules = {} # controller modules :name => module instance (device or logic)
341
+ @communicator = AutomateEm::Communicator.new(self)
342
+ @log_level = log_level
343
+ @controller = controller
344
+ @sys_lock = Mutex.new
345
+
346
+
347
+ #
348
+ # Setup the systems links
349
+ #
350
+ @@god_lock.synchronize {
351
+ @@systems[@controller.name.to_sym] = self # it may not be started
352
+ @@controllers[@controller.id] = self
353
+ }
354
+ end
355
+ end
356
+ end