smartware 0.1.30 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,13 +12,20 @@ opts = Trollop.options do
12
12
  opt :log, "Log file to use", type: String
13
13
  end
14
14
 
15
- p opts[:log]
16
-
17
15
  Smartware::Logging.destination = opts[:log] if opts[:log]
18
16
  Smartware::Logging.init
19
17
 
20
18
  begin
21
- Smartware::Service.start(opts[:config])
19
+ Smartware::ProcessManager.init
20
+ service = Smartware::Service.new opts[:config]
21
+
22
+ Smartware::Logging.logger.info "Initializing Smartware"
23
+
24
+ service.start
25
+
26
+ Smartware::Logging.logger.info "Smartware ready"
27
+
28
+ service.join
22
29
  rescue => e
23
30
  Smartware::Logging.logger.error e.message
24
31
  Smartware::Logging.logger.error e.backtrace.join("\n")
@@ -0,0 +1,10 @@
1
+ interfaces:
2
+ - name: CashAcceptor
3
+ uri: druby://localhost:6001
4
+ driver: Dummy
5
+ - name: Modem
6
+ uri: druby://localhost:6002
7
+ driver: Dummy
8
+ - name: Printer
9
+ uri: druby://localhost:6005
10
+ driver: Dummy
@@ -1,10 +1,25 @@
1
+ require 'thread'
2
+ require 'yaml'
3
+ require 'active_support/core_ext/string/inflections'
4
+ require 'drb'
5
+ require 'redcarpet'
6
+
1
7
  require 'smartkiosk/common'
8
+
2
9
  require 'smartware/version'
3
10
  require 'smartware/logging'
4
11
  require 'smartware/service'
12
+ require 'smartware/process_manager'
5
13
  require 'smartware/clients/cash_acceptor'
6
14
  require 'smartware/clients/printer'
7
15
  require 'smartware/clients/modem'
16
+ require 'smartware/clients/watchdog'
17
+ require 'smartware/interfaces/interface'
18
+ require 'smartware/interfaces/cash_acceptor'
19
+ require 'smartware/interfaces/modem'
20
+ require 'smartware/interfaces/printer'
21
+ require 'smartware/interfaces/watchdog'
22
+ require 'smartware/connection_monitor'
8
23
 
9
24
  module Smartware
10
25
 
@@ -24,4 +39,8 @@ module Smartware
24
39
  Smartware::Client::Modem
25
40
  end
26
41
 
42
+ def self.watchdog
43
+ Smartware::Client::Watchdog
44
+ end
45
+
27
46
  end
@@ -32,8 +32,14 @@ module Smartware
32
32
  'No device'
33
33
  end
34
34
 
35
- def self.print(filepath)
36
- @device.print filepath
35
+ def self.print(text, max_time = 30)
36
+ @device.print text, max_time
37
+ rescue => e
38
+ 'No device'
39
+ end
40
+
41
+ def self.print_text(text, max_time = 30)
42
+ @device.print_text text, max_time
37
43
  rescue => e
38
44
  'No device'
39
45
  end
@@ -0,0 +1,36 @@
1
+ require 'drb'
2
+
3
+ module Smartware
4
+ module Client
5
+
6
+ module Watchdog
7
+
8
+ DRb.start_service
9
+ @device = DRbObject.new_with_uri('druby://localhost:6003')
10
+
11
+ def self.error
12
+ @device.error
13
+ rescue => e
14
+ 'No device'
15
+ end
16
+
17
+ def self.model
18
+ @device.model
19
+ rescue => e
20
+ 'No device'
21
+ end
22
+
23
+ def self.version
24
+ @device.version
25
+ rescue => e
26
+ 'No device'
27
+ end
28
+
29
+ def self.reboot_modem
30
+ @device.reboot_modem
31
+ rescue => e
32
+ 'No device'
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ module Smartware
2
+ class ConnectionMonitor
3
+ def initialize(timeout)
4
+ @timeout = [ timeout / 5, 1 ].max
5
+ @counter = @timeout
6
+ end
7
+
8
+ def run
9
+ loop do
10
+ if Smartware.modem.error.nil?
11
+ @counter = @timeout
12
+ else
13
+ if @counter == 0
14
+ Smartware.watchdog.reboot_modem
15
+ @counter = @timeout
16
+ else
17
+ @counter -= 1
18
+ end
19
+ end
20
+
21
+ sleep 5
22
+ end
23
+ end
24
+ end
25
+ end
@@ -4,6 +4,11 @@
4
4
  #
5
5
  require 'serialport'
6
6
 
