rfid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/INSTALL.rdoc +55 -0
  2. data/README.rdoc +60 -0
  3. data/bin/rfid +18 -0
  4. data/bin/rfid_checkout +22 -0
  5. data/config.ru +9 -0
  6. data/config/boot.rb +6 -0
  7. data/config/environment.rb +8 -0
  8. data/config/rfid.yml +6 -0
  9. data/config/rfid.yml.sample +6 -0
  10. data/config/thin.yml +14 -0
  11. data/config/thin.yml.sample +14 -0
  12. data/lib/rfid.rb +75 -0
  13. data/lib/rfid/application.rb +115 -0
  14. data/lib/rfid/application/public/favicon.ico +0 -0
  15. data/lib/rfid/application/public/javascripts/application.js +12 -0
  16. data/lib/rfid/application/public/javascripts/debug.js +59 -0
  17. data/lib/rfid/application/public/stylesheets/application.css +49 -0
  18. data/lib/rfid/application/public/stylesheets/bootstrap.css +2467 -0
  19. data/lib/rfid/application/views/events.erb +34 -0
  20. data/lib/rfid/application/views/index.erb +21 -0
  21. data/lib/rfid/application/views/key_sets.erb +13 -0
  22. data/lib/rfid/application/views/key_string.erb +11 -0
  23. data/lib/rfid/application/views/keys.erb +20 -0
  24. data/lib/rfid/application/views/layout.erb +56 -0
  25. data/lib/rfid/application/views/redis.erb +16 -0
  26. data/lib/rfid/application/views/usage.erb +47 -0
  27. data/lib/rfid/cli.rb +78 -0
  28. data/lib/rfid/config.rb +23 -0
  29. data/lib/rfid/core_ext.rb +3 -0
  30. data/lib/rfid/core_ext/json_engine.rb +2 -0
  31. data/lib/rfid/event.rb +101 -0
  32. data/lib/rfid/translator.rb +68 -0
  33. data/lib/rfid/version.rb +3 -0
  34. data/lib/rfid/websocket.rb +35 -0
  35. data/lib/runner.rb +18 -0
  36. data/lib/server.rb +19 -0
  37. data/lib/templates/init +109 -0
  38. data/spec/models/application_spec.rb +54 -0
  39. data/spec/models/config_spec.rb +33 -0
  40. data/spec/models/event_spec.rb +22 -0
  41. data/spec/rfid_spec.rb +58 -0
  42. data/spec/spec_helper.rb +25 -0
  43. metadata +228 -0
