tamashii-agent 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,13 @@
1
+ require 'tamashii/agent/common/loggable'
2
+
3
+ module Tamashii
4
+ module Agent
5
+ module Common
6
+ end
7
+ EVENT_BEEP = 1
8
+ EVENT_SYSTEM_COMMAND = 2
9
+ EVENT_AUTH_RESULT = 3
10
+ EVENT_CARD_DATA = 4
11
+ EVENT_CONNECTION_NOT_READY = 255
12
+ end
13
+ end
@@ -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
+