rfid 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 (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