tamashii-agent 0.1.11 → 0.2.0
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 +4 -4
- data/bin/console +4 -4
- data/lib/tamashii/agent/adapter/lcd.rb +22 -0
- data/lib/tamashii/agent/buzzer.rb +6 -5
- data/lib/tamashii/agent/card_reader.rb +16 -10
- data/lib/tamashii/agent/common.rb +0 -5
- data/lib/tamashii/agent/component.rb +29 -27
- data/lib/tamashii/agent/config.rb +4 -0
- data/lib/tamashii/agent/connection.rb +156 -32
- data/lib/tamashii/agent/device/fake_lcd.rb +30 -0
- data/lib/tamashii/agent/device/lcd.rb +86 -0
- data/lib/tamashii/agent/event.rb +27 -0
- data/lib/tamashii/agent/handler/buzzer.rb +2 -2
- data/lib/tamashii/agent/handler/lcd.rb +19 -0
- data/lib/tamashii/agent/handler/remote_response.rb +13 -0
- data/lib/tamashii/agent/handler/system.rb +2 -2
- data/lib/tamashii/agent/handler.rb +2 -1
- data/lib/tamashii/agent/lcd.rb +72 -0
- data/lib/tamashii/agent/master.rb +23 -14
- data/lib/tamashii/agent/version.rb +1 -1
- data/lib/tamashii/agent.rb +0 -1
- data/tamashii-agent.gemspec +4 -1
- metadata +51 -6
- data/lib/tamashii/agent/handler/request_pool_response.rb +0 -14
- data/lib/tamashii/agent/request_pool/request.rb +0 -38
- data/lib/tamashii/agent/request_pool/response.rb +0 -18
- data/lib/tamashii/agent/request_pool.rb +0 -80
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bfc8a55dfec1ccbc0d4d7f6c26fb1331120b9924
|
4
|
+
data.tar.gz: 5ecc7b988ce09c76058a48c410d1b3d7d5bf1afa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8689f7b4ed689a28abe1137bab775e80e78a6ec01f8958c2bce1fe3e0f812a2d06b421deb32ac4337283d719d698a1df41ac72147e8d4ef822aef6f97b2723d3
|
7
|
+
data.tar.gz: 7ba6892db8cd54b5a8eb1333302ee470552a65c8b40f093625fa29b7b800a5a4eac3dc57d3f63d97bf6c1b0988e6245f0720445ab2eef12c76c5b6e5cb716a29
|
data/bin/console
CHANGED
@@ -7,8 +7,8 @@ require "tamashii/agent"
|
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
8
8
|
|
9
9
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
|
11
|
-
|
10
|
+
require "pry"
|
11
|
+
Pry.start
|
12
12
|
|
13
|
-
require "irb"
|
14
|
-
IRB.start
|
13
|
+
#require "irb"
|
14
|
+
#IRB.start
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'tamashii/agent/adapter/base'
|
2
|
+
require 'tamashii/agent/device/lcd'
|
3
|
+
require 'tamashii/agent/device/fake_lcd'
|
4
|
+
|
5
|
+
module Tamashii
|
6
|
+
module Agent
|
7
|
+
module Adapter
|
8
|
+
# :nodoc:
|
9
|
+
class LCD < Base
|
10
|
+
class << self
|
11
|
+
def real_class
|
12
|
+
Device::LCD
|
13
|
+
end
|
14
|
+
|
15
|
+
def fake_class
|
16
|
+
Device::FakeLCD
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'tamashii/agent/component'
|
2
|
+
require 'tamashii/agent/event'
|
2
3
|
require 'tamashii/agent/adapter/buzzer'
|
3
4
|
|
4
5
|
module Tamashii
|
@@ -10,11 +11,11 @@ module Tamashii
|
|
10
11
|
logger.debug "Using buzzer instance: #{@buzzer.class}"
|
11
12
|
end
|
12
13
|
|
13
|
-
def process_event(
|
14
|
-
case
|
15
|
-
when
|
16
|
-
logger.debug "Beep: #{
|
17
|
-
case
|
14
|
+
def process_event(event)
|
15
|
+
case event.type
|
16
|
+
when Event::BEEP
|
17
|
+
logger.debug "Beep: #{event.body}"
|
18
|
+
case event.body
|
18
19
|
when "ok"
|
19
20
|
@buzzer.play_ok
|
20
21
|
when "no"
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'mfrc522'
|
2
2
|
|
3
3
|
require 'tamashii/agent/component'
|
4
|
+
require 'tamashii/agent/event'
|
4
5
|
require 'tamashii/agent/adapter/card_reader'
|
5
6
|
|
6
7
|
|
@@ -17,19 +18,23 @@ module Tamashii
|
|
17
18
|
# override
|
18
19
|
def worker_loop
|
19
20
|
loop do
|
20
|
-
|
21
|
-
|
21
|
+
if !handle_new_event(true)
|
22
|
+
# no event available
|
23
|
+
sleep 0.1
|
24
|
+
end
|
25
|
+
if handle_card
|
26
|
+
# card is sent, sleep to prevent duplicate sent
|
27
|
+
sleep 1.0
|
28
|
+
else
|
29
|
+
# no card available
|
30
|
+
sleep 0.1
|
31
|
+
end
|
22
32
|
end
|
23
33
|
end
|
24
34
|
|
25
|
-
def handle_io
|
26
|
-
ready = @selector.select(0.1)
|
27
|
-
ready.each { |m| m.value.call } if ready
|
28
|
-
end
|
29
|
-
|
30
35
|
def handle_card
|
31
36
|
# read card
|
32
|
-
return unless @reader.picc_request(MFRC522::PICC_REQA)
|
37
|
+
return false unless @reader.picc_request(MFRC522::PICC_REQA)
|
33
38
|
|
34
39
|
begin
|
35
40
|
uid, sak = @reader.picc_select
|
@@ -40,15 +45,16 @@ module Tamashii
|
|
40
45
|
logger.error "GemError when selecting card: #{e.message}"
|
41
46
|
end
|
42
47
|
@reader.picc_halt
|
48
|
+
true
|
43
49
|
end
|
44
50
|
|
45
51
|
def process_uid(uid)
|
46
52
|
logger.info "New card detected, UID: #{uid}"
|
47
|
-
@master.send_event(
|
53
|
+
@master.send_event(Event.new(Event::CARD_DATA, uid))
|
48
54
|
end
|
49
55
|
|
50
56
|
# override
|
51
|
-
def process_event(
|
57
|
+
def process_event(event)
|
52
58
|
# silent is gold
|
53
59
|
end
|
54
60
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
require 'nio'
|
2
1
|
require 'tamashii/agent/common'
|
2
|
+
require 'tamashii/agent/event'
|
3
|
+
|
3
4
|
|
4
5
|
module Tamashii
|
5
6
|
module Agent
|
@@ -7,27 +8,33 @@ module Tamashii
|
|
7
8
|
include Common::Loggable
|
8
9
|
|
9
10
|
def initialize
|
10
|
-
@
|
11
|
+
@event_queue = Queue.new
|
11
12
|
end
|
12
13
|
|
13
|
-
def send_event(
|
14
|
-
|
15
|
-
@pipe_w.write(str)
|
14
|
+
def send_event(event)
|
15
|
+
@event_queue.push(event)
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
def check_new_event(non_block = false)
|
19
|
+
@event_queue.pop(non_block)
|
20
|
+
rescue ThreadError => e
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def handle_new_event(non_block = false)
|
25
|
+
if ev = check_new_event(non_block)
|
26
|
+
process_event(ev)
|
27
|
+
end
|
28
|
+
ev
|
22
29
|
end
|
23
30
|
|
24
|
-
def process_event(
|
25
|
-
logger.debug "Got event: #{
|
31
|
+
def process_event(event)
|
32
|
+
logger.debug "Got event: #{event.type}, #{event.body}"
|
26
33
|
end
|
27
34
|
|
28
35
|
# worker
|
29
36
|
def run
|
30
|
-
@
|
37
|
+
@worker_thr = Thread.start { run_worker_loop }
|
31
38
|
end
|
32
39
|
|
33
40
|
def run!
|
@@ -36,36 +43,31 @@ module Tamashii
|
|
36
43
|
|
37
44
|
def stop
|
38
45
|
logger.info "Stopping component"
|
39
|
-
|
40
|
-
@thr = nil
|
46
|
+
stop_threads
|
41
47
|
clean_up
|
42
48
|
end
|
43
49
|
|
50
|
+
def stop_threads
|
51
|
+
@worker_thr.exit if @worker_thr
|
52
|
+
@worker_thr = nil
|
53
|
+
end
|
54
|
+
|
44
55
|
def clean_up
|
45
56
|
end
|
46
57
|
|
47
58
|
def run_worker_loop
|
48
|
-
create_selector
|
49
|
-
register_event_io
|
50
59
|
worker_loop
|
51
60
|
end
|
52
61
|
|
53
62
|
# a default implementation
|
54
63
|
def worker_loop
|
55
64
|
loop do
|
56
|
-
|
57
|
-
|
65
|
+
if !handle_new_event
|
66
|
+
logger.error "Thread error. Worker loop terminated"
|
67
|
+
break
|
68
|
+
end
|
58
69
|
end
|
59
70
|
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
71
|
end
|
70
72
|
end
|
71
73
|
end
|
@@ -10,6 +10,10 @@ module Tamashii
|
|
10
10
|
register :entry_point, "/tamashii"
|
11
11
|
register :manager_host, "localhost"
|
12
12
|
register :manager_port, 3000
|
13
|
+
register :connection_timeout, 3
|
14
|
+
|
15
|
+
register :lcd_path, '/dev/i2c-1'
|
16
|
+
register :lcd_address, 0x27
|
13
17
|
|
14
18
|
def auth_type(type = nil)
|
15
19
|
return @auth_type ||= :none if type.nil?
|
@@ -2,18 +2,52 @@ require 'socket'
|
|
2
2
|
require 'websocket/driver'
|
3
3
|
require 'aasm'
|
4
4
|
require 'openssl'
|
5
|
+
require 'json'
|
6
|
+
require 'concurrent'
|
7
|
+
require 'nio'
|
5
8
|
|
6
9
|
require 'tamashii/common'
|
7
10
|
|
8
11
|
require 'tamashii/agent/config'
|
12
|
+
require 'tamashii/agent/event'
|
9
13
|
require 'tamashii/agent/component'
|
10
|
-
require 'tamashii/agent/request_pool'
|
11
14
|
|
12
15
|
require 'tamashii/agent/handler'
|
13
16
|
|
14
17
|
module Tamashii
|
15
18
|
module Agent
|
16
19
|
class Connection < Component
|
20
|
+
|
21
|
+
class RequestTimeoutError < RuntimeError; end
|
22
|
+
|
23
|
+
class RequestObserver
|
24
|
+
include Common::Loggable
|
25
|
+
def initialize(connection, id, ev_type, ev_body, future)
|
26
|
+
@connection = connection
|
27
|
+
@id = id
|
28
|
+
@ev_type = ev_type
|
29
|
+
@ev_body = ev_body
|
30
|
+
@future = future
|
31
|
+
end
|
32
|
+
|
33
|
+
def update(time, ev_data, reason)
|
34
|
+
if @future.fulfilled?
|
35
|
+
res_ev_type = ev_data[:ev_type]
|
36
|
+
res_ev_body = ev_data[:ev_body]
|
37
|
+
case res_ev_type
|
38
|
+
when Type::RFID_RESPONSE_JSON
|
39
|
+
logger.debug "Handled: #{res_ev_type}: #{res_ev_body}"
|
40
|
+
@connection.handle_card_result(JSON.parse(res_ev_body))
|
41
|
+
else
|
42
|
+
logger.warn "Unhandled packet result: #{res_ev_type}: #{res_ev_body}"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
logger.error "#{@id} Failed with #{reason}"
|
46
|
+
@connection.on_request_timeout(@ev_type, @ev_body)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
17
51
|
include AASM
|
18
52
|
|
19
53
|
aasm do
|
@@ -41,7 +75,6 @@ module Tamashii
|
|
41
75
|
|
42
76
|
attr_reader :url
|
43
77
|
attr_reader :master
|
44
|
-
attr_reader :request_pool
|
45
78
|
|
46
79
|
def initialize(master, host, port)
|
47
80
|
super()
|
@@ -53,57 +86,70 @@ module Tamashii
|
|
53
86
|
@port = port
|
54
87
|
@tag = 0
|
55
88
|
|
56
|
-
@
|
57
|
-
@
|
58
|
-
@request_pool.set_handler(:request_meet, method(:handle_request_meet))
|
59
|
-
@request_pool.set_handler(:send_request, method(:handle_send_request))
|
89
|
+
@future_ivar_pool = Concurrent::Map.new
|
90
|
+
@driver_lock = Mutex.new
|
60
91
|
|
92
|
+
setup_resolver
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_selector
|
96
|
+
@selector = NIO::Selector.new
|
97
|
+
end
|
98
|
+
|
99
|
+
def setup_resolver
|
61
100
|
env_data = {connection: self}
|
62
101
|
Resolver.config do
|
63
102
|
[Type::REBOOT, Type::POWEROFF, Type::RESTART, Type::UPDATE].each do |type|
|
64
103
|
handle type, Handler::System, env_data
|
65
104
|
end
|
105
|
+
[Type::LCD_MESSAGE, Type::LCD_SET_IDLE_TEXT].each do |type|
|
106
|
+
handle type, Handler::LCD, env_data
|
107
|
+
end
|
66
108
|
handle Type::BUZZER_SOUND, Handler::Buzzer, env_data
|
67
|
-
handle Type::RFID_RESPONSE_JSON, Handler::
|
109
|
+
handle Type::RFID_RESPONSE_JSON, Handler::RemoteResponse, env_data
|
68
110
|
end
|
69
111
|
end
|
70
112
|
|
71
|
-
def
|
72
|
-
@master.send_event(
|
73
|
-
end
|
74
|
-
|
75
|
-
def handle_request_meet(req, res)
|
76
|
-
logger.debug "Got packet: #{res.ev_type}: #{res.ev_body}"
|
77
|
-
case res.ev_type
|
78
|
-
when Type::RFID_RESPONSE_JSON
|
79
|
-
json = JSON.parse(res.ev_body)
|
80
|
-
handle_card_result(json)
|
81
|
-
else
|
82
|
-
logger.warn "Unhandled packet result: #{res.ev_type}: #{res.ev_body}"
|
83
|
-
end
|
113
|
+
def on_request_timeout(ev_type, ev_body)
|
114
|
+
@master.send_event(Event.new(Event::CONNECTION_NOT_READY, "Connection not ready for #{ev_type}:#{ev_body}"))
|
84
115
|
end
|
85
116
|
|
86
117
|
def handle_card_result(result)
|
87
118
|
if result["auth"]
|
88
|
-
@master.send_event(
|
119
|
+
@master.send_event(Event.new(Event::BEEP, "ok"))
|
89
120
|
else
|
90
|
-
@master.send_event(
|
121
|
+
@master.send_event(Event.new(Event::BEEP, "no"))
|
122
|
+
end
|
123
|
+
if result["message"]
|
124
|
+
@master.send_event(Event.new(Event::LCD_MESSAGE, result["message"]))
|
91
125
|
end
|
92
126
|
end
|
93
127
|
|
94
|
-
def
|
128
|
+
def try_send_request(ev_type, ev_body)
|
95
129
|
if self.ready?
|
96
|
-
@
|
130
|
+
@driver_lock.synchronize do
|
131
|
+
@driver.binary(Packet.new(ev_type, @tag, ev_body).dump)
|
132
|
+
end
|
97
133
|
true
|
98
134
|
else
|
99
135
|
false
|
100
136
|
end
|
101
137
|
end
|
102
138
|
|
103
|
-
|
104
|
-
|
139
|
+
def stop_threads
|
140
|
+
super
|
141
|
+
@websocket_thr.exit if @websocket_thr
|
142
|
+
@websocket_thr = nil
|
143
|
+
end
|
144
|
+
|
145
|
+
def run
|
146
|
+
super
|
147
|
+
@websocket_thr = Thread.start { run_websocket_loop }
|
148
|
+
end
|
149
|
+
|
150
|
+
def run_websocket_loop
|
151
|
+
create_selector
|
105
152
|
loop do
|
106
|
-
@request_pool.update
|
107
153
|
ready = @selector.select(1)
|
108
154
|
ready.each { |m| m.value.call } if ready
|
109
155
|
if @io.nil?
|
@@ -218,11 +264,75 @@ module Tamashii
|
|
218
264
|
end
|
219
265
|
end
|
220
266
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
267
|
+
# override
|
268
|
+
def process_event(event)
|
269
|
+
case event.type
|
270
|
+
when Event::CARD_DATA
|
271
|
+
id = event.body
|
272
|
+
wrapped_body = {
|
273
|
+
id: id,
|
274
|
+
ev_body: event.body
|
275
|
+
}.to_json
|
276
|
+
new_remote_request(id, Type::RFID_NUMBER, wrapped_body)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def schedule_task_runner(id, ev_type, ev_body, start_time, times)
|
281
|
+
logger.debug "Schedule send attemp #{id} : #{times + 1} time(s)"
|
282
|
+
if try_send_request(ev_type, ev_body)
|
283
|
+
# Request sent, do nothing
|
284
|
+
logger.debug "Request sent for id = #{id}"
|
285
|
+
else
|
286
|
+
if Time.now - start_time < Config.connection_timeout
|
287
|
+
# Re-schedule self
|
288
|
+
logger.warn "Reschedule #{id} after 1 sec"
|
289
|
+
schedule_next_task(1, id, ev_type, ev_body, start_time, times + 1)
|
290
|
+
else
|
291
|
+
# This job is expired. Do nothing
|
292
|
+
logger.warn "Abort scheduling #{id}"
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def schedule_next_task(interval, id, ev_type, ev_body, start_time, times)
|
298
|
+
Concurrent::ScheduledTask.execute(interval, args: [id, ev_type, ev_body, start_time, times], &method(:schedule_task_runner))
|
299
|
+
end
|
300
|
+
|
301
|
+
def create_request_scheduler_task(id, ev_type, ev_body)
|
302
|
+
schedule_next_task(0, id, ev_type, ev_body, Time.now, 0)
|
303
|
+
end
|
304
|
+
|
305
|
+
def create_request_async(id, ev_type, ev_body)
|
306
|
+
req = Concurrent::Future.new do
|
307
|
+
# Create IVar for store result
|
308
|
+
ivar = Concurrent::IVar.new
|
309
|
+
@future_ivar_pool[id] = ivar
|
310
|
+
# Schedule to get the result
|
311
|
+
create_request_scheduler_task(id, ev_type, ev_body)
|
312
|
+
# Wait for result
|
313
|
+
if result = ivar.value(Config.connection_timeout)
|
314
|
+
# IVar is already removed from pool
|
315
|
+
result
|
316
|
+
else
|
317
|
+
# Manually remove IVar
|
318
|
+
# Any fulfill at this point is useless
|
319
|
+
logger.error "Timeout when getting IVar for #{id}"
|
320
|
+
@future_ivar_pool.delete(id)
|
321
|
+
raise RequestTimeoutError, "Request Timeout"
|
322
|
+
end
|
323
|
+
end
|
324
|
+
req.add_observer(RequestObserver.new(self, id, ev_type, ev_body, req))
|
325
|
+
req.execute
|
326
|
+
req
|
327
|
+
end
|
328
|
+
|
329
|
+
def new_remote_request(id, ev_type, ev_body)
|
330
|
+
# enqueue if not exists
|
331
|
+
if !@future_ivar_pool[id]
|
332
|
+
create_request_async(id, ev_type, ev_body)
|
333
|
+
logger.debug "Request created: #{id}"
|
334
|
+
else
|
335
|
+
logger.warn "Duplicated id: #{id}, ignored"
|
226
336
|
end
|
227
337
|
end
|
228
338
|
|
@@ -235,6 +345,20 @@ module Tamashii
|
|
235
345
|
rescue => e
|
236
346
|
logger.warn "Error occured when clean up: #{e.to_s}"
|
237
347
|
end
|
348
|
+
|
349
|
+
# When data is back
|
350
|
+
def handle_remote_response(ev_type, wrapped_ev_body)
|
351
|
+
logger.debug "Remote packet back: #{ev_type} #{wrapped_ev_body}"
|
352
|
+
result = JSON.parse(wrapped_ev_body)
|
353
|
+
id = result["id"]
|
354
|
+
ev_body = result["ev_body"]
|
355
|
+
# fetch ivar and delete it
|
356
|
+
if ivar = @future_ivar_pool.delete(id)
|
357
|
+
ivar.set(ev_type: ev_type, ev_body: ev_body)
|
358
|
+
else
|
359
|
+
logger.warn "IVar #{id} not in pool"
|
360
|
+
end
|
361
|
+
end
|
238
362
|
end
|
239
363
|
end
|
240
364
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Tamashii
|
2
|
+
module Agent
|
3
|
+
module Device
|
4
|
+
# :nodoc:
|
5
|
+
class FakeLCD
|
6
|
+
WIDTH = 16
|
7
|
+
|
8
|
+
attr_accessor :backlight
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@backlight = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def print_message(message)
|
15
|
+
lines = message.lines.map{|l| l.delete("\n")}
|
16
|
+
puts "LCD Display(BACKLIGHT: #{@backlight}):"
|
17
|
+
puts lines.take(2).map { |line| print_line(line) }.join("\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def print_line(message)
|
23
|
+
message = '' unless message
|
24
|
+
message = message.ljust(WIDTH, ' ')
|
25
|
+
message.split('').take(WIDTH).join('')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'i2c'
|
2
|
+
|
3
|
+
module Tamashii
|
4
|
+
module Agent
|
5
|
+
module Device
|
6
|
+
# :nodoc:
|
7
|
+
class LCD
|
8
|
+
WIDTH = 16
|
9
|
+
|
10
|
+
OP_CHR = 1
|
11
|
+
OP_CMD = 0
|
12
|
+
|
13
|
+
LINES = [
|
14
|
+
0x80,
|
15
|
+
0xC0
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
BACKLIGHT_ON = 0x08
|
19
|
+
BACKLIGHT_OFF = 0x00
|
20
|
+
|
21
|
+
ENABLE = 0b00000100
|
22
|
+
|
23
|
+
PULSE = 0.0005
|
24
|
+
DELAY = 0.0005
|
25
|
+
|
26
|
+
attr_accessor :backlight
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@lcd = I2C.create(Config.lcd_path)
|
30
|
+
@address = Config.lcd_address
|
31
|
+
@backlight = true
|
32
|
+
|
33
|
+
byte(0x33, OP_CMD)
|
34
|
+
byte(0x32, OP_CMD)
|
35
|
+
byte(0x06, OP_CMD)
|
36
|
+
byte(0x0C, OP_CMD)
|
37
|
+
byte(0x28, OP_CMD)
|
38
|
+
byte(0x01, OP_CMD)
|
39
|
+
sleep(DELAY)
|
40
|
+
end
|
41
|
+
|
42
|
+
def print_message(message)
|
43
|
+
lines = message.lines.map{|l| l.delete("\n")}
|
44
|
+
2.times.each { |line| print_line(lines[line], LINES[line]) }
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def backlight_mode
|
50
|
+
return BACKLIGHT_ON if @backlight
|
51
|
+
BACKLIGHT_OFF
|
52
|
+
end
|
53
|
+
|
54
|
+
def print_line(message, line)
|
55
|
+
message = '' unless message
|
56
|
+
message = message.ljust(WIDTH, ' ')
|
57
|
+
byte(line, OP_CMD)
|
58
|
+
WIDTH.times.each { |pos| byte(message[pos].ord, OP_CHR) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def write(bits)
|
62
|
+
@lcd.write(@address, bits)
|
63
|
+
end
|
64
|
+
|
65
|
+
def byte(bits, mode)
|
66
|
+
high = mode | (bits & 0xF0) | backlight_mode
|
67
|
+
low = mode | (bits << 4) & 0xF0 | backlight_mode
|
68
|
+
|
69
|
+
write(high)
|
70
|
+
toggle(high)
|
71
|
+
|
72
|
+
write(low)
|
73
|
+
toggle(low)
|
74
|
+
end
|
75
|
+
|
76
|
+
def toggle(bits)
|
77
|
+
sleep(DELAY)
|
78
|
+
write(bits | ENABLE)
|
79
|
+
sleep(PULSE)
|
80
|
+
write(bits & ~ENABLE)
|
81
|
+
sleep(DELAY)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Tamashii
|
2
|
+
module Agent
|
3
|
+
class Event
|
4
|
+
|
5
|
+
BEEP = 1
|
6
|
+
SYSTEM_COMMAND = 2
|
7
|
+
AUTH_RESULT = 3
|
8
|
+
CARD_DATA = 4
|
9
|
+
LCD_MESSAGE = 5
|
10
|
+
LCD_SET_IDLE_TEXT = 6
|
11
|
+
|
12
|
+
CONNECTION_NOT_READY = 255
|
13
|
+
|
14
|
+
attr_reader :type, :body
|
15
|
+
|
16
|
+
def initialize(type, body)
|
17
|
+
@type = type
|
18
|
+
@body = body
|
19
|
+
self.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
@type == other.type && @body == other.body
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'tamashii/agent/
|
1
|
+
require 'tamashii/agent/event'
|
2
2
|
require 'tamashii/agent/handler/base'
|
3
3
|
|
4
4
|
module Tamashii
|
@@ -6,7 +6,7 @@ module Tamashii
|
|
6
6
|
module Handler
|
7
7
|
class Buzzer < Base
|
8
8
|
def resolve(data)
|
9
|
-
@master.send_event(
|
9
|
+
@master.send_event(Event.new(Event::BEEP, data))
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'tamashii/agent/event'
|
2
|
+
require 'tamashii/agent/handler/base'
|
3
|
+
|
4
|
+
module Tamashii
|
5
|
+
module Agent
|
6
|
+
module Handler
|
7
|
+
class LCD < Base
|
8
|
+
def resolve(data)
|
9
|
+
case type
|
10
|
+
when Type::LCD_MESSAGE
|
11
|
+
@master.send_event(Event.new(Event::LCD_MESSAGE, data))
|
12
|
+
when Type::LCD_SET_IDLE_TEXT
|
13
|
+
@master.send_event(Event.new(Event::LCD_SET_IDLE_TEXT, data))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'tamashii/agent/
|
1
|
+
require 'tamashii/agent/event'
|
2
2
|
require 'tamashii/agent/handler/base'
|
3
3
|
|
4
4
|
module Tamashii
|
@@ -6,7 +6,7 @@ module Tamashii
|
|
6
6
|
module Handler
|
7
7
|
class System < Base
|
8
8
|
def resolve(data)
|
9
|
-
@master.send_event(
|
9
|
+
@master.send_event(Event.new(Event::SYSTEM_COMMAND, type.to_s))
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
|
3
|
+
require 'tamashii/agent/common'
|
4
|
+
require 'tamashii/agent/event'
|
5
|
+
require 'tamashii/agent/adapter/lcd'
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
module Tamashii
|
10
|
+
module Agent
|
11
|
+
class LCD < Component
|
12
|
+
def initialize
|
13
|
+
super
|
14
|
+
load_lcd_device
|
15
|
+
@device_lock = Mutex.new
|
16
|
+
@idle_message = "[Tamashii]\nIdle..."
|
17
|
+
logger.debug "Using LCD instance: #{@lcd.class}"
|
18
|
+
@lcd.print_message("Initializing\nPlease wait...")
|
19
|
+
schedule_to_print_idle
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_lcd_device
|
23
|
+
@lcd = Adapter::LCD.object
|
24
|
+
rescue => e
|
25
|
+
logger.error "Unable to load LCD instance: #{Adapter::LCD.current_class}"
|
26
|
+
logger.error "Use #{Adapter::LCD.fake_class} instead"
|
27
|
+
@lcd = Adapter::LCD.fake_class.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def schedule_to_print_idle(delay = 5)
|
31
|
+
@back_to_idle_task = Concurrent::ScheduledTask.execute(delay) do
|
32
|
+
@device_lock.synchronize do
|
33
|
+
@lcd.print_message(@idle_message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_event(event)
|
39
|
+
case event.type
|
40
|
+
when Event::LCD_MESSAGE
|
41
|
+
logger.debug "Show message: #{event.body}"
|
42
|
+
@back_to_idle_task&.cancel
|
43
|
+
@device_lock.synchronize do
|
44
|
+
@lcd.print_message(event.body)
|
45
|
+
schedule_to_print_idle
|
46
|
+
end
|
47
|
+
when Event::LCD_SET_IDLE_TEXT
|
48
|
+
logger.debug "Idle text set to #{event.body}"
|
49
|
+
@idle_message = event.body
|
50
|
+
@device_lock.synchronize do
|
51
|
+
@lcd.print_message(event.body)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def clear_screen
|
57
|
+
@device_lock.synchronize do
|
58
|
+
@lcd.print_message("")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def clean_up
|
63
|
+
clear_screen
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
@@ -1,6 +1,9 @@
|
|
1
|
+
require 'tamashii/agent/common'
|
1
2
|
require 'tamashii/agent/connection'
|
3
|
+
require 'tamashii/agent/lcd'
|
2
4
|
require 'tamashii/agent/buzzer'
|
3
5
|
require 'tamashii/agent/card_reader'
|
6
|
+
require 'tamashii/agent/event'
|
4
7
|
|
5
8
|
require 'thread'
|
6
9
|
|
@@ -41,6 +44,7 @@ module Tamashii
|
|
41
44
|
@components = {}
|
42
45
|
@components[:connection] = create_component(Connection, self, @host, @port)
|
43
46
|
@components[:buzzer] = create_component(Buzzer)
|
47
|
+
@components[:lcd] = create_component(LCD)
|
44
48
|
@components[:card_reader] = create_component(CardReader, self)
|
45
49
|
end
|
46
50
|
|
@@ -53,12 +57,12 @@ module Tamashii
|
|
53
57
|
end
|
54
58
|
|
55
59
|
# override
|
56
|
-
def process_event(
|
60
|
+
def process_event(event)
|
57
61
|
super
|
58
|
-
case
|
59
|
-
when
|
60
|
-
logger.info "System command code: #{
|
61
|
-
case
|
62
|
+
case event.type
|
63
|
+
when Event::SYSTEM_COMMAND
|
64
|
+
logger.info "System command code: #{event.body}"
|
65
|
+
case event.body.to_i
|
62
66
|
when Tamashii::Type::REBOOT
|
63
67
|
system_reboot
|
64
68
|
when Tamashii::Type::POWEROFF
|
@@ -68,30 +72,35 @@ module Tamashii
|
|
68
72
|
when Tamashii::Type::UPDATE
|
69
73
|
system_update
|
70
74
|
end
|
71
|
-
when
|
72
|
-
broadcast_event(
|
75
|
+
when Event::CONNECTION_NOT_READY
|
76
|
+
broadcast_event(Event.new(Event::BEEP, "error"))
|
73
77
|
else
|
74
|
-
broadcast_event(
|
78
|
+
broadcast_event(event)
|
75
79
|
end
|
76
80
|
end
|
77
81
|
|
82
|
+
def show_message(message)
|
83
|
+
logger.info message
|
84
|
+
broadcast_event(Event.new(Event::LCD_MESSAGE, message))
|
85
|
+
end
|
86
|
+
|
78
87
|
def system_reboot
|
79
|
-
|
88
|
+
show_message "Rebooting"
|
80
89
|
system("reboot &")
|
81
90
|
end
|
82
91
|
|
83
92
|
def system_poweroff
|
84
|
-
|
93
|
+
show_message "Powering Off"
|
85
94
|
system("poweroff &")
|
86
95
|
end
|
87
96
|
|
88
97
|
def system_restart
|
89
|
-
|
98
|
+
show_message "Restarting"
|
90
99
|
system("systemctl restart tamashii-agent.service &")
|
91
100
|
end
|
92
101
|
|
93
102
|
def system_update
|
94
|
-
|
103
|
+
show_message("Updating")
|
95
104
|
system("gem update tamashii-agent")
|
96
105
|
system_restart
|
97
106
|
end
|
@@ -105,9 +114,9 @@ module Tamashii
|
|
105
114
|
end
|
106
115
|
|
107
116
|
|
108
|
-
def broadcast_event(
|
117
|
+
def broadcast_event(event)
|
109
118
|
@components.each_value do |c|
|
110
|
-
c.send_event(
|
119
|
+
c.send_event(event)
|
111
120
|
end
|
112
121
|
end
|
113
122
|
end
|
data/lib/tamashii/agent.rb
CHANGED
data/tamashii-agent.gemspec
CHANGED
@@ -35,12 +35,15 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.add_development_dependency "simplecov"
|
36
36
|
spec.add_development_dependency "guard"
|
37
37
|
spec.add_development_dependency "guard-rspec"
|
38
|
+
spec.add_development_dependency "pry"
|
38
39
|
|
39
40
|
|
40
|
-
spec.add_runtime_dependency "tamashii-common"
|
41
|
+
spec.add_runtime_dependency "tamashii-common"
|
41
42
|
spec.add_runtime_dependency "websocket-driver"
|
42
43
|
spec.add_runtime_dependency "nio4r"
|
43
44
|
spec.add_runtime_dependency "pi_piper"
|
44
45
|
spec.add_runtime_dependency "mfrc522"
|
46
|
+
spec.add_runtime_dependency "i2c"
|
45
47
|
spec.add_runtime_dependency "aasm"
|
48
|
+
spec.add_runtime_dependency "concurrent-ruby"
|
46
49
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tamashii-agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 蒼時弦也
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2017-
|
13
|
+
date: 2017-07-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -96,6 +96,20 @@ dependencies:
|
|
96
96
|
- - ">="
|
97
97
|
- !ruby/object:Gem::Version
|
98
98
|
version: '0'
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: pry
|
101
|
+
requirement: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
type: :development
|
107
|
+
prerelease: false
|
108
|
+
version_requirements: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
99
113
|
- !ruby/object:Gem::Dependency
|
100
114
|
name: tamashii-common
|
101
115
|
requirement: !ruby/object:Gem::Requirement
|
@@ -166,6 +180,20 @@ dependencies:
|
|
166
180
|
- - ">="
|
167
181
|
- !ruby/object:Gem::Version
|
168
182
|
version: '0'
|
183
|
+
- !ruby/object:Gem::Dependency
|
184
|
+
name: i2c
|
185
|
+
requirement: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
type: :runtime
|
191
|
+
prerelease: false
|
192
|
+
version_requirements: !ruby/object:Gem::Requirement
|
193
|
+
requirements:
|
194
|
+
- - ">="
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: '0'
|
169
197
|
- !ruby/object:Gem::Dependency
|
170
198
|
name: aasm
|
171
199
|
requirement: !ruby/object:Gem::Requirement
|
@@ -180,6 +208,20 @@ dependencies:
|
|
180
208
|
- - ">="
|
181
209
|
- !ruby/object:Gem::Version
|
182
210
|
version: '0'
|
211
|
+
- !ruby/object:Gem::Dependency
|
212
|
+
name: concurrent-ruby
|
213
|
+
requirement: !ruby/object:Gem::Requirement
|
214
|
+
requirements:
|
215
|
+
- - ">="
|
216
|
+
- !ruby/object:Gem::Version
|
217
|
+
version: '0'
|
218
|
+
type: :runtime
|
219
|
+
prerelease: false
|
220
|
+
version_requirements: !ruby/object:Gem::Requirement
|
221
|
+
requirements:
|
222
|
+
- - ">="
|
223
|
+
- !ruby/object:Gem::Version
|
224
|
+
version: '0'
|
183
225
|
description: The agent module for RubyConfTW checkin system.
|
184
226
|
email:
|
185
227
|
- elct9620@frost.tw
|
@@ -205,6 +247,7 @@ files:
|
|
205
247
|
- lib/tamashii/agent/adapter/base.rb
|
206
248
|
- lib/tamashii/agent/adapter/buzzer.rb
|
207
249
|
- lib/tamashii/agent/adapter/card_reader.rb
|
250
|
+
- lib/tamashii/agent/adapter/lcd.rb
|
208
251
|
- lib/tamashii/agent/buzzer.rb
|
209
252
|
- lib/tamashii/agent/card_reader.rb
|
210
253
|
- lib/tamashii/agent/common.rb
|
@@ -214,16 +257,18 @@ files:
|
|
214
257
|
- lib/tamashii/agent/connection.rb
|
215
258
|
- lib/tamashii/agent/device/fake_buzzer.rb
|
216
259
|
- lib/tamashii/agent/device/fake_card_reader.rb
|
260
|
+
- lib/tamashii/agent/device/fake_lcd.rb
|
261
|
+
- lib/tamashii/agent/device/lcd.rb
|
217
262
|
- lib/tamashii/agent/device/pi_buzzer.rb
|
263
|
+
- lib/tamashii/agent/event.rb
|
218
264
|
- lib/tamashii/agent/handler.rb
|
219
265
|
- lib/tamashii/agent/handler/base.rb
|
220
266
|
- lib/tamashii/agent/handler/buzzer.rb
|
221
|
-
- lib/tamashii/agent/handler/
|
267
|
+
- lib/tamashii/agent/handler/lcd.rb
|
268
|
+
- lib/tamashii/agent/handler/remote_response.rb
|
222
269
|
- lib/tamashii/agent/handler/system.rb
|
270
|
+
- lib/tamashii/agent/lcd.rb
|
223
271
|
- lib/tamashii/agent/master.rb
|
224
|
-
- lib/tamashii/agent/request_pool.rb
|
225
|
-
- lib/tamashii/agent/request_pool/request.rb
|
226
|
-
- lib/tamashii/agent/request_pool/response.rb
|
227
272
|
- lib/tamashii/agent/version.rb
|
228
273
|
- tamashii-agent.gemspec
|
229
274
|
homepage: https://github.com/5xruby/tamashii-agent
|
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'tamashii/agent/handler/base'
|
2
|
-
require 'tamashii/agent/request_pool'
|
3
|
-
|
4
|
-
module Tamashii
|
5
|
-
module Agent
|
6
|
-
module Handler
|
7
|
-
class RequestPoolResponse < Base
|
8
|
-
def resolve(data)
|
9
|
-
@connection.request_pool.add_response(RequestPool::Response.new(self.type, data))
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
module Tamashii
|
3
|
-
module Agent
|
4
|
-
class RequestPool
|
5
|
-
class Request
|
6
|
-
attr_accessor :id
|
7
|
-
attr_accessor :ev_type
|
8
|
-
attr_accessor :ev_body
|
9
|
-
attr_accessor :state
|
10
|
-
|
11
|
-
STATE_PENDING = :pending
|
12
|
-
STATE_SENT = :sent
|
13
|
-
|
14
|
-
def initialize(ev_type, ev_body, id)
|
15
|
-
@ev_type = ev_type
|
16
|
-
@ev_body = ev_body
|
17
|
-
@id = id
|
18
|
-
@state = STATE_PENDING
|
19
|
-
end
|
20
|
-
|
21
|
-
def wrap_body
|
22
|
-
{
|
23
|
-
id: @id,
|
24
|
-
ev_body: @ev_body
|
25
|
-
}.to_json
|
26
|
-
end
|
27
|
-
|
28
|
-
def sent!
|
29
|
-
@state = STATE_SENT
|
30
|
-
end
|
31
|
-
|
32
|
-
def sent?
|
33
|
-
@state == STATE_SENT
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
module Tamashii
|
3
|
-
module Agent
|
4
|
-
class RequestPool
|
5
|
-
class Response
|
6
|
-
attr_accessor :ev_type, :ev_body, :id
|
7
|
-
|
8
|
-
def initialize(ev_type, wrapped_body)
|
9
|
-
@ev_type = ev_type
|
10
|
-
data = JSON.parse(wrapped_body)
|
11
|
-
@id = data["id"]
|
12
|
-
@ev_body = data["ev_body"]
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,80 +0,0 @@
|
|
1
|
-
require 'tamashii/agent/common'
|
2
|
-
require 'tamashii/agent/request_pool/request'
|
3
|
-
require 'tamashii/agent/request_pool/response'
|
4
|
-
|
5
|
-
|
6
|
-
module Tamashii
|
7
|
-
module Agent
|
8
|
-
class RequestPool
|
9
|
-
include Common::Loggable
|
10
|
-
def initialize
|
11
|
-
@pool = {}
|
12
|
-
@handlers = {}
|
13
|
-
end
|
14
|
-
|
15
|
-
def set_handler(sym, method)
|
16
|
-
@handlers[sym] = method
|
17
|
-
end
|
18
|
-
|
19
|
-
def call_handler(sym, *args)
|
20
|
-
if handle?(sym)
|
21
|
-
@handlers[sym].call(*args)
|
22
|
-
else
|
23
|
-
logger.warn "WARN: un-handled event: #{sym}"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def handle?(sym)
|
28
|
-
@handlers.has_key? sym
|
29
|
-
end
|
30
|
-
|
31
|
-
def add_request(req, timedout = 3)
|
32
|
-
@pool[req.id] = {req: req, timestamp: Time.now, timedout: timedout}
|
33
|
-
try_send_request(req)
|
34
|
-
end
|
35
|
-
|
36
|
-
def add_response(res)
|
37
|
-
# find the same id
|
38
|
-
req_data = @pool[res.id]
|
39
|
-
if req_data
|
40
|
-
@pool.delete(res.id)
|
41
|
-
call_handler(:request_meet, req_data[:req], res)
|
42
|
-
else
|
43
|
-
# unmatched response
|
44
|
-
# discard
|
45
|
-
logger.warn "WARN: un-matched response (id=#{res.id}): #{res.inspect}"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def update
|
50
|
-
process_pending
|
51
|
-
check_timedout
|
52
|
-
end
|
53
|
-
|
54
|
-
def check_timedout
|
55
|
-
now = Time.now
|
56
|
-
@pool.each do |id, req_data|
|
57
|
-
if now - req_data[:timestamp] >= req_data[:timedout]
|
58
|
-
# timedout
|
59
|
-
@pool.delete(id)
|
60
|
-
call_handler(:request_timedout, req_data[:req])
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def process_pending
|
66
|
-
@pool.each_value do |data|
|
67
|
-
try_send_request(data[:req]) unless data[:req].sent?
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def try_send_request(req)
|
72
|
-
if handle?(:send_request)
|
73
|
-
req.sent! if call_handler(:send_request, req)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
|