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