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.
@@ -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.info "Session open, unlimited"
61
+ Smartware::Logging.logger.debug "Session open, unlimited"
49
62
  else
50
63
  @limit = limit_min..limit_max
51
64
 
52
- Smartware::Logging.logger.info "Session open, limit: #{@limit}"
65
+ Smartware::Logging.logger.debug "Session open, limit: #{@limit}"
53
66
  end
54
67
 
55
- @commands.push :open
56
- @commands.push :get_money
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.info "Session closed"
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 execute_open
83
- @device.reset
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 execute_get_money
91
- res = @device.current_banknote
112
+ def stacked(banknote)
113
+ value = banknote.value
92
114
 
93
- case res
94
- when Integer
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
- if !@limit.nil? && @limit.end > 0 && @limit.end == self.cashsum
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 execute_close
122
- @device.cancel_accept
123
- Smartware::Logging.logger.info "Cash acceptor close"
122
+ def returned(banknote)
123
+ value = banknote.value
124
124
 
125
- @accepting = false
125
+ Smartware::Logging.logger.debug "cash acceptor: bill returned, #{value}"
126
126
  end
127
127
 
128
- def execute_monitor
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[:cassette] = cassette
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