automate-em 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. data/LGPL3-LICENSE +165 -0
  2. data/README.textile +48 -0
  3. data/Rakefile +40 -0
  4. data/app/models/control_system.rb +20 -0
  5. data/app/models/controller_device.rb +21 -0
  6. data/app/models/controller_http_service.rb +17 -0
  7. data/app/models/controller_logic.rb +5 -0
  8. data/app/models/controller_zone.rb +10 -0
  9. data/app/models/dependency.rb +20 -0
  10. data/app/models/server.rb +12 -0
  11. data/app/models/setting.rb +38 -0
  12. data/app/models/trusted_device.rb +63 -0
  13. data/app/models/user_zone.rb +10 -0
  14. data/app/models/zone.rb +16 -0
  15. data/db/migrate/20111001022500_init.rb +147 -0
  16. data/db/migrate/20111017213801_one_time_key.rb +9 -0
  17. data/db/migrate/20111021071632_encrypt_setting.rb +9 -0
  18. data/db/migrate/20111110075444_servers.rb +15 -0
  19. data/db/migrate/20111114074538_default_port.rb +9 -0
  20. data/db/migrate/20111122073055_makebreak.rb +9 -0
  21. data/db/migrate/20111211062846_create_controller_http_services.rb +18 -0
  22. data/lib/automate-em.rb +155 -0
  23. data/lib/automate-em/constants.rb +6 -0
  24. data/lib/automate-em/core/communicator.rb +318 -0
  25. data/lib/automate-em/core/modules.rb +373 -0
  26. data/lib/automate-em/core/resolver_pool.rb +76 -0
  27. data/lib/automate-em/core/system.rb +356 -0
  28. data/lib/automate-em/device/datagram_server.rb +111 -0
  29. data/lib/automate-em/device/device.rb +140 -0
  30. data/lib/automate-em/device/device_connection.rb +689 -0
  31. data/lib/automate-em/device/tcp_control.rb +210 -0
  32. data/lib/automate-em/engine.rb +36 -0
  33. data/lib/automate-em/interfaces/OLD CODE/deferred.rb +67 -0
  34. data/lib/automate-em/interfaces/OLD CODE/telnet/ansi.rb +137 -0
  35. data/lib/automate-em/interfaces/OLD CODE/telnet/telnet.rb +137 -0
  36. data/lib/automate-em/interfaces/html5.rb +302 -0
  37. data/lib/automate-em/logic/logic.rb +76 -0
  38. data/lib/automate-em/service/http_service.rb +584 -0
  39. data/lib/automate-em/service/service.rb +48 -0
  40. data/lib/automate-em/status.rb +89 -0
  41. data/lib/automate-em/utilities.rb +195 -0
  42. data/lib/automate-em/version.rb +3 -0
  43. data/lib/generators/module/USAGE +8 -0
  44. data/lib/generators/module/module_generator.rb +47 -0
  45. data/lib/tasks/automate-em_tasks.rake +5 -0
  46. data/test/automate-em_test.rb +7 -0
  47. data/test/dummy/README.rdoc +261 -0
  48. data/test/dummy/Rakefile +7 -0
  49. data/test/dummy/app/assets/javascripts/application.js +15 -0
  50. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  51. data/test/dummy/app/controllers/application_controller.rb +3 -0
  52. data/test/dummy/app/helpers/application_helper.rb +2 -0
  53. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/test/dummy/config.ru +4 -0
  55. data/test/dummy/config/application.rb +56 -0
  56. data/test/dummy/config/boot.rb +10 -0
  57. data/test/dummy/config/database.yml +25 -0
  58. data/test/dummy/config/environment.rb +5 -0
  59. data/test/dummy/config/environments/development.rb +37 -0
  60. data/test/dummy/config/environments/production.rb +67 -0
  61. data/test/dummy/config/environments/test.rb +37 -0
  62. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  63. data/test/dummy/config/initializers/inflections.rb +15 -0
  64. data/test/dummy/config/initializers/mime_types.rb +5 -0
  65. data/test/dummy/config/initializers/secret_token.rb +7 -0
  66. data/test/dummy/config/initializers/session_store.rb +8 -0
  67. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  68. data/test/dummy/config/locales/en.yml +5 -0
  69. data/test/dummy/config/routes.rb +4 -0
  70. data/test/dummy/public/404.html +26 -0
  71. data/test/dummy/public/422.html +26 -0
  72. data/test/dummy/public/500.html +25 -0
  73. data/test/dummy/public/favicon.ico +0 -0
  74. data/test/dummy/script/rails +6 -0
  75. data/test/integration/navigation_test.rb +10 -0
  76. data/test/test_helper.rb +15 -0
  77. metadata +328 -0
@@ -0,0 +1,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