smartware 0.2.8 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/smartware.rb +2 -0
- data/lib/smartware/drivers/card_reader/ict3_k5.rb +48 -313
- data/lib/smartware/drivers/cash_acceptor/ccnet.rb +212 -144
- data/lib/smartware/drivers/cash_acceptor/dummy.rb +11 -33
- data/lib/smartware/drivers/common/ccnet_connection.rb +135 -0
- data/lib/smartware/drivers/common/command_based_device.rb +101 -0
- data/lib/smartware/drivers/common/sankyo_connection.rb +154 -0
- data/lib/smartware/interfaces/cash_acceptor.rb +53 -101
- data/lib/smartware/service.rb +17 -12
- data/lib/smartware/version.rb +1 -1
- data/smartware.gemspec +1 -0
- metadata +21 -2
@@ -1,20 +1,10 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
# CCNET protocol driver for CashCode bill validator
|
4
|
-
#
|
5
|
-
require 'serialport'
|
6
|
-
|
7
|
-
COMMUNICATION_ERROR = 1
|
8
|
-
DROP_CASETTE_FULL = 2
|
9
|
-
DROP_CASETTE_OUT_OF_POSITION = 3
|
10
|
-
|
1
|
+
require "smartware/drivers/common/ccnet_connection.rb"
|
11
2
|
|
12
3
|
module Smartware
|
13
4
|
module Driver
|
14
5
|
module CashAcceptor
|
15
|
-
|
16
6
|
class CCNET
|
17
|
-
|
7
|
+
# Commands
|
18
8
|
RESET = 0x30
|
19
9
|
GET_STATUS = 0x31
|
20
10
|
SET_SECURITY = 0x32
|
@@ -33,179 +23,257 @@ module Smartware
|
|
33
23
|
ACK = 0x00
|
34
24
|
DISPENCE = 0x3C
|
35
25
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
26
|
+
# States
|
27
|
+
POWER_UP = 0x10
|
28
|
+
POWER_UP_WITH_BILL_IN_VALIDATOR = 0x11
|
29
|
+
POWER_UP_WITH_BILL_IN_STACKER = 0x12
|
30
|
+
INITIALIZE = 0x13
|
31
|
+
IDLING = 0x14
|
32
|
+
ACCEPTING = 0x15
|
33
|
+
STACKING = 0x17
|
34
|
+
RETURNING = 0x18
|
35
|
+
UNIT_DISABLED = 0x19
|
36
|
+
HOLDING = 0x1A
|
37
|
+
DEVICE_BUSY = 0x1B
|
38
|
+
REJECTING = 0x1C
|
39
|
+
DROP_CASETTE_FULL = 0x41
|
40
|
+
DROP_CASETTE_OUT_OF_POSITION = 0x42
|
41
|
+
VALIDATOR_JAMMED = 0x43
|
42
|
+
DROP_CASETTE_JAMMED = 0x44
|
43
|
+
CHEATED = 0x45
|
44
|
+
PAUSE = 0x46
|
45
|
+
FAILURE = 0x47
|
46
|
+
ESCROW = 0x80
|
47
|
+
STACKED = 0x81
|
48
|
+
RETURNED = 0x82
|
51
49
|
|
52
50
|
ERRORS = {
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
"5f" => Interface::CashAcceptor::CAPACITANCE_CANAL_FAILURE
|
51
|
+
0x41 => Interface::CashAcceptor::DROP_CASETTE_FULL,
|
52
|
+
0x42 => Interface::CashAcceptor::DROP_CASETTE_OUT_OF_POSITION,
|
53
|
+
0x43 => Interface::CashAcceptor::VALIDATOR_JAMMED,
|
54
|
+
0x44 => Interface::CashAcceptor::DROP_CASETTE_JAMMED,
|
55
|
+
0x45 => Interface::CashAcceptor::CHEATED,
|
56
|
+
0x47 => Interface::CashAcceptor::BILL_VALIDATOR_FAILURE,
|
57
|
+
0x50 => Interface::CashAcceptor::STACK_MOTOR_FAILURE,
|
58
|
+
0x51 => Interface::CashAcceptor::TRANSPORT_MOTOR_SPEED_FAILURE,
|
59
|
+
0x52 => Interface::CashAcceptor::TRANSPORT_MOTOR_FAILURE,
|
60
|
+
0x53 => Interface::CashAcceptor::ALIGNING_MOTOR_FAILURE,
|
61
|
+
0x54 => Interface::CashAcceptor::INITIAL_CASETTE_STATUS_FAILURE,
|
62
|
+
0x55 => Interface::CashAcceptor::OPTIC_CANAL_FAILURE,
|
63
|
+
0x56 => Interface::CashAcceptor::MAGNETIC_CANAL_FAILURE,
|
64
|
+
0x5f => Interface::CashAcceptor::CAPACITANCE_CANAL_FAILURE
|
68
65
|
}
|
69
66
|
|
70
|
-
|
67
|
+
attr_reader :bill_types
|
68
|
+
|
69
|
+
attr_accessor :escrow, :stacked, :returned, :status
|
70
|
+
attr_accessor :enabled_types
|
71
71
|
|
72
72
|
def initialize(config)
|
73
|
-
@
|
74
|
-
|
73
|
+
@io = SerialPort.new(config["port"], 9600, 8, 1, SerialPort::NONE)
|
74
|
+
@io.flow_control = SerialPort::NONE
|
75
|
+
|
76
|
+
@connection = EventMachine.attach @io, CCNETConnection
|
77
|
+
@connection.address = 3
|
75
78
|
|
76
|
-
|
77
|
-
|
79
|
+
@escrow = nil
|
80
|
+
@stacked = nil
|
81
|
+
@returned = nil
|
82
|
+
@status = nil
|
83
|
+
|
84
|
+
@bill_types = nil
|
85
|
+
@identification = nil
|
86
|
+
@enabled_types = 0
|
87
|
+
|
88
|
+
set_poll
|
78
89
|
end
|
79
90
|
|
80
91
|
def model
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
else
|
85
|
-
return "Unknown device answer"
|
86
|
-
end
|
87
|
-
rescue
|
88
|
-
-1
|
92
|
+
return nil if @identification.nil?
|
93
|
+
|
94
|
+
@identification[0..14]
|
89
95
|
end
|
90
96
|
|
91
97
|
def version
|
92
|
-
|
93
|
-
|
98
|
+
return nil if @identification.nil?
|
99
|
+
|
100
|
+
@identification[15..26]
|
94
101
|
end
|
95
102
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
103
|
+
private
|
104
|
+
|
105
|
+
def parse_bill_types(table)
|
106
|
+
offset = 0
|
107
|
+
types = Array.new(24)
|
101
108
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
109
|
+
while offset < table.length
|
110
|
+
mantissa = table.getbyte(offset + 0)
|
111
|
+
position = table.getbyte(offset + 4)
|
112
|
+
country = table.slice(offset + 1, 3)
|
106
113
|
|
107
|
-
|
108
|
-
poll
|
109
|
-
ack
|
110
|
-
hold
|
111
|
-
res = poll
|
112
|
-
ack
|
114
|
+
offset += 5
|
113
115
|
|
114
|
-
|
115
|
-
|
116
|
-
|
116
|
+
next if country == "\x00\x00\x00"
|
117
|
+
|
118
|
+
exponent = (10 ** (position & 0x7F))
|
119
|
+
|
120
|
+
if (position & 0x80) != 0
|
121
|
+
value = mantissa / exponent
|
122
|
+
else
|
123
|
+
value = mantissa * exponent
|
124
|
+
end
|
125
|
+
|
126
|
+
types[offset / 5 - 1] = Interface::CashAcceptor::BillType.new value, country
|
117
127
|
end
|
118
|
-
result
|
119
|
-
end
|
120
128
|
|
121
|
-
|
122
|
-
send([GET_STATUS])
|
129
|
+
types
|
123
130
|
end
|
124
131
|
|
125
|
-
def
|
126
|
-
|
127
|
-
end
|
132
|
+
def escrow(bill)
|
133
|
+
ret = false
|
128
134
|
|
129
|
-
|
130
|
-
|
131
|
-
|
135
|
+
begin
|
136
|
+
ret = @escrow.call @bill_types[bill]
|
137
|
+
rescue => e
|
138
|
+
Logging.logger.error "Error in escrow: #{e}"
|
139
|
+
e.backtrace.each { |line| Logging.logger.error line }
|
140
|
+
end
|
132
141
|
|
133
|
-
|
134
|
-
send([STACK])
|
142
|
+
@connection.command(ret ? STACK : RETURN) {}
|
135
143
|
end
|
136
144
|
|
137
|
-
def
|
138
|
-
|
145
|
+
def stacked(bill)
|
146
|
+
begin
|
147
|
+
@stacked.call @bill_types[bill]
|
148
|
+
rescue => e
|
149
|
+
Logging.logger.error "Error in stacked: #{e}"
|
150
|
+
e.backtrace.each { |line| Logging.logger.error line }
|
151
|
+
end
|
139
152
|
end
|
140
153
|
|
141
|
-
def
|
142
|
-
|
154
|
+
def returned(bill)
|
155
|
+
begin
|
156
|
+
@returned.call @bill_types[bill]
|
157
|
+
rescue => e
|
158
|
+
Logging.logger.error "Error in returned: #{e}"
|
159
|
+
e.backtrace.each { |line| Logging.logger.error line }
|
160
|
+
end
|
143
161
|
end
|
144
162
|
|
145
163
|
def poll
|
146
|
-
|
147
|
-
|
164
|
+
@connection.command(POLL) do |resp|
|
165
|
+
interval = nil
|
148
166
|
|
149
|
-
|
150
|
-
|
151
|
-
|
167
|
+
if resp.nil?
|
168
|
+
@identification = nil
|
169
|
+
@bill_types = nil
|
170
|
+
interval = 5
|
171
|
+
error = Interface::CashAcceptor::COMMUNICATION_ERROR
|
172
|
+
else
|
173
|
+
state = resp.getbyte(0)
|
152
174
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
175
|
+
case state
|
176
|
+
when POWER_UP,
|
177
|
+
POWER_UP_WITH_BILL_IN_VALIDATOR
|
178
|
+
POWER_UP_WITH_BILL_IN_STACKER
|
157
179
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
180
|
+
Logging.logger.info "Cash acceptor powered up, initializing"
|
181
|
+
|
182
|
+
@connection.command(RESET) {}
|
183
|
+
|
184
|
+
when INITIALIZE, ACCEPTING, STACKING, RETURNING, REJECTING,
|
185
|
+
CHEATED
|
186
|
+
# Cash acceptor is busy
|
187
|
+
|
188
|
+
when IDLING
|
189
|
+
if @enabled_types == 0
|
190
|
+
@connection.command(ENABLE_BILL_TYPES, "\x00" * 6) {}
|
191
|
+
end
|
192
|
+
|
193
|
+
when UNIT_DISABLED
|
194
|
+
if @identification.nil?
|
195
|
+
Logging.logger.debug "Identifying acceptor"
|
166
196
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
197
|
+
@connection.command(IDENTIFICATION) do |resp|
|
198
|
+
@identification = resp
|
199
|
+
Logging.logger.debug "It's #{model}, serial #{version}"
|
200
|
+
end
|
201
|
+
|
202
|
+
elsif @bill_types.nil?
|
203
|
+
Logging.logger.debug "Loading bill table"
|
204
|
+
|
205
|
+
@connection.command(GET_BILL_TABLE) do |resp|
|
206
|
+
@bill_types = parse_bill_types resp unless resp.nil?
|
207
|
+
end
|
208
|
+
|
209
|
+
elsif @enabled_types != 0
|
210
|
+
mask = [
|
211
|
+
# Enabled types
|
212
|
+
0,
|
213
|
+
(enabled_types & 0x300) >> 2,
|
214
|
+
enabled_types & 0xFF,
|
215
|
+
# Escrow types
|
216
|
+
0,
|
217
|
+
(enabled_types & 0x300) >> 2,
|
218
|
+
enabled_types & 0xFF,
|
219
|
+
]
|
220
|
+
|
221
|
+
@connection.command(ENABLE_BILL_TYPES, mask.pack("C*")) {}
|
178
222
|
else
|
179
|
-
|
223
|
+
interval = 0.5
|
180
224
|
end
|
181
|
-
}
|
182
|
-
res = tmpcrc
|
183
|
-
}
|
184
|
-
crc = tmpcrc
|
185
|
-
crc = ("%02x" % crc).rjust(4,"0")
|
186
|
-
crc = [Integer("0x"+crc[2..3]), Integer("0x"+crc[0..1])]
|
187
|
-
end
|
188
225
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
226
|
+
when DEVICE_BUSY
|
227
|
+
interval = resp.getbyte(1) * 0.1
|
228
|
+
|
229
|
+
when PAUSE
|
230
|
+
Logging.logger.warn "Cash acceptor pause"
|
231
|
+
|
232
|
+
@connection.command(RESET) {}
|
233
|
+
|
234
|
+
when DROP_CASETTE_FULL, DROP_CASETTE_OUT_OF_POSITION,
|
235
|
+
VALIDATOR_JAMMED, DROP_CASETTE_JAMMED
|
236
|
+
|
237
|
+
error = ERRORS[state]
|
238
|
+
|
239
|
+
when FAILURE
|
240
|
+
detail = resp.getbyte(1)
|
241
|
+
error = ERRORS[detail]
|
242
|
+
|
243
|
+
when ESCROW
|
244
|
+
escrow resp.getbyte(1)
|
245
|
+
|
246
|
+
when STACKED
|
247
|
+
stacked resp.getbyte(1)
|
248
|
+
|
249
|
+
when RETURNED
|
250
|
+
returned resp.getbyte(1)
|
251
|
+
|
252
|
+
else # incl. HOLDING
|
253
|
+
Logging.logger.warn "Unexpected cash acceptor state: #{state.to_s 16}"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
if interval.nil?
|
258
|
+
set_poll
|
201
259
|
else
|
202
|
-
|
260
|
+
set_poll interval
|
261
|
+
end
|
262
|
+
|
263
|
+
begin
|
264
|
+
@status.call error
|
265
|
+
rescue => e
|
266
|
+
Logging.logger.error "Error in status: #{e}"
|
267
|
+
e.backtrace.each { |line| Logging.logger.error line }
|
203
268
|
end
|
204
|
-
sp.close
|
205
|
-
res
|
206
269
|
end
|
207
|
-
|
270
|
+
end
|
208
271
|
|
272
|
+
def set_poll(interval = 0.1)
|
273
|
+
EventMachine.add_timer(interval, &method(:poll))
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
209
277
|
end
|
210
278
|
end
|
211
|
-
end
|
279
|
+
end
|
@@ -4,9 +4,20 @@ module Smartware
|
|
4
4
|
module CashAcceptor
|
5
5
|
|
6
6
|
class Dummy
|
7
|
+
attr_reader :bill_types
|
8
|
+
|
9
|
+
attr_accessor :escrow, :stacked, :returned, :status
|
10
|
+
attr_accessor :enabled_types
|
7
11
|
|
8
12
|
def initialize(config)
|
13
|
+
@bill_types = []
|
14
|
+
|
15
|
+
@escrow = nil
|
16
|
+
@stacked = nil
|
17
|
+
@returned = nil
|
18
|
+
@status = nil
|
9
19
|
|
20
|
+
@enabled_types = nil
|
10
21
|
end
|
11
22
|
|
12
23
|
def model
|
@@ -16,39 +27,6 @@ module Smartware
|
|
16
27
|
def version
|
17
28
|
"1.0"
|
18
29
|
end
|
19
|
-
|
20
|
-
def cassette?
|
21
|
-
true
|
22
|
-
end
|
23
|
-
|
24
|
-
def error
|
25
|
-
nil
|
26
|
-
end
|
27
|
-
|
28
|
-
def current_banknote
|
29
|
-
return false if ( rand(9) < 6 )
|
30
|
-
[20, 40, 60, 80].sample
|
31
|
-
end
|
32
|
-
|
33
|
-
def accept
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
def cancel_accept
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def stack
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
def return
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
def reset
|
50
|
-
|
51
|
-
end
|
52
30
|
end
|
53
31
|
|
54
32
|
end
|