smartware 0.2.7 → 0.2.8
Sign up to get free protection for your applications and to get access to all the features.
- data/config/smartware.yml.sample +7 -0
- data/lib/smartware/clients/card_reader.rb +60 -0
- data/lib/smartware/drivers/card_reader/dummy.rb +71 -0
- data/lib/smartware/drivers/card_reader/ict3_k5.rb +466 -0
- data/lib/smartware/drivers/modem/standard.rb +20 -13
- data/lib/smartware/drivers/printer/dummy.rb +21 -1
- data/lib/smartware/interfaces/card_reader.rb +83 -0
- data/lib/smartware/service.rb +1 -1
- data/lib/smartware/version.rb +1 -1
- data/lib/smartware.rb +3 -0
- data/smartware.gemspec +1 -0
- metadata +22 -2
data/config/smartware.yml.sample
CHANGED
@@ -5,6 +5,13 @@ interfaces:
|
|
5
5
|
- name: Modem
|
6
6
|
uri: druby://localhost:6002
|
7
7
|
driver: Dummy
|
8
|
+
- name: Watchdog
|
9
|
+
uri: druby://localhost:6003
|
10
|
+
driver: Dummy
|
11
|
+
- name: CardReader
|
12
|
+
uri: druby://localhost:6004
|
13
|
+
driver: Dummy
|
8
14
|
- name: Printer
|
9
15
|
uri: druby://localhost:6005
|
10
16
|
driver: Dummy
|
17
|
+
connection_timeout: 60
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'drb'
|
2
|
+
|
3
|
+
module Smartware
|
4
|
+
module Client
|
5
|
+
|
6
|
+
module CardReader
|
7
|
+
|
8
|
+
DRb.start_service
|
9
|
+
@device = DRbObject.new_with_uri('druby://localhost:6004')
|
10
|
+
|
11
|
+
def self.open(limit_min = nil, limit_max = nil)
|
12
|
+
@device.open_session(limit_min, limit_max)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.close
|
16
|
+
@device.close_session
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.status
|
20
|
+
@device.status
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.error
|
24
|
+
@device.error
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.model
|
28
|
+
@device.model
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.version
|
32
|
+
@device.version
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.card_inserted?
|
36
|
+
@device.card_inserted?
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.start_accepting
|
40
|
+
@device.start_accepting
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.stop_accepting
|
44
|
+
@device.stop_accepting
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.eject
|
48
|
+
@device.eject
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.capture
|
52
|
+
@device.capture
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.read_magstrip
|
56
|
+
@device.read_magstrip
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Smartware
|
2
|
+
module Driver
|
3
|
+
module CardReader
|
4
|
+
class Dummy
|
5
|
+
def initialize(config)
|
6
|
+
@accepting = false
|
7
|
+
@state = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def model
|
11
|
+
"Dummy card reader"
|
12
|
+
end
|
13
|
+
|
14
|
+
def version
|
15
|
+
""
|
16
|
+
end
|
17
|
+
|
18
|
+
def ready?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def accepting?
|
23
|
+
@accepting
|
24
|
+
end
|
25
|
+
|
26
|
+
def accepting=(accepting)
|
27
|
+
@state = :accepting if accepting
|
28
|
+
@accepting = accepting
|
29
|
+
end
|
30
|
+
|
31
|
+
def eject
|
32
|
+
@state = :eject
|
33
|
+
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def capture
|
38
|
+
@state = :eject
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def status
|
43
|
+
case @state
|
44
|
+
when nil
|
45
|
+
:ready
|
46
|
+
|
47
|
+
when :accepting
|
48
|
+
@state = :inserted
|
49
|
+
:card_at_gate
|
50
|
+
|
51
|
+
when :inserted
|
52
|
+
:card_inserted
|
53
|
+
|
54
|
+
when :eject
|
55
|
+
@state = nil
|
56
|
+
:card_at_gate
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def read_magstrip
|
61
|
+
[
|
62
|
+
"B4154000000000000^IVANOV/IVAN^1501101000",
|
63
|
+
"4154000000000000=1501101000",
|
64
|
+
nil,
|
65
|
+
nil
|
66
|
+
]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,466 @@
|
|
1
|
+
require "serialport"
|
2
|
+
require "digest/crc16_ccitt"
|
3
|
+
|
4
|
+
module Smartware
|
5
|
+
|
6
|
+
module Driver
|
7
|
+
module CardReader
|
8
|
+
class ICT3K5
|
9
|
+
ERRORS = {
|
10
|
+
0x00 => Interface::CardReader::COMMUNICATION_ERROR, # A given command code is unidentified
|
11
|
+
0x01 => Interface::CardReader::COMMUNICATION_ERROR, # Parameter is not correct
|
12
|
+
0x02 => Interface::CardReader::COMMUNICATION_ERROR, # Command execution is impossible
|
13
|
+
0x03 => Interface::CardReader::COMMUNICATION_ERROR, # Function is not implemented
|
14
|
+
0x04 => Interface::CardReader::COMMUNICATION_ERROR, # Command data error
|
15
|
+
0x06 => Interface::CardReader::COMMUNICATION_ERROR, # Key for decrypting is not received
|
16
|
+
0x10 => Interface::CardReader::CARD_JAM_ERROR,
|
17
|
+
0x11 => Interface::CardReader::CARD_ERROR, # Shutter error
|
18
|
+
0x13 => Interface::CardReader::CARD_ERROR, # Long card
|
19
|
+
0x14 => Interface::CardReader::CARD_ERROR, # Short card
|
20
|
+
0x15 => Interface::CardReader::HARDWARE_ERROR, # Flash Memory Parameter Area CRC error
|
21
|
+
0x16 => Interface::CardReader::CARD_ERROR, # Card position move
|
22
|
+
0x17 => Interface::CardReader::CARD_JAM_ERROR, # Jam error at retrieve
|
23
|
+
0x18 => Interface::CardReader::CARD_ERROR, # Two card error
|
24
|
+
0x20 => Interface::CardReader::MAG_READ_ERROR, # Parity error
|
25
|
+
0x21 => Interface::CardReader::MAG_READ_ERROR, # Sentinel error
|
26
|
+
0x23 => Interface::CardReader::MAG_READ_ERROR, # No data contents
|
27
|
+
0x24 => Interface::CardReader::MAG_READ_ERROR, # No stripe
|
28
|
+
0x30 => Interface::CardReader::HARDWARE_ERROR, # Power loss
|
29
|
+
0x31 => Interface::CardReader::COMMUNICATION_ERROR, # DTR low
|
30
|
+
0x39 => Interface::CardReader::HARDWARE_ERROR, # Fan failure
|
31
|
+
0x40 => Interface::CardReader::CARD_ERROR, # Pull out error
|
32
|
+
0x43 => Interface::CardReader::CARD_ERROR, # IC positioning error
|
33
|
+
0x50 => Interface::CardReader::HARDWARE_ERROR, # Capture counter overflow
|
34
|
+
0x60 => Interface::CardReader::ICC_ERROR, # Abnormal VCC condition
|
35
|
+
0x61 => Interface::CardReader::ICC_ERROR, # ATR error
|
36
|
+
0x62 => Interface::CardReader::ICC_ERROR, # Invalid ATR error
|
37
|
+
0x63 => Interface::CardReader::ICC_ERROR, # No response
|
38
|
+
0x64 => Interface::CardReader::ICC_ERROR, # Communication error
|
39
|
+
0x65 => Interface::CardReader::ICC_ERROR, # Not activated
|
40
|
+
0x66 => Interface::CardReader::ICC_ERROR, # Unsupported card
|
41
|
+
0x69 => Interface::CardReader::ICC_ERROR, # Unsupported card
|
42
|
+
0x73 => Interface::CardReader::HARDWARE_ERROR, # EEPROM error
|
43
|
+
0xB0 => Interface::CardReader::COMMUNICATION_ERROR # Not received initialize
|
44
|
+
}
|
45
|
+
|
46
|
+
class CRC < Digest::CRC16CCITT
|
47
|
+
INIT_CRC = 0x0000
|
48
|
+
end
|
49
|
+
|
50
|
+
class CommandResponse
|
51
|
+
def initialize(response)
|
52
|
+
@response = response
|
53
|
+
end
|
54
|
+
|
55
|
+
def error?
|
56
|
+
@response.nil? || (@response[0] != "P" && @response[0] != "N")
|
57
|
+
end
|
58
|
+
|
59
|
+
def positive?
|
60
|
+
@response[0] == "P"
|
61
|
+
end
|
62
|
+
|
63
|
+
def negative?
|
64
|
+
@response[0] == "N"
|
65
|
+
end
|
66
|
+
|
67
|
+
def response
|
68
|
+
@response[1..-1]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
STX = 0xF2
|
73
|
+
ACK = 0x06
|
74
|
+
NAK = 0x15
|
75
|
+
|
76
|
+
def initialize(config)
|
77
|
+
@config = config
|
78
|
+
|
79
|
+
@port = SerialPort.new(config["port"], 115200, 8, 1, SerialPort::EVEN)
|
80
|
+
|
81
|
+
@state = :not_ready
|
82
|
+
@event_read, @event_write = IO.pipe
|
83
|
+
@read_buf = ""
|
84
|
+
@write_buf = ""
|
85
|
+
@command_queue = Queue.new
|
86
|
+
@status_mutex = Mutex.new
|
87
|
+
@active_command = nil
|
88
|
+
@active_block = nil
|
89
|
+
@start_time = nil
|
90
|
+
@retries = nil
|
91
|
+
@ready = false
|
92
|
+
@accepting = false
|
93
|
+
|
94
|
+
Thread.new &method(:dispatch)
|
95
|
+
end
|
96
|
+
|
97
|
+
def model
|
98
|
+
"ICT3K5"
|
99
|
+
end
|
100
|
+
|
101
|
+
def version
|
102
|
+
""
|
103
|
+
end
|
104
|
+
|
105
|
+
def ready?
|
106
|
+
@status_mutex.synchronize { @ready }
|
107
|
+
end
|
108
|
+
|
109
|
+
def accepting?
|
110
|
+
@status_mutex.synchronize { @accepting }
|
111
|
+
end
|
112
|
+
|
113
|
+
def accepting=(accepting)
|
114
|
+
if accepting
|
115
|
+
set_led :green
|
116
|
+
resp = command 0x3A, 0x30
|
117
|
+
else
|
118
|
+
set_led :red
|
119
|
+
resp = command 0x3A, 0x31
|
120
|
+
end
|
121
|
+
|
122
|
+
translate_response resp
|
123
|
+
|
124
|
+
@status_mutex.synchronize { @accepting = accepting }
|
125
|
+
end
|
126
|
+
|
127
|
+
def eject
|
128
|
+
resp = command 0x33, 0x30
|
129
|
+
translate_response resp
|
130
|
+
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
def capture
|
135
|
+
resp = command 0x33, 0x31
|
136
|
+
translate_response resp
|
137
|
+
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
def status
|
142
|
+
return :not_ready if !ready?
|
143
|
+
|
144
|
+
resp = command 0x31, 0x30
|
145
|
+
translate_response resp
|
146
|
+
|
147
|
+
case resp.response[2..3]
|
148
|
+
when "00"
|
149
|
+
:ready
|
150
|
+
|
151
|
+
when "01"
|
152
|
+
:card_at_gate
|
153
|
+
|
154
|
+
when "02"
|
155
|
+
:card_inserted
|
156
|
+
|
157
|
+
else
|
158
|
+
:not_ready
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def read_magstrip
|
163
|
+
[ 0x31, 0x32, 0x33, 0x34 ].map! do |track|
|
164
|
+
resp = command 0x36, track
|
165
|
+
translate_response resp if resp.error?
|
166
|
+
|
167
|
+
if resp.positive?
|
168
|
+
resp.response[4..-1]
|
169
|
+
else
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def command(*args, &block)
|
178
|
+
if block_given?
|
179
|
+
@command_queue.push [ args, block ]
|
180
|
+
@event_write.write "\x01"
|
181
|
+
else
|
182
|
+
queue = Queue.new
|
183
|
+
command(*args) { |response| queue.push response }
|
184
|
+
queue.pop
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def translate_response(response)
|
189
|
+
if response.error?
|
190
|
+
raise Interface::CardReader::CardReaderError.new(
|
191
|
+
"communication error",
|
192
|
+
Interface::CardReader::COMMUNICATION_ERROR
|
193
|
+
)
|
194
|
+
elsif response.negative?
|
195
|
+
error = response.response[0..1].to_i(16)
|
196
|
+
if ERRORS.include? error
|
197
|
+
translated_error = ERRORS[error]
|
198
|
+
else
|
199
|
+
translated_error = Interface::CardReader::HARDWARE_ERROR
|
200
|
+
end
|
201
|
+
|
202
|
+
raise Interface::CardReader::CardReaderError.new(
|
203
|
+
"command failed: #{error}",
|
204
|
+
translated_error
|
205
|
+
)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def set_led(color)
|
210
|
+
code = nil
|
211
|
+
|
212
|
+
case color
|
213
|
+
when :off
|
214
|
+
code = 0x30
|
215
|
+
|
216
|
+
when :green
|
217
|
+
code = 0x31
|
218
|
+
|
219
|
+
when :red
|
220
|
+
code = 0x32
|
221
|
+
|
222
|
+
when :orange
|
223
|
+
code = 0x33
|
224
|
+
end
|
225
|
+
|
226
|
+
command(0x35, code) do |resp|
|
227
|
+
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def complete_init(response)
|
232
|
+
if response.error?
|
233
|
+
Smartware::Logging.logger.info "ICT3K5: initialization error"
|
234
|
+
elsif response.negative?
|
235
|
+
Smartware::Logging.logger.info "ICT3K5: initialization negative: #{response.response}"
|
236
|
+
else
|
237
|
+
Smartware::Logging.logger.info "ICT3K5: initialization: #{response.response}"
|
238
|
+
@status_mutex.synchronize { @ready = true }
|
239
|
+
set_led :red
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def run_periodic
|
244
|
+
if @start_time.nil?
|
245
|
+
elapsed = nil
|
246
|
+
else
|
247
|
+
elapsed = Time.now - @start_time
|
248
|
+
end
|
249
|
+
|
250
|
+
case @state
|
251
|
+
when :not_ready
|
252
|
+
if @port.dsr == 1
|
253
|
+
|
254
|
+
Smartware::Logging.logger.info "ICT3K5: DSR active, initializing"
|
255
|
+
|
256
|
+
@state = :accepting
|
257
|
+
|
258
|
+
flushed = []
|
259
|
+
|
260
|
+
command 0x30, # Initialize
|
261
|
+
0x30, # Eject card,
|
262
|
+
0x33, 0x32, 0x34, 0x31, 0x30, # Compatibility nonsense
|
263
|
+
0x30, # Power down card
|
264
|
+
0x31, # Identify reader
|
265
|
+
0x30, # Eject card on DTR low
|
266
|
+
0x30, # Turn off capture counter
|
267
|
+
&method(:complete_init)
|
268
|
+
|
269
|
+
flushed.each do |(command, block)|
|
270
|
+
block.call CommandResponse.new(error: "timeout")
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
when :waiting_ack
|
275
|
+
if elapsed > 0.3
|
276
|
+
Smartware::Logging.logger.info "ICT3K5: ACK timeout"
|
277
|
+
retry_or_fail
|
278
|
+
end
|
279
|
+
|
280
|
+
when :reading_response
|
281
|
+
if elapsed > 20
|
282
|
+
Smartware::Logging.logger.info "ICT3K5: command timeout"
|
283
|
+
retry_or_fail
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
if @port.dsr == 0
|
288
|
+
@status_mutex.synchronize do
|
289
|
+
@ready = false
|
290
|
+
@accepting = false
|
291
|
+
end
|
292
|
+
|
293
|
+
if !@active_command.nil?
|
294
|
+
Smartware::Logging.logger.info "ICT3K5: DSR fall"
|
295
|
+
|
296
|
+
fail_command
|
297
|
+
end
|
298
|
+
|
299
|
+
until @command_queue.empty?
|
300
|
+
unpacked, block = @command_queue.pop(true)
|
301
|
+
|
302
|
+
block.call CommandResponse.new(nil)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def start_execution
|
308
|
+
@event_read.readbyte
|
309
|
+
|
310
|
+
unpacked, @active_block = @command_queue.pop
|
311
|
+
@active_command = frame_command(unpacked)
|
312
|
+
|
313
|
+
@write_buf << @active_command
|
314
|
+
@start_time = Time.now
|
315
|
+
@state = :waiting_ack
|
316
|
+
@retries = 8
|
317
|
+
end
|
318
|
+
|
319
|
+
def complete_command(response)
|
320
|
+
block = @active_block
|
321
|
+
|
322
|
+
@state = :accepting
|
323
|
+
@active_command = nil
|
324
|
+
@active_block = nil
|
325
|
+
|
326
|
+
Smartware::Logging.logger.info "ICT3K5: completing command"
|
327
|
+
|
328
|
+
block.call CommandResponse.new(response)
|
329
|
+
end
|
330
|
+
|
331
|
+
def fail_command
|
332
|
+
block = @active_block
|
333
|
+
|
334
|
+
@state = :accepting
|
335
|
+
@active_command = nil
|
336
|
+
@active_block = nil
|
337
|
+
|
338
|
+
Smartware::Logging.logger.info "ICT3K5: failing command"
|
339
|
+
|
340
|
+
block.call CommandResponse.new(nil)
|
341
|
+
end
|
342
|
+
|
343
|
+
def retry_or_fail
|
344
|
+
if @retries == 0
|
345
|
+
fail_command
|
346
|
+
else
|
347
|
+
@retries -= 1
|
348
|
+
@start_time = Time.now
|
349
|
+
@state = :waiting_ack
|
350
|
+
@write_buf << @active_command
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def frame_command(bytes)
|
355
|
+
data = [ STX, bytes.length + 1 ].pack("Cn")
|
356
|
+
data << "C"
|
357
|
+
data << bytes.pack("C*")
|
358
|
+
|
359
|
+
crc = CRC.new
|
360
|
+
crc << data
|
361
|
+
data << [ crc.checksum ].pack("n")
|
362
|
+
|
363
|
+
data
|
364
|
+
end
|
365
|
+
|
366
|
+
def read_chunk
|
367
|
+
@read_buf << @port.read_nonblock(8192)
|
368
|
+
rescue IO::WaitReadable
|
369
|
+
end
|
370
|
+
|
371
|
+
def write_chunk
|
372
|
+
bytes = @port.write_nonblock @write_buf
|
373
|
+
@write_buf.slice! 0, bytes
|
374
|
+
|
375
|
+
rescue IO::WaitWritable
|
376
|
+
end
|
377
|
+
|
378
|
+
def handle_input
|
379
|
+
until @read_buf.empty? do
|
380
|
+
case @state
|
381
|
+
when :waiting_ack
|
382
|
+
initial_byte = @read_buf.slice!(0, 1).ord
|
383
|
+
|
384
|
+
case initial_byte
|
385
|
+
when ACK
|
386
|
+
Smartware::Logging.logger.info "ICT3K5: ACK"
|
387
|
+
|
388
|
+
@state = :reading_response
|
389
|
+
@start_time = Time.now
|
390
|
+
|
391
|
+
when NAK
|
392
|
+
Smartware::Logging.logger.info "ICT3K5: NAK"
|
393
|
+
|
394
|
+
retry_or_fail
|
395
|
+
|
396
|
+
else
|
397
|
+
Smartware::Logging.logger.info "ICT3K5: garbage on line: #{initial_byte}"
|
398
|
+
end
|
399
|
+
|
400
|
+
when :reading_response
|
401
|
+
break if @read_buf.length < 5
|
402
|
+
|
403
|
+
leading_byte, length = @read_buf[0..2].unpack("Cn")
|
404
|
+
if leading_byte != STX
|
405
|
+
Smartware::Logging.logger.info "ICT3K5: garbage on line: #{leading_byte}"
|
406
|
+
|
407
|
+
@read_buf.slice! 0, 1
|
408
|
+
next
|
409
|
+
end
|
410
|
+
|
411
|
+
full_length = 5 + length
|
412
|
+
|
413
|
+
break if @read_buf.length < full_length
|
414
|
+
|
415
|
+
message = @read_buf.slice! 0, full_length
|
416
|
+
sum, = message.slice!(full_length - 2, 2).unpack("n")
|
417
|
+
crc = CRC.new
|
418
|
+
crc << message
|
419
|
+
if sum == crc.checksum
|
420
|
+
Smartware::Logging.logger.info "ICT3K5: message checksum ok, ACK and process"
|
421
|
+
@write_buf << ACK.chr
|
422
|
+
complete_command message[3..-1]
|
423
|
+
else
|
424
|
+
Smartware::Logging.logger.info "ICT3K5: message checksum invalid, NAK"
|
425
|
+
@write_buf << NAK.chr
|
426
|
+
end
|
427
|
+
|
428
|
+
else
|
429
|
+
break
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def dispatch
|
435
|
+
loop do
|
436
|
+
begin
|
437
|
+
run_periodic
|
438
|
+
|
439
|
+
read_set = [ @port ]
|
440
|
+
write_set = []
|
441
|
+
|
442
|
+
read_set << @event_read if @state == :accepting
|
443
|
+
write_set << @port unless @write_buf.empty?
|
444
|
+
|
445
|
+
read_set, write_set, = IO.select read_set, write_set, [], 1
|
446
|
+
|
447
|
+
unless read_set.nil?
|
448
|
+
start_execution if read_set.include? @event_read
|
449
|
+
read_chunk if read_set.include? @port
|
450
|
+
write_chunk if write_set.include? @port
|
451
|
+
end
|
452
|
+
|
453
|
+
handle_input
|
454
|
+
rescue => e
|
455
|
+
Smartware::Logging.logger.error "Error in ICT3K5 dispatch:"
|
456
|
+
Smartware::Logging.logger.error e.to_s
|
457
|
+
e.backtrace.each do |line|
|
458
|
+
Smartware::Logging.logger.error line
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
@@ -9,7 +9,14 @@ module Smartware
|
|
9
9
|
attr_reader :error, :model, :balance, :version
|
10
10
|
|
11
11
|
def initialize(config)
|
12
|
-
@
|
12
|
+
@port = config["port"]
|
13
|
+
@balance_ussd = config["balance_ussd"]
|
14
|
+
@status_channel_id = config["status_channel"].to_i
|
15
|
+
@ppp_channel_id = config["ppp_channel"].to_i
|
16
|
+
@poll_interval = config["poll_interval"].to_i
|
17
|
+
@balance_interval = config["balance_interval"].to_i
|
18
|
+
@apn = config["apn"]
|
19
|
+
|
13
20
|
@state = :closed
|
14
21
|
@error = Interface::Modem::MODEM_NOT_AVAILABLE
|
15
22
|
@mux = nil
|
@@ -18,7 +25,7 @@ module Smartware
|
|
18
25
|
@model = "GSM modem"
|
19
26
|
@version = ""
|
20
27
|
@signal = "+CSQ: 99,99"
|
21
|
-
@
|
28
|
+
@balance_timer = 0
|
22
29
|
@balance = nil
|
23
30
|
@ppp_state = :stopped
|
24
31
|
@ppp_pid = nil
|
@@ -70,13 +77,13 @@ module Smartware
|
|
70
77
|
Smartware::Logging.logger.info "trying to open modem"
|
71
78
|
|
72
79
|
begin
|
73
|
-
@mux = CMUX::MUX.new @
|
80
|
+
@mux = CMUX::MUX.new @port
|
74
81
|
@state = :open
|
75
|
-
@status_channel = @mux.allocate(@
|
82
|
+
@status_channel = @mux.allocate(@status_channel_id).open
|
76
83
|
@chatter = CMUX::ModemChatter.new @status_channel
|
77
84
|
@chatter.subscribe "CUSD", self
|
78
85
|
@error = nil
|
79
|
-
@
|
86
|
+
@balance_timer = 0
|
80
87
|
Smartware::Logging.logger.info "modem ready"
|
81
88
|
rescue => e
|
82
89
|
close_modem "unable to open modem: #{e}"
|
@@ -103,15 +110,15 @@ module Smartware
|
|
103
110
|
end
|
104
111
|
|
105
112
|
if modem_works
|
106
|
-
if
|
107
|
-
@
|
113
|
+
if !@balance_ussd.nil? && @balance_timer == 0
|
114
|
+
@balance_timer = @balance_interval
|
108
115
|
begin
|
109
|
-
@chatter.command("+CUSD=1,\"#{@
|
116
|
+
@chatter.command("+CUSD=1,\"#{@balance_ussd}\",15", 1)
|
110
117
|
rescue => e
|
111
118
|
close_modem "USSD request failed: #{e}"
|
112
119
|
end
|
113
120
|
else
|
114
|
-
@
|
121
|
+
@balance_timer -= 1
|
115
122
|
end
|
116
123
|
else
|
117
124
|
close_modem "modem is not responding"
|
@@ -125,11 +132,11 @@ module Smartware
|
|
125
132
|
if @state == :open
|
126
133
|
Smartware::Logging.logger.info "trying to start pppd"
|
127
134
|
begin
|
128
|
-
@ppp_channel = @mux.allocate @
|
135
|
+
@ppp_channel = @mux.allocate @ppp_channel_id
|
129
136
|
|
130
137
|
@ppp_pid = Process.spawn "smartware-ppp-helper",
|
131
138
|
@ppp_channel.device,
|
132
|
-
@
|
139
|
+
@apn
|
133
140
|
@ppp_state = :running
|
134
141
|
|
135
142
|
Smartware::Logging.logger.info "started pppd, PID #{@ppp_pid}"
|
@@ -171,12 +178,12 @@ module Smartware
|
|
171
178
|
def wait_for_event
|
172
179
|
if @state == :open
|
173
180
|
begin
|
174
|
-
CMUX::ModemChatter.poll [ @chatter ], @
|
181
|
+
CMUX::ModemChatter.poll [ @chatter ], @poll_interval
|
175
182
|
rescue => e
|
176
183
|
close_modem "modem poll failed: #{e}"
|
177
184
|
end
|
178
185
|
else
|
179
|
-
sleep @
|
186
|
+
sleep @poll_interval
|
180
187
|
end
|
181
188
|
|
182
189
|
end
|
@@ -25,6 +25,13 @@ module Smartware
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def print(data)
|
28
|
+
name = "smartwareprint_#{Time.now.strftime("%Y-%m-%d-%H:%M:%S")}.txt"
|
29
|
+
pathname = File.join(Dir.home, name)
|
30
|
+
|
31
|
+
File.open(pathname, 'w') { |io| io.write data }
|
32
|
+
|
33
|
+
Logging.logger.info "Created #{pathname}"
|
34
|
+
|
28
35
|
true
|
29
36
|
end
|
30
37
|
|
@@ -33,10 +40,23 @@ module Smartware
|
|
33
40
|
end
|
34
41
|
|
35
42
|
def new_render
|
36
|
-
|
43
|
+
DummyRender.new
|
37
44
|
end
|
38
45
|
end
|
39
46
|
|
47
|
+
class DummyRender < Redcarpet::Render::Base
|
48
|
+
def linebreak
|
49
|
+
"\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
def normal_text(text, keep_newlines = false)
|
53
|
+
unless keep_newlines
|
54
|
+
text.gsub! "\n", " "
|
55
|
+
end
|
56
|
+
|
57
|
+
text
|
58
|
+
end
|
59
|
+
end
|
40
60
|
end
|
41
61
|
end
|
42
62
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Smartware
|
2
|
+
module Interface
|
3
|
+
class CardReader < Interface
|
4
|
+
COMMUNICATION_ERROR = 1
|
5
|
+
HARDWARE_ERROR = 2
|
6
|
+
CARD_JAM_ERROR = 3
|
7
|
+
CARD_ERROR = 4
|
8
|
+
MAG_READ_ERROR = 5
|
9
|
+
ICC_ERROR = 6
|
10
|
+
|
11
|
+
class CardReaderError < RuntimeError
|
12
|
+
attr_reader :code
|
13
|
+
|
14
|
+
def initialize(message, code)
|
15
|
+
super(message)
|
16
|
+
|
17
|
+
@code = code
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(config)
|
22
|
+
super
|
23
|
+
|
24
|
+
@status[:model] = @device.model
|
25
|
+
@status[:version] = @device.version
|
26
|
+
end
|
27
|
+
|
28
|
+
def card_inserted?
|
29
|
+
@device.status == :card_inserted
|
30
|
+
rescue CardReaderError => e
|
31
|
+
@status[:error] = e.code
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def start_accepting
|
36
|
+
@device.accepting = true
|
37
|
+
@status[:error] = nil
|
38
|
+
true
|
39
|
+
rescue CardReaderError => e
|
40
|
+
@status[:error] = e.code
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop_accepting
|
45
|
+
@device.accepting = false
|
46
|
+
@status[:error] = nil
|
47
|
+
true
|
48
|
+
rescue CardReaderError => e
|
49
|
+
@status[:error] = e.code
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
def eject
|
54
|
+
@device.eject
|
55
|
+
|
56
|
+
sleep 0.5 while @device.status == :card_at_gate
|
57
|
+
|
58
|
+
@status[:error] = nil
|
59
|
+
true
|
60
|
+
rescue CardReaderError => e
|
61
|
+
@status[:error] = e.code
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def capture
|
66
|
+
@device.capture
|
67
|
+
@status[:error] = nil
|
68
|
+
true
|
69
|
+
rescue CardReaderError => e
|
70
|
+
@status[:error] = e.code
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
def read_magstrip
|
75
|
+
@device.read_magstrip
|
76
|
+
|
77
|
+
rescue CardReaderError => e
|
78
|
+
@status[:error] = e.code
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/smartware/service.rb
CHANGED
data/lib/smartware/version.rb
CHANGED
data/lib/smartware.rb
CHANGED
@@ -3,6 +3,7 @@ require 'yaml'
|
|
3
3
|
require 'active_support/core_ext/string/inflections'
|
4
4
|
require 'drb'
|
5
5
|
require 'redcarpet'
|
6
|
+
require 'stringio'
|
6
7
|
|
7
8
|
require 'smartkiosk/common'
|
8
9
|
|
@@ -14,11 +15,13 @@ require 'smartware/clients/cash_acceptor'
|
|
14
15
|
require 'smartware/clients/printer'
|
15
16
|
require 'smartware/clients/modem'
|
16
17
|
require 'smartware/clients/watchdog'
|
18
|
+
require 'smartware/clients/card_reader'
|
17
19
|
require 'smartware/interfaces/interface'
|
18
20
|
require 'smartware/interfaces/cash_acceptor'
|
19
21
|
require 'smartware/interfaces/modem'
|
20
22
|
require 'smartware/interfaces/printer'
|
21
23
|
require 'smartware/interfaces/watchdog'
|
24
|
+
require 'smartware/interfaces/card_reader'
|
22
25
|
require 'smartware/connection_monitor'
|
23
26
|
|
24
27
|
module Smartware
|
data/smartware.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smartware
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.8
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-01-
|
13
|
+
date: 2013-01-24 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: smartkiosk-common
|
@@ -108,6 +108,22 @@ dependencies:
|
|
108
108
|
- - ! '>='
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: digest-crc
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :runtime
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
111
127
|
description: Smartware is the Smartkiosk hardware control daemon
|
112
128
|
email:
|
113
129
|
- e.sudarchikov@roundlake.ru
|
@@ -127,11 +143,14 @@ files:
|
|
127
143
|
- bin/smartware-ppp-helper
|
128
144
|
- config/smartware.yml.sample
|
129
145
|
- lib/smartware.rb
|
146
|
+
- lib/smartware/clients/card_reader.rb
|
130
147
|
- lib/smartware/clients/cash_acceptor.rb
|
131
148
|
- lib/smartware/clients/modem.rb
|
132
149
|
- lib/smartware/clients/printer.rb
|
133
150
|
- lib/smartware/clients/watchdog.rb
|
134
151
|
- lib/smartware/connection_monitor.rb
|
152
|
+
- lib/smartware/drivers/card_reader/dummy.rb
|
153
|
+
- lib/smartware/drivers/card_reader/ict3_k5.rb
|
135
154
|
- lib/smartware/drivers/cash_acceptor/ccnet.rb
|
136
155
|
- lib/smartware/drivers/cash_acceptor/dummy.rb
|
137
156
|
- lib/smartware/drivers/modem/dummy.rb
|
@@ -140,6 +159,7 @@ files:
|
|
140
159
|
- lib/smartware/drivers/printer/esc_pos.rb
|
141
160
|
- lib/smartware/drivers/watchdog/dummy.rb
|
142
161
|
- lib/smartware/drivers/watchdog/watchdog_daemon.rb
|
162
|
+
- lib/smartware/interfaces/card_reader.rb
|
143
163
|
- lib/smartware/interfaces/cash_acceptor.rb
|
144
164
|
- lib/smartware/interfaces/interface.rb
|
145
165
|
- lib/smartware/interfaces/modem.rb
|