7
+ COMMUNICATION_ERROR = 1
8
+ DROP_CASETTE_FULL = 2
9
+ DROP_CASETTE_OUT_OF_POSITION = 3
10
+
11
+
7
12
  module Smartware
8
13
  module Driver
9
14
  module CashAcceptor
@@ -45,32 +50,31 @@ module Smartware
45
50
  }
46
51
 
47
52
  ERRORS = {
48
- "41" => "23", # 'Drop Cassette Full',
49
- "42" => "24", # 'Drop Cassette out of position. The Bill Validator has detected the drop cassette to be open or removed.',
50
- "43" => "25", # 'Validator Jammed. A bill(s) has jammed in the acceptance path.',
51
- "44" => "26", # 'Drop Cassette Jammed. A bill has jammed in drop cassette.',
52
- "45" => "27", # 'Cheated. Intentions of the user to deceive the Bill Validator are detected',
53
- "46" => "28", # 'Pause. Bill Validator stops motion of the second bill until the second bill is removed.',
54
- "47" => "29", # 'Bill validator failure',
55
- "50" => "30", # 'Stack Motor Failure',
56
- "51" => "31", # 'Transport Motor Speed Failure',
57
- "52" => "32", # 'Transport Motor Failure',
58
- "53" => "33", # 'Aligning Motor Failure',
59
- "54" => "34", # 'Initial Cassette Status Failure',
60
- "55" => "35", # 'Optic Canal Failure',
61
- "56" => "36", # 'Magnetic Canal Failure',
62
- "5f" => "37" # 'Capacitance Canal Failure'
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
63
68
  }
64
69
 
65
70
  NOMINALS = { "2" => 10, "3" => 50, "4" => 100, "5" => 500, "6" => 1000, "7" => 5000 }
66
71
 
67
- def initialize(port)
68
- @port = port
72
+ def initialize(config)
73
+ @port = config["port"]
69
74
  end
70
75
 
71
76
  def cassette?
72
- return false if error == "24"
73
- true
77
+ error != Interface::CashAcceptor::DROP_CASETTE_OUT_OF_POSITION
74
78
  end
75
79
 
76
80
  def model
@@ -92,12 +96,12 @@ module Smartware
92
96
  def error
93
97
  res = poll
94
98
  ack
95
- return false if res != nil and CCNET::STATUSES.keys.include?(res[3])
96
- return false if res == nil
99
+ return nil if res != nil and CCNET::STATUSES.keys.include?(res[3])
100
+ return nil if res == nil
97
101
 
98
102
  result = check_error(res)
99
103
  rescue
100
- -1
104
+ Interface::CashAcceptor::COMMUNICATION_ERROR
101
105
  end
102
106
 
103
107
  def current_banknote
@@ -108,7 +112,7 @@ module Smartware
108
112
  ack
109
113
 
110
114
  result = check_error(res)
111
- if res != nil and res[2] == "7" and res[3] == "80" and CCNET::NOMINALS.keys.include?(res[4]) # has money?
115
+ if !res.nil? and res[2] == "7" and res[3] == "80" and CCNET::NOMINALS.keys.include?(res[4]) # has money?
112
116
  result = CCNET::NOMINALS[res[4]]
113
117
  end
114
118
  result
@@ -156,7 +160,7 @@ module Smartware
156
160
  if CCNET::ERRORS.keys.include? res[3]
157
161
  res[3] == "47" ? CCNET::ERRORS[res[4]] : CCNET::ERRORS[res[3]] # More details for 47 error
158
162
  else
159
- false
163
+ nil
160
164
  end
161
165
  end
162
166
 
@@ -5,8 +5,8 @@ module Smartware
5
5
 
6
6
  class Dummy
7
7
 
8
- def initialize(port)
9
- @port = port
8
+ def initialize(config)
9
+
10
10
  end
11
11
 
12
12
  def model
@@ -23,7 +23,7 @@ module Smartware
23
23
  end
24
24
 
25
25
  def error
26
- false
26
+ nil
27
27
  end
28
28
 
29
29
  def current_banknote
@@ -6,7 +6,7 @@ module Smartware
6
6
  class Dummy
7
7
 
8
8
  def initialize(config)
9
- @port = config
9
+
10
10
  end
11
11
 
12
12
  def model
@@ -18,7 +18,7 @@ module Smartware
18
18
  end
19
19
 
20
20
  def error
21
- false
21
+ nil
22
22
  end
23
23
 
24
24
  def signal_level
@@ -11,7 +11,7 @@ module Smartware
11
11
  def initialize(config)
12
12
  @config = config
13
13
  @state = :closed
14
- @error = "not initialized yet"
14
+ @error = Interface::Modem::MODEM_NOT_AVAILABLE
15
15
  @mux = nil
