smartware 0.1.30 → 0.2

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