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.
- data/bin/smartware +10 -3
- data/config/smartware.yml.sample +10 -0
- data/lib/smartware.rb +19 -0
- data/lib/smartware/clients/printer.rb +8 -2
- data/lib/smartware/clients/watchdog.rb +36 -0
- data/lib/smartware/connection_monitor.rb +25 -0
- data/lib/smartware/drivers/cash_acceptor/ccnet.rb +28 -24
- data/lib/smartware/drivers/cash_acceptor/dummy.rb +3 -3
- data/lib/smartware/drivers/modem/dummy.rb +2 -2
- data/lib/smartware/drivers/modem/standard.rb +3 -3
- data/lib/smartware/drivers/printer/dummy.rb +16 -5
- data/lib/smartware/drivers/printer/esc_pos.rb +272 -0
- data/lib/smartware/drivers/watchdog/dummy.rb +29 -0
- data/lib/smartware/drivers/watchdog/watchdog_daemon.rb +40 -0
- data/lib/smartware/interfaces/cash_acceptor.rb +154 -109
- data/lib/smartware/interfaces/interface.rb +47 -0
- data/lib/smartware/interfaces/modem.rb +29 -58
- data/lib/smartware/interfaces/printer.rb +78 -60
- data/lib/smartware/interfaces/watchdog.rb +26 -0
- data/lib/smartware/process_manager.rb +40 -0
- data/lib/smartware/service.rb +26 -70
- data/lib/smartware/version.rb +1 -1
- data/smartware.gemspec +3 -1
- metadata +44 -4
- data/lib/smartware/drivers/printer/tg24xx.rb +0 -64
data/bin/smartware
CHANGED
@@ -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::
|
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")
|
data/lib/smartware.rb
CHANGED
@@ -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(
|
36
|
-
@device.print
|
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" =>
|
49
|
-
"42" =>
|
50
|
-
"43" =>
|
51
|
-
"44" =>
|
52
|
-
"45" =>
|
53
|
-
"46" =>
|
54
|
-
"47" =>
|
55
|
-
"50" =>
|
56
|
-
"51" =>
|
57
|
-
"52" =>
|
58
|
-
"53" =>
|
59
|
-
"54" =>
|
60
|
-
"55" =>
|
61
|
-
"56" =>
|
62
|
-
"5f" =>
|
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(
|
68
|
-
@port = port
|
72
|
+
def initialize(config)
|
73
|
+
@port = config["port"]
|
69
74
|
end
|
70
75
|
|
71
76
|
def cassette?
|
72
|
-
|
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
|
96
|
-
return
|
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
|
-
|
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
|
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
|
-
|
163
|
+
nil
|
160
164
|
end
|
161
165
|
end
|
162
166
|
|
@@ -11,7 +11,7 @@ module Smartware
|
|
11
11
|
def initialize(config)
|
12
12
|
@config = config
|
13
13
|
@state = :closed
|
14
|
-
@error =
|
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 =
|
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 =
|
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(
|
7
|
+
def initialize(config)
|
9
8
|
|
10
9
|
end
|
11
10
|
|
12
11
|
def error
|
13
|
-
|
12
|
+
nil
|
14
13
|
end
|
15
14
|
|
16
15
|
def model
|
17
16
|
'Generic printer'
|
18
17
|
end
|
19
18
|
|
20
|
-
|
21
|
-
|
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
|