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
@@ -0,0 +1,135 @@
|
|
1
|
+
require "smartware/drivers/common/command_based_device.rb"
|
2
|
+
|
3
|
+
module Smartware
|
4
|
+
class CCNETConnection < CommandBasedDevice
|
5
|
+
|
6
|
+
POLYNOMIAL = 0x8408
|
7
|
+
|
8
|
+
attr_accessor :address
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
|
13
|
+
@address = 0
|
14
|
+
@command_timer = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def remove_timeouts
|
20
|
+
unless @command_timer.nil?
|
21
|
+
EventMachine.cancel_timer @command_timer
|
22
|
+
@command_timer = nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def install_timeouts
|
27
|
+
@command_timer = EventMachine.add_timer(1) do
|
28
|
+
@command_timer = nil
|
29
|
+
retry_or_fail
|
30
|
+
post_command unless @executing_command
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def max_retries
|
35
|
+
8
|
36
|
+
end
|
37
|
+
|
38
|
+
def submit_command(command = nil, data = "")
|
39
|
+
if command.kind_of? Array
|
40
|
+
command, subcommand = command
|
41
|
+
else
|
42
|
+
subcommand = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
if data.length <= 250
|
46
|
+
if command.nil?
|
47
|
+
frame = [ 0x02, @address, data.size + 5 ].pack("C*")
|
48
|
+
elsif subcommand.nil?
|
49
|
+
frame = [ 0x02, @address, data.size + 6, command ].pack("C*")
|
50
|
+
else
|
51
|
+
frame = [ 0x02, @address, data.size + 7, command, subcommand ].pack("C*")
|
52
|
+
end
|
53
|
+
else
|
54
|
+
if command.nil?
|
55
|
+
frame = [ 0x02, @address, 0, data.size + 7 ].pack("C3n")
|
56
|
+
elsif subcommand.nil?
|
57
|
+
frame = [ 0x02, @address, 0, command, data.size + 8 ].pack("C4n")
|
58
|
+
else
|
59
|
+
frame = [ 0x02, @address, 0, command, subcommand, data.size + 9 ].pack("C5n")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
frame << data
|
64
|
+
frame << [ crc_for(frame) ].pack("v")
|
65
|
+
|
66
|
+
send_data frame
|
67
|
+
end
|
68
|
+
|
69
|
+
def crc_for(msg)
|
70
|
+
crc = 0
|
71
|
+
|
72
|
+
msg.each_byte do |byte|
|
73
|
+
crc ^= byte
|
74
|
+
|
75
|
+
8.times do
|
76
|
+
if (crc & 1) != 0
|
77
|
+
crc >>= 1
|
78
|
+
crc ^= POLYNOMIAL
|
79
|
+
else
|
80
|
+
crc >>= 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
crc
|
86
|
+
end
|
87
|
+
|
88
|
+
def handle_response
|
89
|
+
while @buffer.size >= 5
|
90
|
+
short_length = @buffer.getbyte(2)
|
91
|
+
if short_length == 0
|
92
|
+
break if @buffer.size < 7
|
93
|
+
|
94
|
+
length, = @buffer.slice(3, 2).unpack("n")
|
95
|
+
else
|
96
|
+
length = short_length
|
97
|
+
end
|
98
|
+
|
99
|
+
break if @buffer.size < length
|
100
|
+
|
101
|
+
message = @buffer.slice! 0, length
|
102
|
+
crc, = message[-2..-1].unpack("v")
|
103
|
+
expected_crc = crc_for(message[0..-3])
|
104
|
+
|
105
|
+
if expected_crc != crc
|
106
|
+
Logging.logger.warn "bad response CRC: #{expected_crc} expected, #{crc} got"
|
107
|
+
|
108
|
+
retry_or_fail if @executing_command
|
109
|
+
next
|
110
|
+
end
|
111
|
+
|
112
|
+
if message.getbyte(1) != @address
|
113
|
+
Logging.logger.warn "unexpected address: #{@buffer.inspect}"
|
114
|
+
|
115
|
+
next
|
116
|
+
end
|
117
|
+
|
118
|
+
if @executing_command
|
119
|
+
# Acknowlege
|
120
|
+
submit_command nil, "\x00"
|
121
|
+
|
122
|
+
if short_length == 0
|
123
|
+
complete message[5..-3]
|
124
|
+
else
|
125
|
+
complete message[3..-3]
|
126
|
+
end
|
127
|
+
else
|
128
|
+
Logging.logger.warn "unexpected message: #{@buffer.inspect}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
post_command unless @executing_command
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Smartware
|
2
|
+
class CommandBasedDevice < EventMachine::Connection
|
3
|
+
def initialize
|
4
|
+
@ready_for_commands = true
|
5
|
+
@executing_command = false
|
6
|
+
@retries = nil
|
7
|
+
@command = nil
|
8
|
+
@completion_proc = nil
|
9
|
+
@buffer = ""
|
10
|
+
|
11
|
+
@command_queue = []
|
12
|
+
@command_mutex = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def receive_data(data)
|
16
|
+
@buffer << data
|
17
|
+
|
18
|
+
handle_response
|
19
|
+
end
|
20
|
+
|
21
|
+
def command(*args, &block)
|
22
|
+
if !block_given?
|
23
|
+
queue = Queue.new
|
24
|
+
command(*args) do |resp|
|
25
|
+
queue.push resp
|
26
|
+
end
|
27
|
+
queue.pop
|
28
|
+
else
|
29
|
+
@command_mutex.synchronize do
|
30
|
+
@command_queue.push [ args, block ]
|
31
|
+
end
|
32
|
+
|
33
|
+
EventMachine.schedule method(:post_command)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def ready?
|
40
|
+
@ready_for_commands && !@executing_command
|
41
|
+
end
|
42
|
+
|
43
|
+
def post_command
|
44
|
+
executing_command, command = @command_mutex.synchronize do
|
45
|
+
if ready?
|
46
|
+
[ @command_queue.any?, @command_queue.first ]
|
47
|
+
else
|
48
|
+
[ false, nil ]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if executing_command
|
53
|
+
@executing_command = executing_command
|
54
|
+
install_timeouts
|
55
|
+
@retries = max_retries
|
56
|
+
command, = @command_mutex.synchronize { @command_queue.first }
|
57
|
+
submit_command *command
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def kill_queue
|
62
|
+
blocks = nil
|
63
|
+
|
64
|
+
@command_mutex.synchronize do
|
65
|
+
blocks = @command_queue.map { |command, block| block }
|
66
|
+
@command_queue.clear
|
67
|
+
|
68
|
+
@ready_for_commands = false
|
69
|
+
end
|
70
|
+
|
71
|
+
blocks.each do |block|
|
72
|
+
block.call nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def retry_or_fail
|
77
|
+
if @retries == 0
|
78
|
+
complete nil
|
79
|
+
else
|
80
|
+
@retries -= 1
|
81
|
+
command, = @command_mutex.synchronize { @command_queue.first }
|
82
|
+
remove_timeouts
|
83
|
+
install_timeouts
|
84
|
+
submit_command *command
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def complete(data)
|
89
|
+
command, proc = @command_mutex.synchronize { @command_queue.shift }
|
90
|
+
remove_timeouts
|
91
|
+
@executing_command = false
|
92
|
+
|
93
|
+
begin
|
94
|
+
proc.call data
|
95
|
+
rescue => e
|
96
|
+
Logging.logger.error "Error in completion handler: #{e}"
|
97
|
+
e.backtrace.each { |line| Logging.logger.error line }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require "smartware/drivers/common/command_based_device"
|
2
|
+
require "digest/crc16_xmodem"
|
3
|
+
|
4
|
+
module Smartware
|
5
|
+
class SankyoConnection < CommandBasedDevice
|
6
|
+
class CommandResponse
|
7
|
+
def initialize(response)
|
8
|
+
@response = response
|
9
|
+
end
|
10
|
+
|
11
|
+
def positive?
|
12
|
+
@response[0] == "P"
|
13
|
+
end
|
14
|
+
|
15
|
+
def negative?
|
16
|
+
@response[0] != "P"
|
17
|
+
end
|
18
|
+
|
19
|
+
def response
|
20
|
+
@response[1..-1]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
STX = 0xF2
|
25
|
+
ACK = 0x06
|
26
|
+
NAK = 0x15
|
27
|
+
|
28
|
+
attr_accessor :initialize_device
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
super
|
32
|
+
|
33
|
+
@command_timer = nil
|
34
|
+
@initialize_device = nil
|
35
|
+
@ready_for_commands = false
|
36
|
+
@state = :drop
|
37
|
+
|
38
|
+
EventMachine.add_periodic_timer(10) { check_dsr! }
|
39
|
+
EventMachine.next_tick { check_dsr! }
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_dsr!
|
43
|
+
if @io.dsr == 0
|
44
|
+
@state = :drop
|
45
|
+
kill_queue
|
46
|
+
else
|
47
|
+
was_ready, @ready_for_commands = @ready_for_commands, true
|
48
|
+
|
49
|
+
if !was_ready
|
50
|
+
@initialize_device.call
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def remove_timeouts
|
58
|
+
unless @command_timer.nil?
|
59
|
+
EventMachine.cancel_timer @command_timer
|
60
|
+
@command_timer = nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def install_timeouts(type = :sync)
|
65
|
+
interval = nil
|
66
|
+
case type
|
67
|
+
when :sync
|
68
|
+
interval = 0.3
|
69
|
+
|
70
|
+
when :command
|
71
|
+
interval = 20
|
72
|
+
|
73
|
+
else
|
74
|
+
raise "invalid timeout type: #{type.inspect}"
|
75
|
+
end
|
76
|
+
|
77
|
+
@command_timer = EventMachine.add_timer(interval) do
|
78
|
+
@command_timer = nil
|
79
|
+
retry_or_fail
|
80
|
+
post_command unless @executing_command
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def max_retries
|
85
|
+
8
|
86
|
+
end
|
87
|
+
|
88
|
+
def submit_command(*bytes)
|
89
|
+
data = [ STX, bytes.length + 1 ].pack("Cn")
|
90
|
+
data << "C"
|
91
|
+
data << bytes.pack("C*")
|
92
|
+
|
93
|
+
crc = Digest::CRC16XModem.new
|
94
|
+
crc << data
|
95
|
+
data << [ crc.checksum ].pack("n")
|
96
|
+
|
97
|
+
@state = :sync
|
98
|
+
send_data data
|
99
|
+
end
|
100
|
+
|
101
|
+
def complete(response)
|
102
|
+
@state = :drop
|
103
|
+
super
|
104
|
+
end
|
105
|
+
|
106
|
+
def handle_response
|
107
|
+
until @buffer.empty?
|
108
|
+
case @state
|
109
|
+
when :drop
|
110
|
+
@buffer.clear
|
111
|
+
|
112
|
+
when :sync
|
113
|
+
initial_byte = @buffer.slice!(0, 1).ord
|
114
|
+
|
115
|
+
case initial_byte
|
116
|
+
when ACK
|
117
|
+
@state = :response
|
118
|
+
remove_timeouts
|
119
|
+
install_timeouts :command
|
120
|
+
|
121
|
+
when NAK
|
122
|
+
retry_or_fail
|
123
|
+
end
|
124
|
+
|
125
|
+
when :response
|
126
|
+
break if @buffer.length < 5
|
127
|
+
|
128
|
+
leading_byte, length = @buffer[0..2].unpack("Cn")
|
129
|
+
if leading_byte != STX
|
130
|
+
@buffer.slice! 0, 1
|
131
|
+
next
|
132
|
+
end
|
133
|
+
|
134
|
+
full_length = 5 + length
|
135
|
+
|
136
|
+
break if @buffer.length < full_length
|
137
|
+
|
138
|
+
message = @buffer.slice! 0, full_length
|
139
|
+
sum, = message.slice!(full_length - 2, 2).unpack("n")
|
140
|
+
crc = Digest::CRC16XModem.new
|
141
|
+
crc << message
|
142
|
+
if sum == crc.checksum
|
143
|
+
send_data ACK.chr
|
144
|
+
complete CommandResponse.new(message[3..-1])
|
145
|
+
else
|
146
|
+
send_data NAK.chr
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
post_command
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -19,9 +19,28 @@ module Smartware
|
|
19
19
|
CAPACITANCE_CANAL_FAILURE = 15
|
20
20
|
COMMUNICATION_ERROR = 16
|
21
21
|
|
22
|
+
class BillType
|
23
|
+
attr_reader :value
|
24
|
+
attr_reader :country
|
25
|
+
|
26
|
+
def initialize(value, country)
|
27
|
+
@value = value
|
28
|
+
@country = country
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
"#{@country}:#{@value}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
22
36
|
def initialize(config)
|
23
37
|
super
|
24
38
|
|
39
|
+
@device.escrow = method :escrow
|
40
|
+
@device.stacked = method :stacked
|
41
|
+
@device.returned = method :returned
|
42
|
+
@device.status = method :status
|
43
|
+
|
25
44
|
@limit = nil
|
26
45
|
@banknotes = {}
|
27
46
|
@banknotes.default = 0
|
@@ -30,12 +49,6 @@ module Smartware
|
|
30
49
|
@status[:casette] = true
|
31
50
|
end
|
32
51
|
|
33
|
-
@accepting = false
|
34
|
-
@commands = Queue.new
|
35
|
-
@commands.push :close
|
36
|
-
|
37
|
-
Thread.new &method(:dispatch_commands)
|
38
|
-
Thread.new &method(:periodic)
|
39
52
|
Smartware::Logging.logger.info "Cash acceptor monitor started"
|
40
53
|
end
|
41
54
|
|
@@ -45,21 +58,34 @@ module Smartware
|
|
45
58
|
if limit_min.nil? || limit_max.nil?
|
46
59
|
@limit = nil
|
47
60
|
|
48
|
-
Smartware::Logging.logger.
|
61
|
+
Smartware::Logging.logger.debug "Session open, unlimited"
|
49
62
|
else
|
50
63
|
@limit = limit_min..limit_max
|
51
64
|
|
52
|
-
Smartware::Logging.logger.
|
65
|
+
Smartware::Logging.logger.debug "Session open, limit: #{@limit}"
|
53
66
|
end
|
54
67
|
|
55
|
-
|
56
|
-
|
68
|
+
EventMachine.schedule do
|
69
|
+
types = 0
|
70
|
+
@device.bill_types.each_with_index do |type, index|
|
71
|
+
next if type.nil?
|
72
|
+
|
73
|
+
if type.country == 'RUS'
|
74
|
+
types |= 1 << index
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
@device.enabled_types = types
|
79
|
+
end
|
57
80
|
end
|
58
81
|
|
59
82
|
def close_session
|
60
|
-
Smartware::Logging.logger.
|
83
|
+
Smartware::Logging.logger.debug "Session closed"
|
84
|
+
|
85
|
+
EventMachine.schedule do
|
86
|
+
@device.enabled_types = 0
|
87
|
+
end
|
61
88
|
|
62
|
-
@commands.push :close
|
63
89
|
@limit = nil
|
64
90
|
end
|
65
91
|
|
@@ -75,110 +101,36 @@ module Smartware
|
|
75
101
|
|
76
102
|
private
|
77
103
|
|
78
|
-
def limit_satisfied(sum)
|
104
|
+
def limit_satisfied?(sum)
|
79
105
|
@limit.nil? or @limit.include? sum
|
80
106
|
end
|
81
107
|
|
82
|
-
def
|
83
|
-
|
84
|
-
@device.accept
|
85
|
-
Smartware::Logging.logger.info "Cash acceptor open"
|
86
|
-
|
87
|
-
@accepting = true
|
108
|
+
def escrow(banknote)
|
109
|
+
limit_satisfied?(self.cashsum + banknote.value)
|
88
110
|
end
|
89
111
|
|
90
|
-
def
|
91
|
-
|
112
|
+
def stacked(banknote)
|
113
|
+
value = banknote.value
|
92
114
|
|
93
|
-
|
94
|
-
|
95
|
-
if limit_satisfied(self.cashsum + res)
|
96
|
-
@device.stack
|
97
|
-
update_status do
|
98
|
-
@banknotes[res] += 1
|
99
|
-
end
|
100
|
-
Smartware::Logging.logger.info "Cash acceptor bill stacked, #{res}"
|
101
|
-
else
|
102
|
-
@device.return
|
103
|
-
Smartware::Logging.logger.info "Cash acceptor limit violation, return #{res}"
|
104
|
-
end
|
105
|
-
|
106
|
-
when String
|
107
|
-
Smartware::Logging.logger.error "Cash acceptor error #{res}"
|
108
|
-
|
109
|
-
update_status do
|
110
|
-
@status[:error] = res
|
111
|
-
end
|
115
|
+
update_status do
|
116
|
+
@banknotes[value] += 1
|
112
117
|
end
|
113
118
|
|
114
|
-
|
115
|
-
# Close cash acceptor if current cashsum equal max-limit
|
116
|
-
|
117
|
-
execute_close
|
118
|
-
end
|
119
|
+
Smartware::Logging.logger.debug "cash acceptor: bill stacked, #{value}"
|
119
120
|
end
|
120
121
|
|
121
|
-
def
|
122
|
-
|
123
|
-
Smartware::Logging.logger.info "Cash acceptor close"
|
122
|
+
def returned(banknote)
|
123
|
+
value = banknote.value
|
124
124
|
|
125
|
-
|
125
|
+
Smartware::Logging.logger.debug "cash acceptor: bill returned, #{value}"
|
126
126
|
end
|
127
127
|
|
128
|
-
def
|
129
|
-
error = @device.error
|
130
|
-
model = @device.model
|
131
|
-
version = @device.version
|
132
|
-
cassette = @device.cassette?
|
133
|
-
|
128
|
+
def status(error)
|
134
129
|
update_status do
|
135
130
|
@status[:error] = error
|
136
|
-
@status[:model] = model
|
137
|
-
@status[:version] = version
|
138
|
-
@status[:
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def dispatch_commands
|
143
|
-
loop do
|
144
|
-
command = @commands.pop
|
145
|
-
|
146
|
-
begin
|
147
|
-
start = Time.now
|
148
|
-
|
149
|
-
send :"execute_#{command}"
|
150
|
-
|
151
|
-
complete = Time.now
|
152
|
-
if complete - start > 1
|
153
|
-
Smartware::Logging.logger.warn "#{command} has been running for #{complete - start} seconds."
|
154
|
-
end
|
155
|
-
|
156
|
-
rescue => e
|
157
|
-
Smartware::Logging.logger.error "Execution of #{command} failed:"
|
158
|
-
Smartware::Logging.logger.error e.to_s
|
159
|
-
e.backtrace.each do |line|
|
160
|
-
Smartware::Logging.logger.error line
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
def periodic
|
167
|
-
loop do
|
168
|
-
begin
|
169
|
-
if @commands.empty?
|
170
|
-
@commands.push :monitor
|
171
|
-
@commands.push :get_money if @accepting
|
172
|
-
end
|
173
|
-
|
174
|
-
sleep 0.5
|
175
|
-
rescue => e
|
176
|
-
Smartware::Logging.logger.error "Error in periodic failed:"
|
177
|
-
Smartware::Logging.logger.error e.to_s
|
178
|
-
e.backtrace.each do |line|
|
179
|
-
Smartware::Logging.logger.error line
|
180
|
-
end
|
181
|
-
end
|
131
|
+
@status[:model] = @device.model
|
132
|
+
@status[:version] = @device.version
|
133
|
+
@status[:casette] = error != DROP_CASETTE_OUT_OF_POSITION
|
182
134
|
end
|
183
135
|
end
|
184
136
|
end
|