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.
- data/INSTALL.rdoc +55 -0
- data/README.rdoc +60 -0
- data/bin/rfid +18 -0
- data/bin/rfid_checkout +22 -0
- data/config.ru +9 -0
- data/config/boot.rb +6 -0
- data/config/environment.rb +8 -0
- data/config/rfid.yml +6 -0
- data/config/rfid.yml.sample +6 -0
- data/config/thin.yml +14 -0
- data/config/thin.yml.sample +14 -0
- data/lib/rfid.rb +75 -0
- data/lib/rfid/application.rb +115 -0
- data/lib/rfid/application/public/favicon.ico +0 -0
- data/lib/rfid/application/public/javascripts/application.js +12 -0
- data/lib/rfid/application/public/javascripts/debug.js +59 -0
- data/lib/rfid/application/public/stylesheets/application.css +49 -0
- data/lib/rfid/application/public/stylesheets/bootstrap.css +2467 -0
- data/lib/rfid/application/views/events.erb +34 -0
- data/lib/rfid/application/views/index.erb +21 -0
- data/lib/rfid/application/views/key_sets.erb +13 -0
- data/lib/rfid/application/views/key_string.erb +11 -0
- data/lib/rfid/application/views/keys.erb +20 -0
- data/lib/rfid/application/views/layout.erb +56 -0
- data/lib/rfid/application/views/redis.erb +16 -0
- data/lib/rfid/application/views/usage.erb +47 -0
- data/lib/rfid/cli.rb +78 -0
- data/lib/rfid/config.rb +23 -0
- data/lib/rfid/core_ext.rb +3 -0
- data/lib/rfid/core_ext/json_engine.rb +2 -0
- data/lib/rfid/event.rb +101 -0
- data/lib/rfid/translator.rb +68 -0
- data/lib/rfid/version.rb +3 -0
- data/lib/rfid/websocket.rb +35 -0
- data/lib/runner.rb +18 -0
- data/lib/server.rb +19 -0
- data/lib/templates/init +109 -0
- data/spec/models/application_spec.rb +54 -0
- data/spec/models/config_spec.rb +33 -0
- data/spec/models/event_spec.rb +22 -0
- data/spec/rfid_spec.rb +58 -0
- data/spec/spec_helper.rb +25 -0
- 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"> <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>© 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
|
+
<script src="/javascripts/websocket.js" type="text/javascript"></script>
|
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>
|
data/lib/rfid/cli.rb
ADDED
@@ -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
|
data/lib/rfid/config.rb
ADDED
@@ -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
|
data/lib/rfid/event.rb
ADDED
@@ -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
|