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.
@@ -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
+