automate-em 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/LGPL3-LICENSE +165 -0
  2. data/README.textile +48 -0
  3. data/Rakefile +40 -0
  4. data/app/models/control_system.rb +20 -0
  5. data/app/models/controller_device.rb +21 -0
  6. data/app/models/controller_http_service.rb +17 -0
  7. data/app/models/controller_logic.rb +5 -0
  8. data/app/models/controller_zone.rb +10 -0
  9. data/app/models/dependency.rb +20 -0
  10. data/app/models/server.rb +12 -0
  11. data/app/models/setting.rb +38 -0
  12. data/app/models/trusted_device.rb +63 -0
  13. data/app/models/user_zone.rb +10 -0
  14. data/app/models/zone.rb +16 -0
  15. data/db/migrate/20111001022500_init.rb +147 -0
  16. data/db/migrate/20111017213801_one_time_key.rb +9 -0
  17. data/db/migrate/20111021071632_encrypt_setting.rb +9 -0
  18. data/db/migrate/20111110075444_servers.rb +15 -0
  19. data/db/migrate/20111114074538_default_port.rb +9 -0
  20. data/db/migrate/20111122073055_makebreak.rb +9 -0
  21. data/db/migrate/20111211062846_create_controller_http_services.rb +18 -0
  22. data/lib/automate-em.rb +155 -0
  23. data/lib/automate-em/constants.rb +6 -0
  24. data/lib/automate-em/core/communicator.rb +318 -0
  25. data/lib/automate-em/core/modules.rb +373 -0
  26. data/lib/automate-em/core/resolver_pool.rb +76 -0
  27. data/lib/automate-em/core/system.rb +356 -0
  28. data/lib/automate-em/device/datagram_server.rb +111 -0
  29. data/lib/automate-em/device/device.rb +140 -0
  30. data/lib/automate-em/device/device_connection.rb +689 -0
  31. data/lib/automate-em/device/tcp_control.rb +210 -0
  32. data/lib/automate-em/engine.rb +36 -0
  33. data/lib/automate-em/interfaces/OLD CODE/deferred.rb +67 -0
  34. data/lib/automate-em/interfaces/OLD CODE/telnet/ansi.rb +137 -0
  35. data/lib/automate-em/interfaces/OLD CODE/telnet/telnet.rb +137 -0
  36. data/lib/automate-em/interfaces/html5.rb +302 -0
  37. data/lib/automate-em/logic/logic.rb +76 -0
  38. data/lib/automate-em/service/http_service.rb +584 -0
  39. data/lib/automate-em/service/service.rb +48 -0
  40. data/lib/automate-em/status.rb +89 -0
  41. data/lib/automate-em/utilities.rb +195 -0
  42. data/lib/automate-em/version.rb +3 -0
  43. data/lib/generators/module/USAGE +8 -0
  44. data/lib/generators/module/module_generator.rb +47 -0
  45. data/lib/tasks/automate-em_tasks.rake +5 -0
  46. data/test/automate-em_test.rb +7 -0
  47. data/test/dummy/README.rdoc +261 -0
  48. data/test/dummy/Rakefile +7 -0
  49. data/test/dummy/app/assets/javascripts/application.js +15 -0
  50. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  51. data/test/dummy/app/controllers/application_controller.rb +3 -0
  52. data/test/dummy/app/helpers/application_helper.rb +2 -0
  53. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/test/dummy/config.ru +4 -0
  55. data/test/dummy/config/application.rb +56 -0
  56. data/test/dummy/config/boot.rb +10 -0
  57. data/test/dummy/config/database.yml +25 -0
  58. data/test/dummy/config/environment.rb +5 -0
  59. data/test/dummy/config/environments/development.rb +37 -0
  60. data/test/dummy/config/environments/production.rb +67 -0
  61. data/test/dummy/config/environments/test.rb +37 -0
  62. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  63. data/test/dummy/config/initializers/inflections.rb +15 -0
  64. data/test/dummy/config/initializers/mime_types.rb +5 -0
  65. data/test/dummy/config/initializers/secret_token.rb +7 -0
  66. data/test/dummy/config/initializers/session_store.rb +8 -0
  67. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  68. data/test/dummy/config/locales/en.yml +5 -0
  69. data/test/dummy/config/routes.rb +4 -0
  70. data/test/dummy/public/404.html +26 -0
  71. data/test/dummy/public/422.html +26 -0
  72. data/test/dummy/public/500.html +25 -0
  73. data/test/dummy/public/favicon.ico +0 -0
  74. data/test/dummy/script/rails +6 -0
  75. data/test/integration/navigation_test.rb +10 -0
  76. data/test/test_helper.rb +15 -0
  77. metadata +328 -0
