automate-em 0.0.2 → 0.0.3

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.
@@ -85,7 +85,7 @@ class TokensController < ActionController::Base
85
85
 
86
86
 
87
87
  def servers
88
- render :json => Server.all
88
+ render :json => Server.where(:online => true).all
89
89
  end
90
90
 
91
91
 
@@ -14,7 +14,7 @@ class Dependency < ActiveRecord::Base
14
14
  protected
15
15
 
16
16
 
17
- validates_presence_of :classname, :filename, :module_name, :actual_name
18
- validates_uniqueness_of :filename
17
+ validates_presence_of :classname, :module_name, :actual_name
18
+ validates_uniqueness_of :classname
19
19
  validates_uniqueness_of :actual_name
20
20
  end
@@ -56,10 +56,8 @@ class Communicator
56
56
 
57
57
  if user.class == User
58
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
59
+ response[:ids] << controller.id
60
+ response[:names] << controller.name
63
61
  end # We ignore token requests here as they should know the system they can connect to
64
62
  end
65
63
  return response
@@ -77,11 +75,11 @@ class Communicator
77
75
  else
78
76
  sys = nil
79
77
 
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
78
+ if user.is_a? User
79
+ sys = user.control_systems.select('control_systems.id').where('control_systems.id = ? AND control_systems.active = ?', system.to_i, true).first
82
80
 
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
81
+ elsif user.is_a?(TrustedDevice) && user.control_system_id == system.to_i
82
+ sys = User.find(user.user_id).control_systems.select('control_systems.id, control_systems.active').where('control_systems.id = ?', system.to_i).first
85
83
  if sys.nil?
86
84
  #
87
85
  # Kill comms, this key is not valid
@@ -99,7 +97,7 @@ class Communicator
99
97
  end
100
98
  end
101
99
 
102
- system = sys.nil? ? nil : sys.name.to_sym
100
+ system = sys.nil? ? nil : sys.id
103
101
  if System[system].nil?
104
102
  interface.shutdown #kill comms
105
103
  return nil
@@ -278,7 +276,11 @@ class Communicator
278
276
 
279
277
  begin
280
278
  @command_lock.synchronize {
281
- @system.modules[mod].instance.public_send(command, *args) # Not send string however call function command
279
+ if @system != System
280
+ @system.modules[mod].instance.public_send(command, *args) # Not send string however call function command
281
+ else
282
+ @system.public_send(command, *args)
283
+ end
282
284
  }
283
285
  rescue => e
