automate-em 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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