16
16
  @status_channel = nil
17
17
  @info_requested = false
@@ -75,7 +75,7 @@ module Smartware
75
75
  @status_channel = @mux.allocate(@config["status_channel"]).open
76
76
  @chatter = CMUX::ModemChatter.new @status_channel
77
77
  @chatter.subscribe "CUSD", self
78
- @error = false
78
+ @error = nil
79
79
  @ussd_interval = 0
80
80
  Smartware::Logging.logger.info "modem ready"
81
81
  rescue => e
@@ -184,7 +184,7 @@ module Smartware
184
184
  def close_modem(reason)
185
185
  Smartware::Logging.logger.warn "#{reason}"
186
186
 
187
- @error = reason
187
+ @error = Interface::Modem::MODEM_NOT_AVAILABLE
188
188
 
189
189
  begin
190
190
  @mux.close
@@ -1,24 +1,35 @@
1
- # coding: utf-8
2
1
  module Smartware
3
2
  module Driver
4
3
  module Printer
5
4
 
6
5
  class Dummy
7
6
 
8
- def initialize(port)
7
+ def initialize(config)
9
8
 
10
9
  end
11
10
 
12
11
  def error
13
- false
12
+ nil
14
13
  end
15
14
 
16
15
  def model
17
16
  'Generic printer'
18
17
  end
19
18
 
20
- def version
21
- 'from hell'
19
+ def version
20
+ 'from hell'
21
+ end
22
+
23
+ def print(data)
24
+
25
+ end
26
+
27
+ def query
28
+
29
+ end
30
+
31
+ def new_render
32
+ Redcarpet::Render::HTML.new
22
33
  end
23
34
  end
24
35
 
