smartware 0.2.8 → 0.3
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.
- 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
|