smartware 0.2.8 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,20 +1,10 @@
1
- # coding: utf-8
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
- STATUSES = {
37
- "10" => "10", # 'Power up',
38
- "11" => "11", # 'Power Up with Bill in Validator',
39
- "12" => "12", # 'Power Up with Bill in Stacker',
40
- "13" => "13", # 'Initialize',
41
- "14" => "14", # 'Idling',
42
- "15" => "15", # 'Accepting',
43
- "17" => "16", # 'Stacking',
44
- "18" => "17", # 'Returning',
45
- "19" => "18", # 'Unit Disabled',
46
- "1a" => "19", # 'Holding',
47
- "1b" => "20", # 'Device Busy',
48
- "1c" => "21", # 'Rejecting',
49
- "82" => "22" # 'Bill returned'
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
- "41" => Interface::CashAcceptor::DROP_CASETTE_FULL,
54
- "42" => Interface::CashAcceptor::DROP_CASETTE_OUT_OF_POSITION,
55
- "43" => Interface::CashAcceptor::VALIDATOR_JAMMED,
56
- "44" => Interface::CashAcceptor::DROP_CASETTE_JAMMED,
57
- "45" => Interface::CashAcceptor::CHEATED,
58
- "46" => Interface::CashAcceptor::PAUSE,
59
- "47" => Interface::CashAcceptor::BILL_VALIDATOR_FAILURE,
60
- "50" => Interface::CashAcceptor::STACK_MOTOR_FAILURE,
61
- "51" => Interface::CashAcceptor::TRANSPORT_MOTOR_SPEED_FAILURE,
62
- "52" => Interface::CashAcceptor::TRANSPORT_MOTOR_FAILURE,
63
- "53" => Interface::CashAcceptor::ALIGNING_MOTOR_FAILURE,
64
- "54" => Interface::CashAcceptor::INITIAL_CASETTE_STATUS_FAILURE,
65
- "55" => Interface::CashAcceptor::OPTIC_CANAL_FAILURE,
66
- "56" => Interface::CashAcceptor::MAGNETIC_CANAL_FAILURE,
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
- NOMINALS = { "2" => 10, "3" => 50, "4" => 100, "5" => 500, "6" => 1000, "7" => 5000 }
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
- @port = config["port"]
74
- end
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
- def cassette?
77
- error != Interface::CashAcceptor::DROP_CASETTE_OUT_OF_POSITION
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
- if answer = send([IDENTIFICATION], false)
82
- answer = answer[2..answer.length]
83
- return "#{answer[0..15]} #{answer[16..27]} #{answer[28..34].unpack("C*")}"
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
- # TODO: implement this
93
- "not implemented"
98
+ return nil if @identification.nil?
99
+
100
+ @identification[15..26]
94
101
  end
95
102
 
96
- def error
97
- res = poll
98
- ack
99
- return nil if res != nil and CCNET::STATUSES.keys.include?(res[3])
100
- return nil if res == nil
103
+ private
104
+
105
+ def parse_bill_types(table)
106
+ offset = 0
107
+ types = Array.new(24)
101
108
 
102
- result = check_error(res)
103
- rescue
104
- Interface::CashAcceptor::COMMUNICATION_ERROR
105
- end
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
- def current_banknote
108
- poll
109
- ack
110
- hold
111
- res = poll
112
- ack
114
+ offset += 5
113
115
 
114
- result = check_error(res)
115
- if !res.nil? and res[2] == "7" and res[3] == "80" and CCNET::NOMINALS.keys.include?(res[4]) # has money?
116
- result = CCNET::NOMINALS[res[4]]
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
- def get_status
122
- send([GET_STATUS])
129
+ types
123
130
  end
124
131
 
125
- def reset
126
- send([RESET])
127
- end
132
+ def escrow(bill)
133
+ ret = false
128
134
 
129
- def ack
130
- send([ACK])
131
- end
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
- def stack
134
- send([STACK])
142
+ @connection.command(ret ? STACK : RETURN) {}
135
143
  end
136
144
 
137
- def return
138
- send([RETURN])
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 hold
142
- send([])
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
- send([POLL])
147
- end
164
+ @connection.command(POLL) do |resp|
165
+ interval = nil
148
166
 
149
- def accept
150
- send([ENABLE_BILL_TYPES,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF])
151
- end
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
- def cancel_accept
154
- send([ENABLE_BILL_TYPES,0x00,0x00,0x00,0x00,0x00,0x00])
155
- get_status
156
- end
175
+ case state
176
+ when POWER_UP,
177
+ POWER_UP_WITH_BILL_IN_VALIDATOR
178
+ POWER_UP_WITH_BILL_IN_STACKER
157
179
 
158
- private
159
- def check_error(res)
160
- if CCNET::ERRORS.keys.include? res[3]
161
- res[3] == "47" ? CCNET::ERRORS[res[4]] : CCNET::ERRORS[res[3]] # More details for 47 error
162
- else
163
- nil
164
- end
165
- end
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
- def crc_for(msg)
168
- polynom = 0x8408.to_i
169
- msg = msg.collect{|o| o.to_i}
170
- res = 0
171
- tmpcrc = 0
172
- 0.upto(msg.length-1){|i|
173
- tmpcrc = res ^ msg[i]
174
- 0.upto(7){
175
- if tmpcrc & 1 != 0
176
- tmpcrc = tmpcrc >> 1
177
- tmpcrc = tmpcrc ^ polynom
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
- tmpcrc = tmpcrc >> 1
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
- def send(msg, parse_answer = true)
190
- sp = SerialPort.new(@port, 9600, 8, 1, SerialPort::NONE)
191
- sp.read_timeout = 100
192
- message = [0x02, 0x03, 5 + msg.length]
193
- message += msg
194
- crc = crc_for(message)
195
- message += crc
196
- message = message.pack("C*")
197
- sp.write message
198
- ans = sp.gets
199
- if ans
200
- parse_answer ? res = ans.unpack("C*").map{|e| e.to_s(16) } : res = ans
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
- res = nil
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
- end
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