@@ -0,0 +1,34 @@
1
+ <div class="page-header">
2
+ <h1>Create new event</h1>
3
+ </div>
4
+
5
+ <% if params[:state] == 'success' %>
6
+ <div class="alert-message success">
7
+ <a href="javascript:void(0);" class="close" onclick="$(this).parents('div.alert-message').hide();">×</a>
8
+ <p><strong>Well done!</strong> You successfully create websocket event.</p>
9
+ </div>
10
+ <% end %>
11
+
12
+ <form action="/events" method="post" class="form-stacked">
13
+ <fieldset>
14
+ <div class="clearfix">
15
+ <label for="event_name">Name</label>
16
+ <div class="input">
17
+ <input type="text" size="30" name="event[name]" id="event_name" class="xxlarge">
18
+ </div>
19
+ </div>
20
+
21
+ <div class="clearfix">
22
+ <label for="event_data">Data</label>
23
+ <div class="input">
24
+ <textarea rows="3" name="event[data]" id="event_data" class="xxlarge"></textarea>
25
+ <span class="help-block">
26
+ In JSON format. e.g. "{"name": "Joe", "message_count": 23}".
27
+ </span>
28
+ </div>
29
+ </div>
30
+ <div class="actions">
31
+ <input type="submit" value="Create" class="btn primary">&nbsp;<button class="btn" type="reset">Cancel</button>
32
+ </div>
33
+ </fieldset>
34
+ </form>
@@ -0,0 +1,21 @@
1
+ <script type="text/javascript" charset="utf-8">
2
+ jsload('websocket', "https://rfidapi.aimbulance.com/js/websocket.js");
3
+ jsload('debug', "/javascripts/debug.js");
4
+ </script>
5
+
6
+ <div class="page-header">
7
+ <h1>RFID <small>Debug console</small></h1>
8
+ </div>
9
+
10
+ <table id="debug_console" class="bordered-table zebra-striped">
11
+ <thead>
12
+ <tr>
13
+ <th>Type</th>
14
+ <th>Socket</th>
15
+ <th>Details</th>
16
+ <th>Time</th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ </tbody>
21
+ </table>
@@ -0,0 +1,13 @@
1
+ <div class="page-header">
2
+ <h1>Key "<%= @key %>" is a <%= rfid.redis.type @key %> <small>size: <%= redis_get_size(@key) %></small></h1>
3
+ </div>
4
+
5
+ <table class="bordered-table zebra-striped">
6
+ <% for row in redis_get_value_as_array(@key) %>
7
+ <tr>
8
+ <td>
9
+ <%= row %>
10
+ </td>
11
+ </tr>
12
+ <% end %>
13
+ </table>
@@ -0,0 +1,11 @@
1
+ <div class="page-header">
2
+ <h1>Key "<%= @key %>" is a <%= rfid.redis.type @key %> <small>size: <%= redis_get_size(@key) %></small></h1>
3
+ </div>
4
+
5
+ <table class="bordered-table zebra-striped">
6
+ <tr>
7
+ <td>
8
+ <%= redis_get_value_as_array(@key) %>
9
+ </td>
10
+ </tr>
11
+ </table>
@@ -0,0 +1,20 @@
1
+ <div class="page-header">
2
+ <h1>Keys owned by <%= rfid %> <small>All keys are actually prefixed with "rfid:"</small></h1>
3
+ </div>
4
+
5
+ <table class="bordered-table zebra-striped">
6
+ <tr>
7
+ <th>key</th>
8
+ <th>type</th>
9
+ <th>size</th>
10
+ </tr>
11
+ <% for key in rfid.keys.sort %>
12
+ <tr>
13
+ <th>
14
+ <a href="<%= "/keys/#{key}" %>"><%= key %></a>
15
+ </th>
16
+ <td><%= rfid.redis.type key %></td>
17
+ <td><%= redis_get_size key %></td>
18
+ </tr>
19
+ <% end %>
20
+ </table>
@@ -0,0 +1,56 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>RFID websocket status</title>
6
+ <meta name="description" content="RFID websocket">
7
+ <meta name="author" content="Aimbulance">
8
+
9
+ <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
10
+ <!--[if lt IE 9]>
11
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
12
+ <![endif]-->
13
+
14
+ <!-- Le styles -->
15
+ <link rel="stylesheet" type="text/css" href="/stylesheets/bootstrap.css">
16
+ <link rel="stylesheet" type="text/css" href="/stylesheets/application.css">
17
+ <link rel="shortcut icon" href="/favicon.ico">
18
+
19
+ <script src="https://www.google.com/jsapi" type="text/javascript"></script>
20
+ <script language="Javascript" type="text/javascript">
21
+ //<![CDATA[
22
+ google.load("jquery", "1.7.0");
23
+ //]]>
24
+ </script>
25
+ <script src="/javascripts/application.js" type="text/javascript"></script>
26
+ </head>
27
+ <body>
28
+
29
+ <div class="topbar">
30
+ <div class="fill">
31
+ <div class="container">
32
+ <a class="brand" href="/">RFID WebSockets</a>
33
+
34
+ <ul class="nav">
35
+ <% ["Events", "Redis", "Keys", "Usage"].each do |name| %>
36
+ <%= tab name %>
37
+ <% end %>
38
+ </ul>
39
+
40
+ <ul class="nav secondary-nav">
41
+ <li><a href="javascript:void(0);" onclick="window.location.reload();">Refresh</a></li>
42
+ </ul>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <div class="container">
48
+ <div class="content">
49
+ <%= yield %>
50
+ </div>
51
+ <footer>
52
+ <p>&copy; 2011 Aimbulance. All rights reserved.</p>
53
+ </footer>
54
+ </div>
55
+ </body>
56
+ </html>
@@ -0,0 +1,16 @@
1
+ <div class="page-header">
2
+ <h1><%= rfid.redis_id %> <small>connection</small></h1>
3
+ </div>
4
+
5
+ <table class="bordered-table zebra-striped">
6
+ <% for key, value in rfid.redis.info.to_a.sort_by { |i| i[0].to_s } %>
7
+ <tr>
8
+ <th>
9
+ <%= key %>
10
+ </th>
11
+ <td>
12
+ <%= value %>
13
+ </td>
14
+ </tr>
15
+ <% end %>
16
+ </table>
@@ -0,0 +1,47 @@
1
+ <div class="page-header">
2
+ <h1>Install instructions</h1>
3
+ </div>
4
+
5
+ <h3>Include javascripts</h3>
6
+ Simple:
7
+ <pre class="prettyprint">
8
+ &lt;script src=&quot;/javascripts/websocket.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
9
+ </pre>
10
+
11
+ Dynamic:
12
+ <pre class="prettyprint">
13
+ (function(d, s, id) {
14
+ var js, fjs = d.getElementsByTagName(s)[0];
15
+ if (d.getElementById(id)) {return;}
16
+ js = d.createElement(s); js.id = id;
17
+ js.src = "/javascripts/websocket.js";
18
+ // Setup a callback to track once the script loads.
19
+ js.onload = subscribe;
20
+ fjs.parentNode.insertBefore(js, fjs);
21
+ }(document, 'script', 'rfid-websocket'));
22
+
23
+ function subscribe() {
24
+ ...
25
+ }
26
+ </pre>
27
+
28
+ <h3>Subscribe to channel</h3>
29
+ <pre class="prettyprint">
30
+ var channel = new FancyWebSocket("ws://127.0.0.1:8080", { debug: true });
31
+
32
+ channel.bind('connect', function(data){
33
+ console.log(data);
34
+ });
35
+
36
+ channel.bind('checkout', function(data){
37
+ console.log(this.socket_id);
38
+ });
39
+
40
+ channel.bind('open', function(data){
41
+ // open
42
+ });
43
+
44
+ channel.bind('close', function(data){
45
+ // close
46
+ });
47
+ </pre>
@@ -0,0 +1,78 @@
1
+ require "thor"
2
+
3
+ module Rfid
4
+ class CLI < Thor
5
+ include Thor::Actions
6
+
7
+ default_task :help
8
+
9
+ desc "websocket [ACTION]", "Action: start/stop/restart/run websocket server. For development pass run"
10
+ def websocket(method_name = 'start')
11
+ command = File.join(self.class.source_root, 'lib/runner.rb')
12
+ run_ruby_script "#{command} #{method_name}", :verbose => false
13
+ end
14
+ map %w(-w --websocket) => :websocket
15
+
16
+ desc "app [ACTION]", "Action: start/stop/restart webui for websocket server"
17
+ def app(method_name = 'start')
18
+ path = self.class.source_root
19
+ config = File.join(path, 'config/thin.yml')
20
+ rack = File.join(path, 'config.ru')
21
+
22
+ in_root do
23
+ run "thin #{method_name} -R #{rack} -C #{config} --chdir #{path}", :verbose => false
24
+ end
25
+ end
26
+ map %w(-a --app) => :app
27
+
28
+ desc "checkout [DEVICE] [CARD1] [CARD2] ...", "Create RFID API event and send callback data to websocket server"
29
+ def checkout(device, *cards)
30
+ event = Rfid::Event.new(device, cards)
31
+ say "Create new event on device #{event.device_id} (#{device})", :green
32
+ event.save
33
+ end
34
+ map %w(-c --checkout) => :checkout
35
+
36
+ desc "setup", "Configure RFID options and create tmp directories"
37
+ method_option :force, :type => :boolean, :aliases => "-f", :banner => "Reset settings to defaults"
38
+ def setup
39
+ require 'yaml/store'
40
+
41
+ if options[:force]
42
+ say("Reset settings to defaults", :red)
43
+ copy_file("config/rfid.yml.sample", File.join(self.class.source_root, "config/rfid.yml"))
44
+ end
45
+
46
+ say("Press ENTER if you don't want change current value", :green)
47
+
48
+ store = YAML::Store.new(Rfid.config_path)
49
+
50
+ store.transaction do
51
+ Rfid.config.table.each do |key, value|
52
+ value = ask("#{key} (#{value}):", :yellow)
53
+
54
+ unless value.blank?
55
+ store[key.to_s] = value
56
+ say_status :set, "#{key} => #{value}"
57
+ end
58
+ end
59
+ end
60
+
61
+ say("Setup init script", :green)
62
+ copy_file "lib/templates/init", "/etc/init.d/rfid"
63
+ chmod "/etc/init.d/rfid", 0755
64
+
65
+ say("Setup completed", :green)
66
+ end
67
+
68
+ desc "version", "Prints the rfid's version information"
69
+ def version
70
+ say "Rfid version #{Rfid::VERSION}"
71
+ end
72
+ map %w(-v --version) => :version
73
+
74
+ def self.source_root
75
+ File.dirname(File.expand_path('../../', __FILE__))
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,23 @@
1
+ require "yaml"
2
+ require "ostruct"
3
+
4
+ module Rfid
5
+ class Config < OpenStruct
6
+ attr_reader :table, :filepath
7
+
8
+ def initialize(filepath)
9
+ @filepath = filepath.to_s.strip
10
+ super(parse_file)
11
+ end
12
+
13
+ def reload!
14
+ @table = parse_file.symbolize_keys
15
+ end
16
+
17
+ protected
18
+
19
+ def parse_file
20
+ YAML.load_file(@filepath)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path|
2
+ require "rfid/core_ext/#{File.basename(path, '.rb')}"
3
+ end
@@ -0,0 +1,2 @@
1
+ require 'json'
2
+ ActiveSupport::JSON.engine = :json_gem
@@ -0,0 +1,101 @@
1
+ require 'curb'
2
+ require 'digest/md5'
3
+ require 'active_support/json'
4
+
5
+ module Rfid
6
+ class Event
7
+ attr_accessor :device_id, :key, :cards
8
+
9
+ def initialize(device, cards)
10
+ @key = device.to_s.strip
11
+ @device_id = (Rfid.config.device_id || @key)
12
+ @cards = Array.wrap(cards)
13
+ @url = File.join(Rfid.config.api_url, "/events/#{@device_id}.json")
14
+ @time = Time.now.strftime("%Y%m%d%H%M%S")
15
+ @mode = 1
16
+ end
17
+
18
+ def attributes
19
+ {
20
+ :device_id => @device_id,
21
+ :cards => @cards,
22
+ :time => @time,
23
+ :mode => @mode,
24
+ :url => @url,
25
+ :key => @key
26
+ }
27
+ end
28
+
29
+ def save
30
+ Rfid.push_event({:event => 'checkout', :data => attributes})
31
+
32
+ curl = Curl::Easy.new(@url) do |c|
33
+ c.headers["User-Agent"] = "rfid-gem-#{Rfid::VERSION}"
34
+ c.verbose = true
35
+ c.timeout = 25
36
+ end
37
+
38
+ body = @cards.inject([]) do |arr, card|
39
+ arr << Curl::PostField.content('event[cards][]', card)
40
+ arr
41
+ end
42
+
43
+ body << Curl::PostField.content('event[mode]', @mode)
44
+ body << Curl::PostField.content('event[time]', @time)
45
+ body << Curl::PostField.content('event[hash]', hash)
46
+
47
+ begin
48
+ curl.http_post(*body)
49
+ parse(curl.response_code, curl.body_str)
50
+ rescue Exception => e
51
+ error!(e.message, e.backtrace.join("\n"))
52
+ end
53
+ end
54
+
55
+ def to_s
56
+ "<Event device: #{@device_id}, cards: #{@cards.join(',')}, time: #{@time}>"
57
+ end
58
+
59
+ protected
60
+
61
+ # Generate md5 hash
62
+ def hash
63
+ value = cards.map(&:to_s).sort.join
64
+ secret_hash(value, @time)
65
+ end
66
+
67
+ # Generate md5 sum
68
+ # md5(id, secret_key, cards.sort.join, time)
69
+ #
70
+ def secret_hash(*args)
71
+ Digest::MD5.hexdigest([device_id, secret_token].concat(args).map(&:to_s).join)
72
+ end
73
+
74
+ # Get device secret_token
75
+ def secret_token
76
+ Rfid.config.device_token
77
+ end
78
+
79
+ # Save error in logs end send data to websocket
80
+ def error!(message, backtrace = nil)
81
+ Rfid.log("Error in", to_s, message, backtrace)
82
+ Rfid.push_event({:event => 'error', :data => { :message => message, :event => attributes }})
83
+ end
84
+
85
+ # Parse response end send it to websocket
86
+ def parse(status, body)
87
+ case status
88
+ when 200, 201 then
89
+ hash = ActiveSupport::JSON.decode(body)
90
+ Rfid.push_event(hash['data'])
91
+ when 422 then
92
+ hash = ActiveSupport::JSON.decode(body)
93
+ error!(hash['errors'])
94
+ when 500 then
95
+ error!("RFID Server API error: 500")
96
+ else
97
+ error!(body)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,68 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+ require 'em-hiredis'
3
+
4
+ module Rfid
5
+ module Translator
6
+ extend self
7
+
8
+ mattr_accessor :channel
9
+ @@channel = nil
10
+
11
+ def redis
12
+ @redis ||= EM::Hiredis.connect(Rfid.config.redis_url)
13
+ end
14
+
15
+ def start
16
+ redis.blpop(Rfid.config.redis_key, 0).callback do |list, data|
17
+ send(data)
18
+ EM.next_tick(&method(:start))
19
+ end
20
+ end
21
+
22
+ def connected?
23
+ !self.channel.nil?
24
+ end
25
+
26
+ def send(message)
27
+ if connected?
28
+ web_log('client_message', nil, "Message", message)
29
+ self.channel.push(message)
30
+ end
31
+ end
32
+
33
+ # types:
34
+ # 'socket_opened'
35
+ # 'socket_closed'
36
+ # 'channel_occupied'
37
+ # 'channel_vacated'
38
+ # 'channel_subscribed'
39
+ # 'channel_unsubscribed'
40
+ # 'api_message'
41
+ # 'client_message'
42
+ # 'presence_message'
43
+ # 'error'
44
+ # 'api_error'
45
+ # 'client_error'
46
+ # 'pipe_incoming_message'
47
+ # 'wsapi_message'
48
+ #
49
+ def web_log(type, socket_id, message = nil, data = nil)
50
+ if connected?
51
+ hash = {
52
+ :event => 'log_message',
53
+ :data => {
54
+ :socket_id => socket_id,
55
+ :message => message,
56
+ :date => Time.now.strftime("%H:%M:%S"),
57
+ :type => type,
58
+ :data => data,
59
+ }
60
+ }
61
+
62
+ self.channel.push(hash.to_json)
63
+ end
64
+
65
+ Rfid.log(type, socket_id, message, data)
66
+ end
67
+ end
68
+ end