smartware 0.2.8 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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