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.
@@ -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