@@ -0,0 +1,272 @@
1
+ require "serialport"
2
+
3
+ module Smartware
4
+ module Driver
5
+ module Printer
6
+
7
+ class EscPos
8
+ QUERY = [
9
+ 0x10, 0x04, 20, # Transmit Full Status
10
+ ].pack("C*")
11
+
12
+ MODEL_QUERY = [
13
+ 0x1D, 0x49, 0x31, # Printer model ID
14
+ ].pack("C*")
15
+
16
+ VERSION_QUERY = [
17
+ 0x1D, 0x49, 0x33, # Printer ROM version
18
+ ].pack("C*")
19
+
20
+ attr_reader :error, :model, :version, :status
21
+
22
+ def initialize(config)
23
+ @sp = SerialPort.new config["port"], 115200, 8, 1, SerialPort::NONE
24
+ @sp.read_timeout = 500
25
+
26
+ @error = nil
27
+ @status = :ready
28
+ @model = ''
29
+ @version = ''
30
+ @buf = "".force_encoding("BINARY")
31
+ end
32
+
33
+ def query
34
+ begin
35
+ @sp.write QUERY
36
+ magic, paper_status, user_status, recoverable, unrecoverable = read_response(6).unpack("nC*")
37
+
38
+ raise "invalid magic: #{magic.to_s 16}" if magic != 0x100F
39
+
40
+ if unrecoverable != 0
41
+ @status = :error
42
+
43
+ @error = Interface::Printer::HARDWARE_ERROR
44
+ elsif (paper_status & 1) != 0
45
+ @status = :error
46
+ @error = Interface::Printer::OUT_OF_PAPER
47
+
48
+ elsif (recoverable != 0) || ((user_status & 3) != 0)
49
+ @status = :transient_error
50
+ @error = Interface::Printer::HARDWARE_ERROR
51
+
52
+ elsif (paper_status & 4) != 0
53
+ @status = :warning
54
+ @error = Interface::Printer::PAPER_NEAR_END
55
+ end
56
+
57
+ @sp.write MODEL_QUERY
58
+ printer_model, = read_response(1).unpack("C*")
59
+
60
+ @sp.write VERSION_QUERY
61
+ printer_version = read_response(4)
62
+
63
+ @model = "ESC/POS Printer #{printer_model.to_s 16}"
64
+ @version = "ROM #{printer_version}"
65
+
66
+ rescue => e
67
+ Smartware::Logging.logger.warn "Printer communication error: #{e}"
68
+ e.backtrace.each { |line| Smartware::Logging.logger.warn line }
69
+
70
+ @buf = ""
71
+
72
+ @error = Interface::Printer::COMMUNICATION_ERROR
73
+ @status = :error
74
+ end
75
+ end
76
+
77
+ def print(data)
78
+ begin
79
+ @sp.write data
80
+
81
+ start = Time.now
82
+
83
+ loop do
84
+ now = Time.now
85
+ break if now - start > 30
86
+
87
+ @sp.write QUERY
88
+ magic, paper_status, user_status, recoverable, unrecoverable = read_response(6).unpack("nC*")
89
+
90
+ raise "invalid magic: #{magic.to_s 16}" if magic != 0x100F
91
+
92
+ # paper not in motion
93
+ break if (user_status & 8) == 0
94
+ end
95
+ rescue => e
96
+ Smartware::Logging.logger.warn "Printer communication error: #{e}"
97
+ end
98
+ end
99
+
100
+ def new_render
101
+ Render.new
102
+ end
103
+
104
+ private
105
+
106
+ def read_response(bytes)
107
+ while @buf.length < bytes
108
+ @buf << @sp.sysread(128)
109
+ end
110
+
111
+ @buf.slice! 0...bytes
112
+ end
113
+ end
114
+
115
+ class Render < Redcarpet::Render::Base
116
+ START_PAGE = [
117
+ 0x1B, 0x40, # Initialize the printer
118
+ 0x1B, 0x52, 0x00, # Set characters set: USA
119
+ 0x1B, 0x74, 0x17, # Select code code: 866
120
+ 0x1B, 0x78, 0x02, # High quality
121
+ ].pack("C*")
122
+
123
+ END_PAGE = [
124
+ 0x1C, 0xC0, 0xAA, 0x0F, 0xEE, 0x0B, 0x34, # Total cut and automatic paper moving back
125
+ ].pack("C*")
126
+
127
+ ALT_FONT = 1
128
+ BOLD = 8
129
+ DOUBLEH = 16
130
+ DOUBLEW = 32
131
+ SCRIPT = 64
132
+ UNDERLINE = 128
133
+
134
+
135
+ def block_code(code, language)
136
+ code
137
+ end
138
+
139
+ def block_quote(text)
140
+ text
141
+ end
142
+
143
+ def block_html(text)
144
+ text
145
+ end
146
+
147
+ def header(text, level)
148
+ styled(DOUBLEW | DOUBLEH) { text } + "\n"
149
+ end
150
+
151
+ def hrule
152
+ "_" * 32 + "\n"
153
+ end
154
+
155
+ def list(text, type)
156
+ items = text.split "\x01"
157
+ out = ""
158
+
159
+ case type
160
+ when :unordered
161
+
162
+ items.each_with_index do |text, index|
163
+ out << "- #{text}\n"
164
+ end
165
+
166
+ when :ordered
167
+
168
+ items.each_with_index do |text, index|
169
+ out << "#{index + 1}. #{text}\n"
170
+ end
171
+ end
172
+
173
+ out
174
+ end
175
+
176
+ def list_item(text, type)
177
+ "#{text}\x01"
178
+ end
179
+
180
+ def paragraph(text)
181
+ text + "\n\n"
182
+ end
183
+
184
+ def table(header, body)
185
+ ""
186
+ end
187
+
188
+ def tablerow(text)
189
+ ""
190
+ end
191
+
192
+ def tablecell(text, align)
193
+ ""
194
+ end
195
+
196
+ def autolink(link, type)
197
+ link
198
+ end
199
+
200
+ def codespan(text)
201
+ text
202
+ end
203
+
204
+ def double_emphasis(text)
205
+ styled(BOLD) { text }
206
+ end
207
+
208
+ def emphasis(text)
209
+ styled(SCRIPT) { text }
210
+ end
211
+
212
+ def image(link, title, alt)
213
+ alt
214
+ end
215
+
216
+ def linebreak
217
+ "\n"
218
+ end
219
+
220
+ def link(link, title, content)
221
+ content
222
+ end
223
+
224
+ def raw_html(text)
225
+ ""
226
+ end
227
+
228
+ def triple_emphasis(text)
229
+ styled(UNDERLINE) { text }
230
+ end
231
+
232
+ def normal_text(text)
233
+ text.encode("CP866").gsub "\n", " "
234
+ end
235
+
236
+ def doc_header
237
+ @mode = 0
238
+
239
+ START_PAGE + set_mode
240
+ end
241
+
242
+ def doc_footer
243
+ END_PAGE
244
+ end
245
+
246
+ private
247
+
248
+ def set_mode
249
+ [ 0x1B, 0x21, @mode ].pack("C*")
250
+ end
251
+
252
+ def styled(bit, &block)
253
+ out = ""
254
+
255
+ old_mode = @mode
256
+ @mode |= bit
257
+
258
+ out << set_mode if @mode != old_mode
259
+
260
+ out << yield
261
+
262
+ if @mode != old_mode
263
+ @mode = old_mode
264
+ out << set_mode
265
+ end
266
+
267
+ out
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end