@@ -0,0 +1,137 @@
1
+ #require 'rubygems'
2
+ #require 'eventmachine'
3
+
4
+
5
+ require "#{File.dirname(__FILE__)}/ansi.rb"
6
+ require 'json'
7
+
8
+ module Control
9
+ class TelnetServer < Deferred
10
+
11
+ def initialize(*args)
12
+ super
13
+
14
+ @input_lock = Mutex.new
15
+ end
16
+
17
+ def self.start
18
+ EventMachine::start_server "127.0.0.1", 23, TelnetServer
19
+ System.logger.info 'running telnet server on 23'
20
+ end
21
+
22
+ def received
23
+ @input_lock.synchronize {
24
+ data = @receive_queue.pop(true)
25
+ if data == "\b"
26
+ if @input.length > 0
27
+ send_data " \b"
28
+ @input.chop!
29
+ else
30
+ send_data " "
31
+ end
32
+ elsif data =~ /.*\r\n$/
33
+
34
+ #
35
+ # TODO:: For linux we need to chop! twice here
36
+ # Windows @input is already complete
37
+ #
38
+
39
+ if @input =~ /^(quit|exit)$/i
40
+ disconnect
41
+ return
42
+ end
43
+
44
+ if @input != "" && !@input.nil?
45
+ if @selected.nil?
46
+ if @input =~ /^\d+$/
47
+ @selected = Communicator.select(self, @input.to_i)
48
+ else
49
+ @selected = Communicator.select(self, @input)
50
+ end
51
+ send_line " system #{@input} selected...", :green
52
+ else
53
+ thecommand = @input.split(/\s|\./, 3)
54
+ @input = ""
55
+ on_fail = lambda {
56
+ send_line(" invalid command", :green)
57
+ send_prompt("> ", :green)
58
+ send_prompt(@input)
59
+ }
60
+ if thecommand[0] =~ /register/i
61
+ @selected.register(self, thecommand[1], thecommand[2], &on_fail)
62
+ elsif thecommand[0] =~ /unregister/i
63
+ @selected.unregister(self, thecommand[1], thecommand[2], &on_fail)
64
+ elsif thecommand[2].nil?
65
+ @selected.send_command(thecommand[0], thecommand[1], &on_fail)
66
+ elsif ['{','['].include?(thecommand[2][0])
67
+ @selected.send_command(thecommand[0], thecommand[1], JSON.parse(thecommand[2], {:symbolize_names => true}), &on_fail)
68
+ else
69
+ @selected.send_command(thecommand[0], thecommand[1], thecommand[2], &on_fail)
70
+ end
71
+ send_line " sent...", :green
72
+ end
73
+ end
74
+
75
+ send_prompt("> ", :green)
76
+ @input = ""
77
+ else #if data =~ /^[a-zA-Z0-9\., _-]*$/
78
+ @input << data
79
+ end
80
+ }
81
+ end
82
+
83
+ def notify(mod_sym, stat_sym, data)
84
+ send_line("\r\nStatus: #{mod_sym}:#{stat_sym}==#{data}", :green)
85
+ send_prompt("> ", :green)
86
+ @input_lock.synchronize {
87
+ send(@input) if !@input.empty?
88
+ }
89
+ end
90
+
91
+ protected
92
+
93
+ def initiate_session
94
+ @input_lock.synchronize {
95
+ @input = ""
96
+ }
97
+ send_line("Please select from the following systems:", :green)
98
+ send_line("-----------------------------------------", :green)
99
+ system = Communicator.system_list
100
+ system.each_index { |x| send_line(" #{x}: #{system[x]}", :green) }
101
+ send_prompt("> ", :green)
102
+ end
103
+
104
+ def disconnect
105
+ send_line("Goodbye.", :green)
106
+ close_connection_after_writing
107
+ end
108
+
109
+ def send_line(data, color=nil)
110
+ send(data, color, true)
111
+ end
112
+
113
+ def send_prompt(data, color=nil)
114
+ send(data, color, false)
115
+ end
116
+
117
+ def send(data, color=nil, newline=false)
118
+ if newline
119
+ data = data + "\r\n"
120
+ end
121
+
122
+ case color
123
+ when :red
124
+ send_data(ANSI.red(data))
125
+ when :green
126
+ send_data(ANSI.green(data))
127
+ else
128
+ send_data(data)
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ #EventMachine::run {
135
+ # EventMachine::start_server "127.0.0.1", 8080, TelnetServer
136
+ # puts 'running echo server on 8080'
137
+ #}
@@ -0,0 +1,302 @@
1
+ require 'em-websocket'
2
+ require 'json'
3
+
4
+
5
+ #
6
+ # This system was designed based on the following articles
7
+ # https://gist.github.com/299789
8
+ # http://blog.new-bamboo.co.uk/2010/2/10/json-event-based-convention-websockets
9
+ #
10
+
11
+
12
+ class HTML5Monitor
13
+ @@clients = {}
14
+ @@special_events = {
15
+ "system" => :system,
16
+ "authenticate" => :authenticate,
17
+ "ls" => :ls,
18
+ "ping" => :ping
19
+ }
20
+ @@special_commands = {
21
+ "register" => :register,
22
+ "unregister" => :unregister
23
+ }
24
+
25
+ def self.register(id)
26
+ @@clients[id] = HTML5Monitor.new(id)
27
+ end
28
+
29
+ def self.unregister(id)
30
+ client = @@clients.delete(id)
31
+
32
+ EM.defer do
33
+ begin
34
+ client.disconnected
35
+ AutomateEm::System.logger.debug "There are now #{HTML5Monitor.count} HTML5 clients connected"
36
+ rescue => e
37
+ AutomateEm.print_error(AutomateEm::System.logger, e, {
38
+ :message => "in html5.rb, onclose : unregistering client did not exist (we may have been shutting down)",
39
+ :level => Logger::ERROR
40
+ })
41
+ end
42
+ end
43
+ end
44
+
45
+ def self.count
46
+ return @@clients.length
47
+ end
48
+
49
+ def self.receive(id, data)
50
+ client = @@clients[id]
51
+
52
+ EM.defer do
53
+ begin
54
+ client.receive(data)
55
+ rescue => e
56
+ AutomateEm.print_error(AutomateEm::System.logger, e, {
57
+ :message => "in html5.rb, onmessage : client did not exist (we may have been shutting down)",
58
+ :level => Logger::ERROR
59
+ })
60
+ ensure
61
+ ActiveRecord::Base.clear_active_connections! # Clear any unused connections
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+ def initialize(socket)
68
+ @data_lock = Mutex.new
69
+
70
+ #
71
+ # Must authenticate before any system details will be sent
72
+ #
73
+ @socket = socket
74
+ @system = nil
75
+ @user = nil
76
+
77
+
78
+ @socket.send(JSON.generate({:event => "authenticate", :data => []}))
79
+
80
+ #
81
+ # TODO:: start a schedule here that sends a ping to the browser every so often
82
+ #
83
+ end
84
+
85
+
86
+ #
87
+ #
88
+ # Instance methods:
89
+ #
90
+ #
91
+ def try_auth(data = nil)
92
+
93
+ return false if @ignoreAuth
94
+
95
+ if !!@user
96
+ if data.nil?
97
+ return true
98
+ else
99
+ @user = nil
100
+ return try_auth(data)
101
+ end
102
+ else
103
+ if !data.nil? && data.class == Array
104
+ if data.length == 1 # one time key
105
+ @user = TrustedDevice.try_to_login(data[0])
106
+ elsif data.length == 3
107
+ #user, password, auth_source
108
+ source = AuthSource.where("name = ?", data[2]).first
109
+ @user = User.try_to_login(data[0], data[1], source)
110
+ end
111
+
112
+ return try_auth # no data
113
+ end
114
+
115
+ #
116
+ # Prevent DOS/brute force Attacks
117
+ #
118
+ @ignoreAuth = true
119
+ EM.add_timer(2) do
120
+ begin
121
+ @socket.send(JSON.generate({:event => "authenticate", :data => []}))
122
+ ensure
123
+ @ignoreAuth = false
124
+ end
125
+ end
126
+ end
127
+ return false
128
+ end
129
+
130
+ def send_system
131
+ return if @ignoreSys
132
+ @ignoreSys = true
133
+
134
+ EM.add_timer(2) do
135
+ begin
136
+ @socket.send(JSON.generate({:event => "system", :data => []}))
137
+ ensure
138
+ @ignoreSys = false
139
+ end
140
+ end
141
+ end
142
+
143
+ def disconnected
144
+ @data_lock.synchronize {
145
+ @system.disconnected(self) if (!!@system) # System could be nil or false
146
+ }
147
+ end
148
+
149
+ def receive(data)
150
+ data = JSON.parse(data, {:symbolize_names => true})
151
+ return unless data[:command].class == String
152
+ data[:data] = [] unless data[:data].class == Array
153
+
154
+ @data_lock.synchronize {
155
+ #
156
+ # Ensure authenticated
157
+ #
158
+ if data[:command] == "authenticate"
159
+ return unless try_auth(data[:data])
160
+ send_system
161
+ return
162
+ else
163
+ return unless (try_auth || data[:command] == "ping")
164
+ end
165
+
166
+ #
167
+ # Ensure system is selected
168
+ # If a command is sent out of order
169
+ #
170
+ if @system.nil? && !@@special_events.has_key?(data[:command])
171
+ send_system
172
+ return
173
+ end
174
+
175
+ if @@special_events.has_key?(data[:command]) # system, auth, ls
176
+ case @@special_events[data[:command]]
177
+ when :system
178
+ @system.disconnected(self) unless @system.nil?
179
+ @system = nil
180
+ @system = AutomateEm::Communicator.select(@user, self, data[:data][0]) unless data[:data].empty?
181
+ if @system.nil?
182
+ send_system
183
+ elsif @system == false # System offline
184
+ EM.schedule do
185
+ @socket.send(JSON.generate({:event => "offline", :data => []}))
186
+ shutdown
187
+ end
188
+ else
189
+ EM.schedule do
190
+ @socket.send(JSON.generate({:event => "ready", :data => []}))
191
+ end
192
+ end
193
+ when :ping
194
+ EM.schedule do
195
+ @socket.send(JSON.generate({:event => "pong", :data => []}))
196
+ end
197
+ when :ls
198
+ systems = AutomateEm::Communicator.system_list(@user)
199
+ EM.schedule do
200
+ @socket.send(JSON.generate({:event => "ls", :data => systems}))
201
+ end
202
+ end
203
+ elsif @@special_commands.has_key?(data[:command]) # reg, unreg
204
+ array = data[:data]
205
+ array.insert(0, self)
206
+ @system.public_send(data[:command], *array)
207
+ else # All other commands
208
+ command = data[:command].split('.')
209
+ if command.length == 2
210
+ @system.send_command(command[0], command[1], *data[:data])
211
+ else
212
+ AutomateEm::System.logger.info "-- in html5.rb, recieve : invalid command received - #{data[:command]} --"
213
+ end
214
+ end
215
+ }
216
+ rescue => e
217
+ logger = nil
218
+ @data_lock.synchronize {
219
+ logger = @system.nil? ? AutomateEm::System.logger : @system.logger
220
+ }
221
+ AutomateEm.print_error(logger, e, {
222
+ :message => "in html5.rb, recieve : probably malformed JSON data",
223
+ :level => Logger::ERROR
224
+ })
225
+ shutdown
226
+ end
227
+
228
+ def shutdown
229
+ EM.schedule do
230
+ @socket.close_websocket
231
+ end
232
+ end
233
+
234
+ def notify(mod_sym, stat_sym, data)
235
+ #
236
+ # This should be re-entrant? So no need to protect
237
+ #
238
+ @system.logger.debug "#{mod_sym}.#{stat_sym} sent #{data.inspect}"
239
+ EM.schedule do
240
+ @socket.send(JSON.generate({"event" => "#{mod_sym}.#{stat_sym}", "data" => data}))
241
+ end
242
+ end
243
+ end
244
+
245
+
246
+ module AutomateEm
247
+ class System
248
+ @@socket_server = nil
249
+ def self.start_websockets
250
+ EM.schedule do
251
+ if @@socket_server.nil?
252
+ @@socket_server = EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 81) do |socket| # , :debug => true
253
+ socket.onopen {
254
+ #
255
+ # This socket represents a connected device
256
+ #
257
+ HTML5Monitor.register(socket)
258
+ }
259
+
260
+ socket.onmessage { |data|
261
+ #
262
+ # Attach socket here to system
263
+ # then process commands
264
+ #
265
+ HTML5Monitor.receive(socket, data)
266
+ }
267
+
268
+ socket.onclose {
269
+ HTML5Monitor.unregister(socket)
270
+ }
271
+
272
+ socket.onerror { |error|
273
+ if !error.kind_of?(EM::WebSocket::WebSocketError)
274
+ EM.defer do
275
+ AutomateEm.print_error(AutomateEm::System.logger, error, {
276
+ :message => "in html5.rb, onerror : issue with websocket data",
277
+ :level => Logger::ERROR
278
+ })
279
+ end
280
+ else
281
+ EM.defer do
282
+ AutomateEm::System.logger.info "in html5.rb, onerror : invalid handshake received - #{error.inspect}"
283
+ end
284
+ end
285
+ }
286
+ end
287
+
288
+ end
289
+ end
290
+ EM.defer do
291
+ AutomateEm::System.logger.info 'running HTML5 socket server on port 81'
292
+ end
293
+ end
294
+
295
+ def self.stop_websockets
296
+ EM.schedule do
297
+ EventMachine::stop_server(@@socket_server) unless @@socket_server.nil?
298
+ @@socket_server = nil
299
+ end
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,76 @@
1
+ module AutomateEm
2
+
3
+ #
4
+ # Base class for all control logic classes
5
+ #
6
+ class Logic
7
+ include Status
8
+ include Constants
9
+ include Utilities
10
+
11
+ def initialize(system)
12
+ @system = system
13
+
14
+ #
15
+ # Status variables
16
+ # NOTE:: if changed then change in device.rb
17
+ #
18
+ @status = {}
19
+ @status_lock = Mutex.new
20
+ @status_emit = {} # status => condition_variable
21
+ end
22
+
23
+
24
+ def logger
25
+ @system.logger
26
+ end
27
+
28
+
29
+ def clear_active_timers
30
+ @schedule.clear_jobs unless @schedule.nil?
31
+ end
32
+
33
+
34
+ protected
35
+
36
+
37
+ def setting(name)
38
+ val = LogicModule.lookup(self).settings.where("name = ?", name).first
39
+ if val.nil?
40
+ val = LogicModule.lookup(self).control_system.zones.joins(:settings).where('settings.name = ?', name.to_s).first
41
+ val = val.settings.where("name = ?", name.to_s).first unless val.nil?
42
+
43
+ val = LogicModule.lookup(self).dependency.settings.where("name = ?", name).first if val.nil?
44
+ end
45
+
46
+ if val.present?
47
+ case val.value_type
48
+ when 0
49
+ return val.text_value
50
+ when 1
51
+ return val.integer_value
52
+ when 2
53
+ return val.float_value
54
+ when 3
55
+ return val.datetime_value
56
+ end
57
+ end
58
+
59
+ return nil
60
+ end
61
+
62
+
63
+ attr_reader :system
64
+
65
+
66
+ def register(mod, status, &block)
67
+ @system.communicator.register(self, mod, status, &block)
68
+ end
69
+
70
+ def unregister(mod, status, &block)
71
+ @system.communicator.unregister(self, mod, status, &block)
72
+ end
73
+
74
+
75
+ end
76
+ end