284
286
  AutomateEm.print_error(logger, e, {
@@ -1,42 +1,50 @@
1
1
  module AutomateEm
2
2
  class Modules
3
3
  @@modules = {} # modules (dependency_id => module class)
4
- @@load_lock = Object.new.extend(MonitorMixin) #Mutex.new
5
- @@loading = nil
4
+ @@load_lock = Mutex.new
6
5
 
7
6
  def self.[] (dep_id)
8
- @@load_lock.mon_synchronize {
7
+ @@load_lock.synchronize {
9
8
  @@modules[dep_id]
10
9
  }
11
10
  end
11
+
12
+ def self.lazy_load(dep)
13
+ theClass = Modules[dep.id]
14
+ theClass = Modules.load_module(dep) if theClass.nil?
15
+ theClass
16
+ end
12
17
 
13
18
  def self.load_module(dep)
14
- @@load_lock.mon_synchronize {
15
- begin
16
- found = false
17
- file = "#{dep.classname.underscore}.rb"
18
-
19
- Rails.configuration.automate.module_paths.each do |path|
20
- if File.exists?("#{path}/#{file}")
19
+ begin
20
+ found = false
21
+ file = "#{dep.classname.underscore}.rb"
22
+
23
+ Rails.configuration.automate.module_paths.each do |path|
24
+ if File.exists?("#{path}/#{file}")
25
+ @@load_lock.synchronize {
21
26
  load "#{path}/#{file}"
22
- found = true
23
- break
24
- end
27
+ }
28
+ found = true
29
+ break
25
30
  end
26
-
27
- if not found
28
- raise "File not found! (#{file})"
29
- end
30
-
31
- @@modules[dep.id] = dep.classname.constantize
32
- rescue => e
33
- AutomateEm.print_error(System.logger, e, {
34
- :message => "device module #{dep.actual_name} error whilst loading",
35
- :level => Logger::ERROR
36
- })
37
31
  end
38
-
39
- }
32
+
33
+ if not found
34
+ raise "File not found! (#{file})"
35
+ end
36
+
37
+ @@load_lock.synchronize {
38
+ @@modules[dep.id] = dep.classname.constantize # this is the return value
39
+ }
40
+ rescue Exception => e
41
+ AutomateEm.print_error(System.logger, e, {
42
+ :message => "device module #{dep.actual_name} error whilst loading.",
43
+ :level => Logger::ERROR
44
+ })
45
+
46
+ return false
47
+ end
40
48
  end
41
49
  end
42
50
 
@@ -51,7 +59,7 @@ module AutomateEm
51
59
  @@lookup = {} # @instance => db id array
52
60
  @@lookup_lock = Mutex.new
53
61
 
54
- def initialize(system, controllerDevice)
62
+ def initialize(system, controllerDevice, theModule)
55
63
  @@lookup_lock.synchronize {
56
64
  if @@instances[controllerDevice.id].nil?
57
65
  @system = system
@@ -59,7 +67,7 @@ module AutomateEm
59
67
  @@dbentry[controllerDevice.id] = controllerDevice
60
68
  end
61
69
  }
62
- instantiate_module(controllerDevice)
70
+ instantiate_module(controllerDevice, theModule)
63
71
  end
64
72
 
65
73
 
@@ -103,12 +111,7 @@ module AutomateEm
103
111
  protected
104
112
 
105
113
 
106
- def instantiate_module(controllerDevice)
107
- if Modules[controllerDevice.dependency_id].nil?
108
- Modules.load_module(controllerDevice.dependency) # This is the re-load code function (live bug fixing - removing functions does not work)
109
- end
110
-
111
-
114
+ def instantiate_module(controllerDevice, theModule)
112
115
  baselookup = "#{controllerDevice.ip}:#{controllerDevice.port}:#{controllerDevice.udp}"
113
116
 
114
117
  @@lookup_lock.lock
@@ -118,7 +121,7 @@ module AutomateEm
118
121
  # Instance of a user module
119
122
  #
120
123
  begin
121
- @instance = Modules[controllerDevice.dependency_id].new(controllerDevice.tls, controllerDevice.makebreak)
124
+ @instance = theModule.new(controllerDevice.tls, controllerDevice.makebreak)
122
125
  @instance.join_system(@system)
123
126
  @@instances[@device] = @instance
124
127
  @@devices[baselookup] = @instance
@@ -145,9 +148,9 @@ module AutomateEm
145
148
  end
146
149
 
147
150
  if controllerDevice.udp
148
-
149
- devBase.call_connected # UDP is stateless (always connected)
150
-
151
+ EM.schedule do
152
+ devBase.call_connected # UDP is stateless (always connected)
153
+ end
151
154
  end
152
155
  end
153
156
  }
@@ -205,7 +208,7 @@ module AutomateEm
205
208
  @@lookup = {} # @instance => db id array
206
209
  @@lookup_lock = Mutex.new
207
210
 
208
- def initialize(system, controllerService)
211
+ def initialize(system, controllerService, theModule)
209
212
  @@lookup_lock.synchronize {
210
213
  if @@instances[controllerService.id].nil?
211
214
  @system = system
@@ -213,7 +216,7 @@ module AutomateEm
213
216
  @@dbentry[controllerService.id] = controllerService
214
217
  end
215
218
  }
216
- instantiate_module(controllerService)
219
+ instantiate_module(controllerService, theModule)
217
220
  end
218
221
 
219
222
 
@@ -253,23 +256,21 @@ module AutomateEm
253
256
  protected
254
257
 
255
258
 
256
- def instantiate_module(controllerService)
257
- if Modules[controllerService.dependency_id].nil?
258
- Modules.load_module(controllerService.dependency) # This is the re-load code function (live bug fixing - removing functions does not work)
259
- end
260
-
259
+ def instantiate_module(controllerService, theModule)
261
260
  @@lookup_lock.lock
262
261
  if @@services[controllerService.uri].nil?
263
-
264
- #
265
- # Instance of a user module
266
- #
267
- @instance = Modules[controllerService.dependency_id].new
268
- @instance.join_system(@system)
269
- @@instances[@service] = @instance
270
- @@services[controllerService.uri] = @instance
271
- @@lookup[@instance] = [@service]
272
- @@lookup_lock.unlock #UNLOCK
262
+ begin
263
+ #
264
+ # Instance of a user module
265
+ #
266
+ @instance = theModule.new
267
+ @instance.join_system(@system)
268
+ @@instances[@service] = @instance
269
+ @@services[controllerService.uri] = @instance
270
+ @@lookup[@instance] = [@service]
271
+ ensure
272
+ @@lookup_lock.unlock #UNLOCK
273
+ end
273
274
 
274
275
  HttpService.new(@instance, controllerService)
275
276
 
@@ -308,10 +309,10 @@ module AutomateEm
308
309
  @@lookup_lock = Mutex.new
309
310
 
310
311
 
311
- def initialize(system, controllerLogic)
312
+ def initialize(system, controllerLogic, theModule)
312
313
  @@lookup_lock.synchronize {
313
314
  if @@instances[controllerLogic.id].nil?
314
- instantiate_module(controllerLogic, system)
315
+ instantiate_module(controllerLogic, system, theModule)
315
316
  end
316
317
  }
317
318
  if @instance.respond_to?(:on_load)
@@ -368,11 +369,8 @@ module AutomateEm
368
369
  protected
369
370
 
370
371
 
371
- def instantiate_module(controllerLogic, system)
372
- if Modules[controllerLogic.dependency_id].nil?
373
- Modules.load_module(controllerLogic.dependency) # This is the re-load code function (live bug fixing - removing functions does not work)
374
- end
375
- @instance = Modules[controllerLogic.dependency_id].new(system)
372
+ def instantiate_module(controllerLogic, system, theModule)
373
+ @instance = theModule.new(system)
376
374
  @@instances[controllerLogic.id] = @instance
377
375
  @@lookup[@instance] = controllerLogic
378
376
  end
@@ -1,78 +1,144 @@
1
1
  module AutomateEm
2
2
  class System
3
-
4
- @@systems = {:'---GOD---' => self} # system_name => system instance
5
- @@controllers = {} # controller_id => system instance
3
+ @@controllers = {0 => self} # controller_id => system instance (0 is the system class)
6
4
  @@logger = nil
7
- @@communicator = AutomateEm::Communicator.new(self)
5
+ @@communicator = AutomateEm::Communicator.new(self) # TODO:: remove the need for communicator.start
8
6
  @@communicator.start(true)
9
7
  @@god_lock = Mutex.new
10
8
 
11
9
 
12
- def self.new_system(controller, log_level = Logger::INFO)
10
+
11
+ #
12
+ # Error thrown means: mark as offline, do not retry and email
13
+ # Return false means: retry and email if a second attempt fails
14
+ # Return true means: all is good! system running
15
+ #
16
+ def self.start(controller, log_level = Logger::INFO)
13
17
  begin
14
- if controller.class == Fixnum
18
+
19
+ #
20
+ # Ensure we are dealing with a controller
21
+ #
22
+ if controller.is_a? Fixnum
15
23
  controller = ControlSystem.find(controller)
16
- elsif controller.class == String
24
+ elsif controller.is_a? String
17
25
  controller = ControlSystem.where('name = ?', controller).first
18
26
  end
19
27
 
20
- if controller.class != ControlSystem
28
+ if not controller.is_a? ControlSystem
21
29
  raise 'invalid controller identifier'
22
30
  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
31
+
32
+
33
+ #
34
+ # Check if the system is already loaded or loading
35
+ #
36
+ @@god_lock.synchronize {
37
+ if @@controllers[controller.id].present?
38
+ return true
39
+ end
40
+
41
+ begin
42
+ controller.reload(:lock => true)
43
+ if controller.active
44
+ return true
45
+ else
46
+ controller.active = true
62
47
  end
48
+ ensure
49
+ controller.save
50
+ end
51
+ }
52
+
53
+ #
54
+ # Create the system
55
+ #
56
+ system = System.new(controller, log_level)
57
+
58
+ #
59
+ # Load modules here (Producer)
60
+ #
61
+ proceed = Atomic.new(true)
62
+ queue = Queue.new
63
+ producer = Thread.new do # New thread here to prevent circular waits on the thread pool
64
+ begin
65
+ controller.devices.includes(:dependency).each do |device|
66
+ theClass = Modules.lazy_load(device.dependency)
67
+ raise "Load Error" if theClass == false
68
+ queue.push([device, theClass])
69
+ end
70
+
71
+ controller.services.includes(:dependency).each do |service|
72
+ theClass = Modules.lazy_load(service.dependency)
73
+ raise "Load Error" if theClass == false
74
+ queue.push([service, theClass])
75
+ end
76
+
77
+ controller.logics.includes(:dependency).each do |logic|
78
+ theClass = Modules.lazy_load(logic.dependency)
79
+ raise "Load Error" if theClass == false
80
+ queue.push([logic, theClass])
81
+ end
82
+ rescue
83
+ proceed.value = false
84
+ ensure
85
+ ActiveRecord::Base.clear_active_connections! # Clear any unused connections
86
+ queue.push(:done)
63
87
  end
64
- else
65
- @@god_lock.unlock
66
88
  end
89
+
90
+ #
91
+ # Consume the newly loaded modules here (Consumer)
92
+ #
93
+ mod = queue.pop
94
+ while mod.is_a?(Array) && proceed.value == true
95
+ begin
96
+ system.load(*mod)
97
+
98
+ mod = queue.pop
99
+ rescue => e
100
+ AutomateEm.print_error(@@logger, e, {
101
+ :message => "Error stopping system after problems starting..",
102
+ :level => Logger::ERROR
103
+ })
104
+
105
+ proceed.value = false
106
+ end
107
+ end
108
+
109
+
110
+ #
111
+ # Check if system modules loaded properly
112
+ #
113
+ if proceed.value == false
114
+ #
115
+ # Unload any loaded modules
116
+ #
117
+ system.stop
118
+
119
+ return false
120
+ end
121
+
122
+
123
+ #
124
+ # Setup the systems link
125
+ #
126
+ system.start # Start the systems communicator
127
+ return true
67
128
  ensure
68
129
  ActiveRecord::Base.clear_active_connections! # Clear any unused connections
69
130
  end
70
131
  end
71
132
 
72
133
 
134
+ def self.stop(system)
135
+ System[system].stop
136
+ end
137
+
138
+
73
139
  #
74
140
  # Reloads a dependency live
75
- # This is the re-load code function (live bug fixing - removing functions does not work)
141
+ # This is the re-load code function (live bug fixing - removing / adding / modifying functions)
76
142
  #
77
143
  def self.reload(dep)
78
144
  System.logger.info "reloading dependency: #{dep}"
@@ -90,6 +156,16 @@ module AutomateEm
90
156
  end
91
157
  end
92
158
 
159
+ updated = {}
160
+ dep.services.select('id').each do |ser|
161
+ begin
162
+ inst = ServiceModule.instance_of(ser.id)
163
+ inst.on_update if (!!!updated[inst]) && inst.respond_to?(:on_update)
164
+ ensure
165
+ updated[inst] = true
166
+ end
167
+ end
168
+
93
169
  updated = {}
94
170
  dep.logics.select('id').each do |log|
95
171
  begin
@@ -109,6 +185,11 @@ module AutomateEm
109
185
  #
110
186
  def self.force_load_file(path)
111
187
  load path if File.exists?(path) && File.extname(path) == '.rb'
188
+ rescue LoadError => e # load error explicitly handled
189
+ AutomateEm.print_error(System.logger, e, {
190
+ :message => "force load of #{path} failed",
191
+ :level => Logger::ERROR
192
+ })
112
193
  end
113
194
 
114
195
 
@@ -123,36 +204,30 @@ module AutomateEm
123
204
  @@logger = log
124
205
  end
125
206
 
126
- #def self.controllers
127
- # @@controllers
128
- #end
129
-
130
- #def self.systems
131
- # @@systems
132
- #end
133
-
134
207
  def self.communicator
135
208
  @@communicator
136
209
  end
137
210
 
138
211
  def self.[] (system)
139
- system = system.to_sym if system.class == String
212
+ if system.is_a?(Symbol) || system.is_a?(String)
213
+ id = ControlSystem.where('name = ?', system.to_s).pluck(:id).first
214
+ if id.nil? && system.is_a?(String)
215
+ system = system.to_i
216
+ else
217
+ system = id
218
+ end
219
+ end
220
+
140
221
  @@god_lock.synchronize {
141
- @@systems[system]
222
+ @@controllers[system]
142
223
  }
143
224
  end
144
225
 
145
226
 
146
227
 
147
228
  #
148
- # For access via communicator as a super user
229
+ # For access via communicator
149
230
  #
150
- def self.modules
151
- self
152
- end
153
- def self.instance
154
- self
155
- end
156
231
  def instance
157
232
  self
158
233
  end
@@ -174,49 +249,27 @@ module AutomateEm
174
249
  attr_accessor :logger
175
250
 
176
251
 
252
+ #
253
+ # Loads a module into a system
254
+ #
255
+ def load(dbSetting, theClass)
256
+ if dbSetting.is_a?(ControllerDevice)
257
+ load_hooks(dbSetting, DeviceModule.new(self, dbSetting, theClass))
258
+ elsif dbSetting.is_a?(ControllerHttpService)
259
+ load_hooks(dbSetting, ServiceModule.new(self, dbSetting, theClass))
260
+ else # ControllerLogic
261
+ load_hooks(dbSetting, LogicModule.new(self, dbSetting, theClass))
262
+ end
263
+ end
177
264
 
178
265
  #
179
- # Starts the control system if not running
266
+ # The system is ready to go
180
267
  #
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(Rails.root.join("log/system_#{@controller.id}.log").to_s, 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
268
+ def start
269
+ @@god_lock.synchronize {
270
+ @@controllers[@controller.id] = self
219
271
  }
272
+ @communicator.start
220
273
  end
221
274
 
222
275
  #
@@ -226,29 +279,31 @@ module AutomateEm
226
279
  def stop
227
280
  System.logger.info "stopping #{@controller.name}"
228
281
  @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
282
+ if @controller.active
283
+ @communicator.shutdown
284
+ modules_unloaded = {}
285
+ @modules.each_value do |mod|
286
+
287
+ if modules_unloaded[mod] == nil
288
+ modules_unloaded[mod] = :unloaded
289
+ mod.unload
290
+ end
291
+
292
+ end
293
+ @modules = {} # Modules no longer referenced. Cleanup time!
294
+ @logger.close if Rails.env.production?
295
+ @logger = nil
296
+ end
240
297
 
241
298
  @@god_lock.synchronize {
242
- @@systems.delete(@controller.name.to_sym)
243
299
  @@controllers.delete(@controller.id)
300
+ begin
301
+ @controller.reload(:lock => true)
302
+ @controller.active = false
303
+ ensure
304
+ @controller.save
305
+ end
244
306
  }
245
-
246
- begin
247
- @controller.destroy!
248
- rescue
249
- # Controller may already be deleted
250
- end
251
- @modules = nil
252
307
  }
253
308
  end
254
309
 
@@ -267,43 +322,6 @@ module AutomateEm
267
322
 
268
323
 
269
324
  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
325
 
308
326
 
309
327
  def load_hooks(device, mod)
@@ -315,27 +333,29 @@ module AutomateEm
315
333
  # The first module of a type has two names (display and display_1 for example)
316
334
  # Load order is controlled by the control_system model based on the ordinal
317
335
  #
318
- if not @modules[module_name.to_sym].nil?
319
- while @modules["#{module_name}_#{count}".to_sym].present?
320
- count += 1
336
+ @sys_lock.synchronize {
337
+ if not @modules[module_name.to_sym].nil?
338
+ while @modules["#{module_name}_#{count}".to_sym].present?
339
+ count += 1
340
+ end
341
+ module_name = "#{module_name}_#{count}"
342
+ else
343
+ @modules["#{module_name}_1".to_sym] = mod
321
344
  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
345
+ @modules[module_name.to_sym] = mod
346
+
347
+ #
348
+ # Allow for system specific custom names
349
+ #
350
+ if !device.custom_name.nil?
351
+ @modules[device.custom_name.to_sym] = mod
352
+ end
353
+ }
334
354
  end
335
355
 
336
356
 
337
357
  def initialize(controller, log_level)
338
-
358
+ System.logger.info "starting #{controller.name}"
339
359
 
340
360
  @modules = {} # controller modules :name => module instance (device or logic)
341
361
  @communicator = AutomateEm::Communicator.new(self)
@@ -344,12 +364,13 @@ module AutomateEm
344
364
  @sys_lock = Mutex.new
345
365
 
346
366
 
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
367
+ if Rails.env.production?
368
+ @logger = Logger.new(Rails.root.join("log/system_#{@controller.id}.log").to_s, 10, 4194304)
369
+ else
370
+ @logger = Logger.new(STDOUT)
371
+ end
372
+ @logger.formatter = proc { |severity, datetime, progname, msg|
373
+ "#{datetime.strftime("%d/%m/%Y @ %I:%M%p")} #{severity}: #{@controller.name} - #{msg}\n"
353
374
  }
354
375
  end
355
376
  end
@@ -1,85 +1,4 @@
1
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
2
  class Device
84
3
  include ModuleCore
85
4
 
@@ -93,7 +12,7 @@ module AutomateEm
93
12
  @secure_connection = tls
94
13
  @makebreak_connection = makebreak
95
14
  @status = {}
96
- @status_lock = Mutex.new
15
+ @status_lock = Object.new.extend(MonitorMixin)
97
16
  @system_lock = Mutex.new
98
17
  @status_waiting = false
99
18
  end
@@ -28,7 +28,10 @@ module AutomateEm
28
28
  :max_buffer => 524288, # 512kb
29
29
  :clear_queue_on_disconnect => false,
30
30
  :flush_buffer_on_disconnect => false,
31
- :priority_bonus => 20
31
+ :priority_bonus => 20,
32
+ :inactivity_timeout => 0 # part of make and break options
33
+ # :response_length # an alternative to response_delimiter (lower priority)
34
+ # :response_delimiter # here instead of a function call
32
35
  }
33
36
 
34
37
 
@@ -251,10 +254,10 @@ module AutomateEm
251
254
  def do_receive_data(data)
252
255
  @last_recieve_at = Time.now.to_f
253
256
 
254
- if @parent.respond_to?(:response_delimiter)
255
- begin
257
+ begin
258
+ if @config[:response_delimiter].present?
256
259
  if @buf.nil?
257
- del = @parent.response_delimiter
260
+ del = @config[:response_delimiter]
258
261
  if del.class == Array
259
262
  del = array_to_str(del)
260
263
  elsif del.class == Fixnum
@@ -263,17 +266,26 @@ module AutomateEm
263
266
  @buf = BufferedTokenizer.new(del, @config[:max_buffer]) # Call back for character
264
267
  end
265
268
  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
- })
269
+ elsif @config[:response_length].present?
270
+ (@buf ||= "") << data
271
+ data = @buf.scan(/.{1,#{@config[:response_length]}}/)
272
+ if data[-1].length == @config[:response_length]
273
+ @buf = nil
274
+ else
275
+ @buf = data[-1]
276
+ data = data[0..-2]
273
277
  end
278
+ else
274
279
  data = [data]
275
280
  end
276
- else
281
+ rescue => e
282
+ @buf = nil # clear the buffer
283
+ EM.defer do # Error in a thread
284
+ AutomateEm.print_error(logger, e, {
285
+ :message => "module #{@parent.class} error whilst setting delimiter",
286
+ :level => Logger::ERROR
287
+ })
288
+ end
277
289
  data = [data]
278
290
  end
279
291
 
@@ -297,7 +309,7 @@ module AutomateEm
297
309
 
298
310
 
299
311
  #
300
- # Caled from recieve
312
+ # Called from receive
301
313
  #
302
314
  def process_response(response, command)
303
315
  EM.defer do
@@ -438,7 +450,7 @@ module AutomateEm
438
450
  end
439
451
 
440
452
  def process_response_complete
441
- if (@make_break && @send_queue.empty?) || @command[:force_disconnect]
453
+ if (@make_break && @config[:inactivity_timeout] == 0 && @send_queue.empty?) || @command[:force_disconnect]
442
454
  if @connected
443
455
  close_connection_after_writing
444
456
  @disconnecting = true
@@ -483,7 +495,7 @@ module AutomateEm
483
495
  #
484
496
  # Make sure we are sending appropriately formatted data
485
497
  #
486
- if data.class == Array
498
+ if data.is_a?(Array)
487
499
  data = array_to_str(data)
488
500
  elsif options[:hex_string] == true
489
501
  data = hex_to_byte(data)
@@ -591,42 +603,45 @@ module AutomateEm
591
603
  #
592
604
  # Connection state
593
605
  #
594
- def call_connected(*args) # Called from a deferred thread
606
+ def call_connected(*args)
595
607
  #
596
608
  # NOTE:: Same as add parent in device module!!!
597
609
  # TODO:: Should break into a module and include it
598
610
  #
611
+ set_comm_inactivity_timeout(@config[:inactivity_timeout])
599
612
  @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
613
+ EM.defer do
614
+ @parent[:connected] = true
615
+
616
+ begin
617
+ @send_monitor.mon_synchronize { # Any sends in here are high priority (no emits as this function must return)
618
+ @parent.connected(*args) if @parent.respond_to?(:connected)
619
+ }
620
+ rescue => e
616
621
  #
617
- # First connect if no commands pushed then we disconnect asap
622
+ # save from bad user code (don't want to deplete thread pool)
618
623
  #
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."
624
+ AutomateEm.print_error(logger, e, {
625
+ :message => "module #{@parent.class} error whilst calling: connect",
626
+ :level => Logger::ERROR
627
+ })
628
+ ensure
629
+ EM.schedule do
630
+ #
631
+ # First connect if no commands pushed then we disconnect asap
632
+ #
633
+ if @make_break && @first_connect && @send_queue.size == 0
634
+ close_connection_after_writing
635
+ @disconnecting = true
636
+ @com_paused = true
637
+ @first_connect = false
638
+ elsif @com_paused
639
+ @com_paused = false
640
+ @wait_queue.push(nil)
641
+ else
642
+ EM.defer do
643
+ logger.info "Reconnected, communications not paused."
644
+ end
630
645
  end
631
646
  end
632
647
  end
@@ -51,9 +51,7 @@ module AutomateEm
51
51
  if !@tls_enabled
52
52
  @connected = true
53
53
  @connecting = false
54
- EM.defer do
55
- call_connected
56
- end
54
+ call_connected
57
55
  else
58
56
  if !@parent.respond_to?(:certificates)
59
57
  start_tls
@@ -78,9 +76,7 @@ module AutomateEm
78
76
  def ssl_handshake_completed
79
77
  @connected = true
80
78
  @connecting = false
81
- EM.defer do
82
- call_connected(get_peer_cert) # this will mark the true connection complete stage for encrypted devices
83
- end
79
+ call_connected(get_peer_cert) # this will mark the true connection complete stage for encrypted devices
84
80
  end
85
81
 
86
82
 
@@ -16,7 +16,7 @@ module AutomateEm
16
16
  # NOTE:: if changed then change in device.rb
17
17
  #
18
18
  @status = {}
19
- @status_lock = Mutex.new
19
+ @status_lock = Object.new.extend(MonitorMixin)
20
20
  @status_emit = {} # status => condition_variable
21
21
  end
22
22
 
@@ -0,0 +1,83 @@
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
+ # => Get all zones that the pod is in with the setting set and select the first setting
56
+ # => TODO:: Replace this with NoSQL
57
+ #
58
+ def setting(name)
59
+ val = config.settings.where("name = ?", name.to_s).first
60
+ if val.nil?
61
+ val = config.control_system.zones.joins(:settings).where('settings.name = ?', name.to_s).first
62
+ val = val.settings.where("name = ?", name.to_s).first unless val.nil?
63
+
64
+ val = config.dependency.settings.where("name = ?", name.to_s).first if val.nil?
65
+ end
66
+
67
+ if val.present?
68
+ case val.value_type
69
+ when 0
70
+ return val.text_value
71
+ when 1
72
+ return val.integer_value
73
+ when 2
74
+ return val.float_value
75
+ when 3
76
+ return val.datetime_value
77
+ end
78
+ end
79
+
80
+ return nil
81
+ end
82
+ end
83
+ end
@@ -11,7 +11,7 @@ module AutomateEm
11
11
  # NOTE:: if changed then change in logic.rb
12
12
  #
13
13
  @status = {}
14
- @status_lock = Mutex.new
14
+ @status_lock = Object.new.extend(MonitorMixin)
15
15
  @system_lock = Mutex.new
16
16
  @status_waiting = false
17
17
  end
@@ -6,35 +6,48 @@ module AutomateEm
6
6
 
7
7
  def [] (status)
8
8
  status = status.to_sym if status.class == String
9
- @status_lock.synchronize {
9
+ @status_lock.mon_synchronize {
10
10
  return @status[status]
11
11
  }
12
12
  end
13
13
 
14
14
  def []= (status, data)
15
15
  status = status.to_sym if status.class == String
16
- old_data = check_for_emit(status, data)
17
16
 
18
- if data != old_data
19
- changed # so that the notify is applied
20
- logger.debug "#{self.class} status updated: #{status} = #{data}"
21
- end
22
-
23
-
24
- notify_observers(self, status, data) # only notify changes
17
+ @status_lock.mon_synchronize {
18
+ old_data = check_for_emit(status, data)
19
+
20
+ if data != old_data
21
+ changed # so that the notify is applied
22
+ logger.debug "#{self.class} status updated: #{status} = #{data}"
23
+ end
24
+
25
+ notify_observers(self, status, data) # only notify changes
26
+ }
27
+ end
28
+
29
+ def update_status(status, &block)
30
+ status = status.to_sym if status.class == String
31
+ @status_lock.mon_synchronize {
32
+ data = @status[status]
33
+ data = data.clone unless data.nil?
34
+ newValue = block.call(data)
35
+ self[status] = newValue
36
+ return @status[status]
37
+ }
25
38
  end
26
39
 
27
40
  attr_reader :status # Should not be accessed like this for modification
28
41
 
29
42
 
30
43
  def mark_emit_start(status)
31
- @status_lock.synchronize {
44
+ @status_lock.mon_synchronize {
32
45
  @emit_hasnt_occured = status
33
46
  }
34
47
  end
35
48
 
36
49
  def mark_emit_end
37
- @status_lock.synchronize {
50
+ @status_lock.mon_synchronize {
38
51
  @emit_hasnt_occured.each_pair do | key, block |
39
52
  data = @status[key]
40
53
  task do
@@ -56,7 +69,7 @@ module AutomateEm
56
69
 
57
70
 
58
71
  def check_for_emit(status, data)
59
- @status_lock.synchronize {
72
+ @status_lock.mon_synchronize {
60
73
  old_data = @status[status]
61
74
  @status[status] = data
62
75
 
@@ -1,3 +1,3 @@
1
1
  module AutomateEm
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/automate-em.rb CHANGED
@@ -32,6 +32,7 @@ require 'ipaddress'
32
32
  require 'automate-em/constants.rb'
33
33
  require 'automate-em/utilities.rb'
34
34
  require 'automate-em/status.rb'
35
+ require 'automate-em/module_core.rb'
35
36
 
36
37
  require 'automate-em/core/resolver_pool.rb'
37
38
  require 'automate-em/core/modules.rb'
@@ -122,17 +123,46 @@ module AutomateEm
122
123
  #
123
124
  # Load the system based on the database
124
125
  #
125
- ControlSystem.all.each do |controller|
126
+ ControlSystem.update_all(:active => false)
127
+ ControlSystem.find_each do |controller|
126
128
  EM.defer do
127
129
  begin
128
130
  System.logger.debug "Booting #{controller.name}"
129
- System.new_system(controller, Rails.configuration.automate.log_level)
131
+ result = System.start(controller, Rails.configuration.automate.log_level)
132
+ if result == false
133
+ #
134
+ # TODO:: we need a class for handling failed starts
135
+ #
136
+ AutomateEm.print_error(AutomateEm::System.logger, e, {
137
+ :message => "System #{controller.name} failed to start (gracefully). It is now offline",
138
+ :level => Logger::WARN
139
+ })
140
+ controller.active = false
141
+ controller.save
142
+ @@scheduler.in '5m' do
143
+ System.start(controller, Rails.configuration.automate.log_level)
144
+ end
145
+ end
130
146
  rescue => e
131
147
  AutomateEm.print_error(AutomateEm::System.logger, e, {
132
- :message => "Error during boot",
133
- :level => Logger::FATAL
148
+ :message => "System #{controller.name} threw an error whilst starting. It is now offline",
149
+ :level => Logger::WARN
134
150
  })
135
- EventMachine::stop_event_loop
151
+ #
152
+ # Mark as offline, do not retry and email
153
+ #
154
+ begin
155
+ controller.active = false
156
+ controller.save
157
+ #
158
+ # TODO:: email admin about failure
159
+ #
160
+ rescue => e
161
+ AutomateEm.print_error(AutomateEm::System.logger, e, {
162
+ :message => "Error marking system as offline",
163
+ :level => Logger::ERROR
164
+ })
165
+ end
136
166
  end
137
167
  end
138
168
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: automate-em
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-23 00:00:00.000000000 Z
12
+ date: 2012-06-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - ! '>='
36
36
  - !ruby/object:Gem::Version
37
- version: 1.0.0.beta.3
37
+ version: 1.0.0.beta.4.1
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  requirements:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
- version: 1.0.0.beta.3
45
+ version: 1.0.0.beta.4.1
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: em-priority-queue
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -230,6 +230,7 @@ files:
230
230
  - lib/automate-em/interfaces/OLD CODE/telnet/ansi.rb
231
231
  - lib/automate-em/interfaces/OLD CODE/telnet/telnet.rb
232
232
  - lib/automate-em/logic/logic.rb
233
+ - lib/automate-em/module_core.rb
233
234
  - lib/automate-em/service/http_service.rb
234
235
  - lib/automate-em/service/service.rb
235
236
  - lib/automate-em/status.rb