tamashii-agent 0.1.7
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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +5 -0
- data/Guardfile +70 -0
- data/LICENSE.md +201 -0
- data/README.md +38 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/tamashii-agent +95 -0
- data/lib/tamashii/agent.rb +18 -0
- data/lib/tamashii/agent/adapter/base.rb +28 -0
- data/lib/tamashii/agent/adapter/buzzer.rb +21 -0
- data/lib/tamashii/agent/adapter/card_reader.rb +20 -0
- data/lib/tamashii/agent/buzzer.rb +35 -0
- data/lib/tamashii/agent/card_reader.rb +57 -0
- data/lib/tamashii/agent/common.rb +13 -0
- data/lib/tamashii/agent/common/loggable.rb +18 -0
- data/lib/tamashii/agent/component.rb +71 -0
- data/lib/tamashii/agent/config.rb +26 -0
- data/lib/tamashii/agent/connection.rb +231 -0
- data/lib/tamashii/agent/device/fake_buzzer.rb +30 -0
- data/lib/tamashii/agent/device/fake_card_reader.rb +37 -0
- data/lib/tamashii/agent/device/pi_buzzer.rb +70 -0
- data/lib/tamashii/agent/handler.rb +10 -0
- data/lib/tamashii/agent/handler/base.rb +15 -0
- data/lib/tamashii/agent/handler/buzzer.rb +14 -0
- data/lib/tamashii/agent/handler/request_pool_response.rb +14 -0
- data/lib/tamashii/agent/handler/system.rb +14 -0
- data/lib/tamashii/agent/master.rb +113 -0
- data/lib/tamashii/agent/request_pool.rb +80 -0
- data/lib/tamashii/agent/request_pool/request.rb +38 -0
- data/lib/tamashii/agent/request_pool/response.rb +18 -0
- data/lib/tamashii/agent/version.rb +5 -0
- data/tamashii-agent.gemspec +46 -0
- metadata +252 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
require "tamashii/common"
|
2
|
+
require "tamashii/agent/version"
|
3
|
+
require "tamashii/agent/master"
|
4
|
+
require "tamashii/agent/config"
|
5
|
+
|
6
|
+
|
7
|
+
module Tamashii
|
8
|
+
module Agent
|
9
|
+
def self.config(&block)
|
10
|
+
return Config.class_eval(&block) if block_given?
|
11
|
+
Config
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.logger
|
15
|
+
@logger ||= Tamashii::Logger.new(Config.log_file)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'tamashii/common'
|
2
|
+
require 'tamashii/agent/config'
|
3
|
+
|
4
|
+
module Tamashii
|
5
|
+
module Agent
|
6
|
+
module Adapter
|
7
|
+
class Base
|
8
|
+
class << self
|
9
|
+
def object(*args, &block)
|
10
|
+
current_class.new(*args, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def current_class
|
14
|
+
Config.env == "test" ? fake_class : real_class
|
15
|
+
end
|
16
|
+
|
17
|
+
def real_class
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def fake_class
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'tamashii/agent/adapter/base'
|
2
|
+
require 'tamashii/agent/device/pi_buzzer'
|
3
|
+
require 'tamashii/agent/device/fake_buzzer'
|
4
|
+
|
5
|
+
module Tamashii
|
6
|
+
module Agent
|
7
|
+
module Adapter
|
8
|
+
class Buzzer < Base
|
9
|
+
class << self
|
10
|
+
def real_class
|
11
|
+
Device::PIBuzzer
|
12
|
+
end
|
13
|
+
|
14
|
+
def fake_class
|
15
|
+
Device::FakeBuzzer
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'tamashii/agent/adapter/base'
|
2
|
+
require 'tamashii/agent/device/fake_card_reader'
|
3
|
+
|
4
|
+
module Tamashii
|
5
|
+
module Agent
|
6
|
+
module Adapter
|
7
|
+
class CardReader < Base
|
8
|
+
class << self
|
9
|
+
def real_class
|
10
|
+
MFRC522
|
11
|
+
end
|
12
|
+
|
13
|
+
def fake_class
|
14
|
+
Device::FakeCardReader
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'tamashii/agent/component'
|
2
|
+
require 'tamashii/agent/adapter/buzzer'
|
3
|
+
|
4
|
+
module Tamashii
|
5
|
+
module Agent
|
6
|
+
class Buzzer < Component
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
@buzzer = Adapter::Buzzer.object
|
10
|
+
logger.debug "Using buzzer instance: #{@buzzer.class}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def process_event(ev_type, ev_body)
|
14
|
+
case ev_type
|
15
|
+
when EVENT_BEEP
|
16
|
+
logger.debug "Beep: #{ev_body}"
|
17
|
+
case ev_body
|
18
|
+
when "ok"
|
19
|
+
@buzzer.play_ok
|
20
|
+
when "no"
|
21
|
+
@buzzer.play_no
|
22
|
+
when "error"
|
23
|
+
@buzzer.play_error
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def clean_up
|
29
|
+
super
|
30
|
+
@buzzer.stop
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'mfrc522'
|
2
|
+
|
3
|
+
require 'tamashii/agent/component'
|
4
|
+
require 'tamashii/agent/adapter/card_reader'
|
5
|
+
|
6
|
+
|
7
|
+
module Tamashii
|
8
|
+
module Agent
|
9
|
+
class CardReader < Component
|
10
|
+
def initialize(master)
|
11
|
+
super()
|
12
|
+
@master = master
|
13
|
+
@reader = Adapter::CardReader.object
|
14
|
+
logger.debug "Using card_reader instance: #{@reader.class}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# override
|
18
|
+
def worker_loop
|
19
|
+
loop do
|
20
|
+
handle_io
|
21
|
+
handle_card
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle_io
|
26
|
+
ready = @selector.select(0.1)
|
27
|
+
ready.each { |m| m.value.call } if ready
|
28
|
+
end
|
29
|
+
|
30
|
+
def handle_card
|
31
|
+
# read card
|
32
|
+
return unless @reader.picc_request(MFRC522::PICC_REQA)
|
33
|
+
|
34
|
+
begin
|
35
|
+
uid, sak = @reader.picc_select
|
36
|
+
process_uid(uid.join("-"))
|
37
|
+
rescue CommunicationError, UnexpectedDataError => e
|
38
|
+
logger.error "Error when selecting card: #{e.message}"
|
39
|
+
rescue => e
|
40
|
+
logger.error "GemError when selecting card: #{e.message}"
|
41
|
+
end
|
42
|
+
@reader.picc_halt
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_uid(uid)
|
46
|
+
logger.info "New card detected, UID: #{uid}"
|
47
|
+
@master.send_event(EVENT_CARD_DATA, uid)
|
48
|
+
end
|
49
|
+
|
50
|
+
# override
|
51
|
+
def process_event(ev_type, ev_body)
|
52
|
+
# silent is gold
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'tamashii/agent'
|
2
|
+
|
3
|
+
module Tamashii
|
4
|
+
module Agent
|
5
|
+
module Common
|
6
|
+
module Loggable
|
7
|
+
def logger
|
8
|
+
Agent.logger.progname = self.progname
|
9
|
+
Agent.logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def progname
|
13
|
+
@progname ||= ("%-10s" % self.class.to_s.split(":")[-1])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'nio'
|
2
|
+
require 'tamashii/agent/common'
|
3
|
+
|
4
|
+
module Tamashii
|
5
|
+
module Agent
|
6
|
+
class Component
|
7
|
+
include Common::Loggable
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@pipe_r, @pipe_w = IO.pipe
|
11
|
+
end
|
12
|
+
|
13
|
+
def send_event(type, body)
|
14
|
+
str = [type, body.bytesize].pack("Cn") + body
|
15
|
+
@pipe_w.write(str)
|
16
|
+
end
|
17
|
+
|
18
|
+
def receive_event
|
19
|
+
ev_type, ev_size = @pipe_r.read(3).unpack("Cn")
|
20
|
+
ev_body = @pipe_r.read(ev_size)
|
21
|
+
process_event(ev_type, ev_body)
|
22
|
+
end
|
23
|
+
|
24
|
+
def process_event(ev_type, ev_body)
|
25
|
+
logger.debug "Got event: #{ev_type}, #{ev_body}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# worker
|
29
|
+
def run
|
30
|
+
@thr = Thread.start { run_worker_loop }
|
31
|
+
end
|
32
|
+
|
33
|
+
def run!
|
34
|
+
run_worker_loop
|
35
|
+
end
|
36
|
+
|
37
|
+
def stop
|
38
|
+
logger.info "Stopping component"
|
39
|
+
@thr.exit if @thr
|
40
|
+
@thr = nil
|
41
|
+
clean_up
|
42
|
+
end
|
43
|
+
|
44
|
+
def clean_up
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_worker_loop
|
48
|
+
create_selector
|
49
|
+
register_event_io
|
50
|
+
worker_loop
|
51
|
+
end
|
52
|
+
|
53
|
+
# a default implementation
|
54
|
+
def worker_loop
|
55
|
+
loop do
|
56
|
+
ready = @selector.select
|
57
|
+
ready.each { |m| m.value.call } if ready
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def register_event_io
|
62
|
+
_monitor = @selector.register(@pipe_r, :r)
|
63
|
+
_monitor.value = method(:receive_event)
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_selector
|
67
|
+
@selector = NIO::Selector.new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'tamashii/common'
|
2
|
+
module Tamashii
|
3
|
+
module Agent
|
4
|
+
class Config < Tamashii::Config
|
5
|
+
AUTH_TYPES = [:none, :token]
|
6
|
+
|
7
|
+
register :log_file, STDOUT
|
8
|
+
register :use_ssl, false
|
9
|
+
register :auth_type, :none
|
10
|
+
register :entry_point, "/tamashii"
|
11
|
+
register :manager_host, "localhost"
|
12
|
+
register :manager_port, 3000
|
13
|
+
|
14
|
+
def auth_type(type = nil)
|
15
|
+
return @auth_type ||= :none if type.nil?
|
16
|
+
return unless AUTH_TYPES.include?(type)
|
17
|
+
@auth_type = type.to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
def log_level(level = nil)
|
21
|
+
return Agent.logger.level if level.nil?
|
22
|
+
Agent.logger.level = level
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'websocket/driver'
|
3
|
+
require 'aasm'
|
4
|
+
|
5
|
+
require 'tamashii/common'
|
6
|
+
|
7
|
+
require 'tamashii/agent/config'
|
8
|
+
require 'tamashii/agent/component'
|
9
|
+
require 'tamashii/agent/request_pool'
|
10
|
+
|
11
|
+
require 'tamashii/agent/handler'
|
12
|
+
|
13
|
+
module Tamashii
|
14
|
+
module Agent
|
15
|
+
class Connection < Component
|
16
|
+
include AASM
|
17
|
+
|
18
|
+
aasm do
|
19
|
+
state :init, initial: true
|
20
|
+
state :connecting
|
21
|
+
state :auth_pending
|
22
|
+
state :ready
|
23
|
+
|
24
|
+
event :connect do
|
25
|
+
transitions from: :init, to: :connecting, after: Proc.new { logger.info "Start connecting" }
|
26
|
+
end
|
27
|
+
|
28
|
+
event :auth_request do
|
29
|
+
transitions from: :connecting, to: :auth_pending, after: Proc.new { logger.info "Sending authentication request" }
|
30
|
+
end
|
31
|
+
|
32
|
+
event :auth_success do
|
33
|
+
transitions from: :auth_pending, to: :ready, after: Proc.new { logger.info "Authentication finished. Tag = #{@tag}" }
|
34
|
+
end
|
35
|
+
|
36
|
+
event :reset do
|
37
|
+
transitions to: :init, after: Proc.new { logger.info "Connection state reset" }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :url
|
42
|
+
attr_reader :master
|
43
|
+
attr_reader :request_pool
|
44
|
+
|
45
|
+
def initialize(master, host, port)
|
46
|
+
super()
|
47
|
+
@master = master
|
48
|
+
@url = "#{Config.use_ssl ? "wss" : "ws"}://#{host}:#{port}/#{Config.entry_point}"
|
49
|
+
self.reset
|
50
|
+
|
51
|
+
@host = host
|
52
|
+
@port = port
|
53
|
+
@tag = 0
|
54
|
+
|
55
|
+
@request_pool = RequestPool.new
|
56
|
+
@request_pool.set_handler(:request_timedout, method(:handle_request_timedout))
|
57
|
+
@request_pool.set_handler(:request_meet, method(:handle_request_meet))
|
58
|
+
@request_pool.set_handler(:send_request, method(:handle_send_request))
|
59
|
+
|
60
|
+
env_data = {connection: self}
|
61
|
+
Resolver.config do
|
62
|
+
[Type::REBOOT, Type::POWEROFF, Type::RESTART, Type::UPDATE].each do |type|
|
63
|
+
handle type, Handler::System, env_data
|
64
|
+
end
|
65
|
+
handle Type::BUZZER_SOUND, Handler::Buzzer, env_data
|
66
|
+
handle Type::RFID_RESPONSE_JSON, Handler::RequestPoolResponse, env_data
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_request_timedout(req)
|
71
|
+
@master.send_event(EVENT_CONNECTION_NOT_READY, "Connection not ready for #{req.ev_type}:#{req.ev_body}")
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle_request_meet(req, res)
|
75
|
+
logger.debug "Got packet: #{res.ev_type}: #{res.ev_body}"
|
76
|
+
case res.ev_type
|
77
|
+
when Type::RFID_RESPONSE_JSON
|
78
|
+
json = JSON.parse(res.ev_body)
|
79
|
+
handle_card_result(json)
|
80
|
+
else
|
81
|
+
logger.warn "Unhandled packet result: #{res.ev_type}: #{res.ev_body}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def handle_card_result(result)
|
86
|
+
if result["auth"]
|
87
|
+
@master.send_event(EVENT_BEEP, "ok")
|
88
|
+
else
|
89
|
+
@master.send_event(EVENT_BEEP, "no")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_send_request(req)
|
94
|
+
if self.ready?
|
95
|
+
@driver.binary(Packet.new(req.ev_type, @tag, req.wrap_body).dump)
|
96
|
+
true
|
97
|
+
else
|
98
|
+
false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# override
|
103
|
+
def worker_loop
|
104
|
+
loop do
|
105
|
+
@request_pool.update
|
106
|
+
ready = @selector.select(1)
|
107
|
+
ready.each { |m| m.value.call } if ready
|
108
|
+
if @io.nil?
|
109
|
+
@io = try_create_socket
|
110
|
+
if @io
|
111
|
+
# socket io opened
|
112
|
+
register_socket_io
|
113
|
+
# start ws
|
114
|
+
start_web_driver
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def send_auth_request
|
121
|
+
# TODO: other types of auth
|
122
|
+
@driver.binary(Packet.new(Type::AUTH_TOKEN, 0, [Type::CLIENT[:agent], @master.serial_number,Config.token].join(",")).dump)
|
123
|
+
end
|
124
|
+
|
125
|
+
def start_web_driver
|
126
|
+
@driver = WebSocket::Driver.client(self)
|
127
|
+
@driver.on :open, proc { |e|
|
128
|
+
logger.info "Server opened"
|
129
|
+
self.auth_request
|
130
|
+
send_auth_request
|
131
|
+
}
|
132
|
+
@driver.on :close, proc { |e|
|
133
|
+
logger.info "Server closed"
|
134
|
+
close_socket_io
|
135
|
+
self.reset
|
136
|
+
}
|
137
|
+
@driver.on :message, proc { |e|
|
138
|
+
pkt = Packet.load(e.data)
|
139
|
+
process_packet(pkt) if pkt
|
140
|
+
}
|
141
|
+
@driver.start
|
142
|
+
self.connect
|
143
|
+
end
|
144
|
+
|
145
|
+
def register_socket_io
|
146
|
+
_monitor = @selector.register(@io, :r)
|
147
|
+
_monitor.value = proc do
|
148
|
+
begin
|
149
|
+
msg = @io.recv_nonblock(65535)
|
150
|
+
if msg.empty?
|
151
|
+
# socket closed
|
152
|
+
logger.info "No message received from server. Connection reset"
|
153
|
+
close_socket_io
|
154
|
+
self.reset
|
155
|
+
sleep 1
|
156
|
+
else
|
157
|
+
@driver.parse(msg)
|
158
|
+
end
|
159
|
+
rescue => e
|
160
|
+
logger.error "#{e.message}"
|
161
|
+
logger.debug "Backtrace:"
|
162
|
+
e.backtrace.each {|msg| logger.debug msg}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def try_create_socket
|
168
|
+
logger.info "try to open socket..."
|
169
|
+
TCPSocket.new(@host, @port)
|
170
|
+
rescue
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
|
174
|
+
def close_socket_io
|
175
|
+
logger.info "Socket IO Closed and Deregistered"
|
176
|
+
@selector.deregister(@io)
|
177
|
+
@io.close
|
178
|
+
@io = nil
|
179
|
+
end
|
180
|
+
|
181
|
+
def write(string)
|
182
|
+
@io.write(string)
|
183
|
+
rescue
|
184
|
+
logger.error "Write Error"
|
185
|
+
close_socket_io
|
186
|
+
self.reset
|
187
|
+
end
|
188
|
+
|
189
|
+
def process_packet(pkt)
|
190
|
+
if self.auth_pending?
|
191
|
+
if pkt.type == Type::AUTH_RESPONSE
|
192
|
+
if pkt.body == Packet::STRING_TRUE
|
193
|
+
@tag = pkt.tag
|
194
|
+
self.auth_success
|
195
|
+
else
|
196
|
+
logger.error "Authentication failed. Delay for 3 seconds"
|
197
|
+
sleep 3
|
198
|
+
end
|
199
|
+
else
|
200
|
+
logger.error "Authentication error: Not an authentication result packet"
|
201
|
+
end
|
202
|
+
else
|
203
|
+
if pkt.tag == @tag || pkt.tag == 0
|
204
|
+
Resolver.resolve(pkt)
|
205
|
+
else
|
206
|
+
logger.debug "Tag mismatch packet: tag: #{pkt.tag}, type: #{pkt.type}"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def process_event(ev_type, ev_body)
|
212
|
+
case ev_type
|
213
|
+
when EVENT_CARD_DATA
|
214
|
+
req = RequestPool::Request.new(Type::RFID_NUMBER , ev_body, ev_body)
|
215
|
+
@request_pool.add_request(req)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def clean_up
|
220
|
+
super
|
221
|
+
if @io
|
222
|
+
@driver.close
|
223
|
+
close_socket_io
|
224
|
+
end
|
225
|
+
rescue => e
|
226
|
+
logger.warn "Error occured when clean up: #{e.to_s}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|