smartware 0.1.17 → 0.1.18
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/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/bin/smartware +35 -0
- data/lib/smartware.rb +25 -0
- data/lib/smartware/clients/cash_acceptor.rb +37 -0
- data/lib/smartware/clients/modem.rb +29 -0
- data/lib/smartware/clients/printer.rb +37 -0
- data/lib/smartware/drivers/cash_acceptor/ccnet.rb +202 -0
- data/lib/smartware/drivers/cash_acceptor/dummy.rb +52 -0
- data/lib/smartware/drivers/modem/dummy.rb +38 -0
- data/lib/smartware/drivers/modem/standard.rb +89 -0
- data/lib/smartware/drivers/printer/dummy.rb +25 -0
- data/lib/smartware/drivers/printer/tg24xx.rb +61 -0
- data/lib/smartware/interfaces/cash_acceptor.rb +135 -0
- data/lib/smartware/interfaces/modem.rb +70 -0
- data/lib/smartware/interfaces/printer.rb +76 -0
- data/lib/smartware/logging.rb +31 -0
- data/lib/smartware/service.rb +38 -0
- data/lib/smartware/version.rb +3 -0
- data/smartware.gemspec +23 -0
- metadata +27 -3
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2012 ya.jeks
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Smartware
|
|
2
|
+
|
|
3
|
+
TODO: Write a gem description
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'smartware'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install smartware
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
TODO: Write usage instructions here
|
|
22
|
+
|
|
23
|
+
## Contributing
|
|
24
|
+
|
|
25
|
+
1. Fork it
|
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/smartware
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
trap 'TTIN' do
|
|
4
|
+
Thread.list.each do |thread|
|
|
5
|
+
if thread.backtrace
|
|
6
|
+
puts thread.backtrace.join("\n")
|
|
7
|
+
else
|
|
8
|
+
puts "<no backtrace available>"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require 'smartware/service'
|
|
14
|
+
require 'dante'
|
|
15
|
+
|
|
16
|
+
runner = Dante::Runner.new('smartware')
|
|
17
|
+
runner.description = "Smartkiosk hardware control daemon"
|
|
18
|
+
runner.with_options do |opts|
|
|
19
|
+
opts.on("-c", "--config-file FILE", String, "Hardware config file to use. Sample: --config-file=/path/to/smartware.yml") do |x|
|
|
20
|
+
options[:config] = x
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
runner.execute do |opts|
|
|
25
|
+
abort "You should specify hardware configuration file path(--config-file=/path/to/smartware.yml)" if opts[:config].nil?
|
|
26
|
+
|
|
27
|
+
begin
|
|
28
|
+
Smartware::Service.start(opts[:config])
|
|
29
|
+
rescue => e
|
|
30
|
+
raise e if $DEBUG
|
|
31
|
+
STDERR.puts e.message
|
|
32
|
+
STDERR.puts e.backtrace.join("\n")
|
|
33
|
+
exit 1
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/smartware.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'smartware/clients/cash_acceptor'
|
|
2
|
+
require 'smartware/clients/printer'
|
|
3
|
+
require 'smartware/clients/modem'
|
|
4
|
+
|
|
5
|
+
module Smartware
|
|
6
|
+
|
|
7
|
+
def self.devices
|
|
8
|
+
yield self
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.cash_acceptor
|
|
12
|
+
Smartware::Client::CashAcceptor
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.printer
|
|
16
|
+
Smartware::Client::Printer
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.modem
|
|
20
|
+
Smartware::Client::Modem
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'drb'
|
|
2
|
+
|
|
3
|
+
module Smartware
|
|
4
|
+
module Client
|
|
5
|
+
|
|
6
|
+
module CashAcceptor
|
|
7
|
+
|
|
8
|
+
DRb.start_service
|
|
9
|
+
@device = DRbObject.new_with_uri('druby://localhost:6001')
|
|
10
|
+
|
|
11
|
+
def self.open(limit_min = nil, limit_max = nil)
|
|
12
|
+
@device.open_session(limit_min, limit_max)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.close
|
|
16
|
+
@device.close_session
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.status
|
|
20
|
+
@device.status
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.model
|
|
24
|
+
@device.model
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.banknotes
|
|
28
|
+
@device.banknotes
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.sum
|
|
32
|
+
@device.cashsum
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'drb'
|
|
2
|
+
|
|
3
|
+
module Smartware
|
|
4
|
+
module Client
|
|
5
|
+
|
|
6
|
+
module Modem
|
|
7
|
+
|
|
8
|
+
DRb.start_service
|
|
9
|
+
@device = DRbObject.new_with_uri('druby://localhost:6002')
|
|
10
|
+
|
|
11
|
+
def self.error
|
|
12
|
+
@device.error
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.model
|
|
16
|
+
@device.model
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.balance
|
|
20
|
+
@device.balance
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.signal_level
|
|
24
|
+
@device.signal_level
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'drb'
|
|
2
|
+
|
|
3
|
+
module Smartware
|
|
4
|
+
module Client
|
|
5
|
+
|
|
6
|
+
module Printer
|
|
7
|
+
|
|
8
|
+
DRb.start_service
|
|
9
|
+
@device = DRbObject.new_with_uri('druby://localhost:6005')
|
|
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.test
|
|
24
|
+
@device.test
|
|
25
|
+
rescue => e
|
|
26
|
+
'No device'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.print(filepath)
|
|
30
|
+
@device.print filepath
|
|
31
|
+
rescue => e
|
|
32
|
+
'No device'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
#
|
|
3
|
+
# CCNET protocol driver for CashCode bill validator
|
|
4
|
+
#
|
|
5
|
+
require 'serialport'
|
|
6
|
+
|
|
7
|
+
module Smartware
|
|
8
|
+
module Driver
|
|
9
|
+
module CashAcceptor
|
|
10
|
+
|
|
11
|
+
class CCNET
|
|
12
|
+
|
|
13
|
+
RESET = 0x30
|
|
14
|
+
GET_STATUS = 0x31
|
|
15
|
+
SET_SECURITY = 0x32
|
|
16
|
+
POLL = 0x33
|
|
17
|
+
ENABLE_BILL_TYPES = 0x34
|
|
18
|
+
STACK = 0x35
|
|
19
|
+
RETURN = 0x36
|
|
20
|
+
IDENTIFICATION = 0x37
|
|
21
|
+
HOLD = 0x38
|
|
22
|
+
SET_BARCODE_PARAMETERS = 0x39
|
|
23
|
+
EXTRACT_BARCODE_DATA = 0x3A
|
|
24
|
+
GET_BILL_TABLE = 0x41
|
|
25
|
+
GET_CRC32_OF_THE_CODE = 0x51
|
|
26
|
+
DOWNLOAD = 0x50
|
|
27
|
+
REQUEST_STATISTICS = 0x60
|
|
28
|
+
ACK = 0x00
|
|
29
|
+
DISPENCE = 0x3C
|
|
30
|
+
|
|
31
|
+
STATUSES = {
|
|
32
|
+
"10" => "10", # 'Power up',
|
|
33
|
+
"11" => "11", # 'Power Up with Bill in Validator',
|
|
34
|
+
"12" => "12", # 'Power Up with Bill in Stacker',
|
|
35
|
+
"13" => "13", # 'Initialize',
|
|
36
|
+
"14" => "14", # 'Idling',
|
|
37
|
+
"15" => "15", # 'Accepting',
|
|
38
|
+
"17" => "16", # 'Stacking',
|
|
39
|
+
"18" => "17", # 'Returning',
|
|
40
|
+
"19" => "18", # 'Unit Disabled',
|
|
41
|
+
"1a" => "19", # 'Holding',
|
|
42
|
+
"1b" => "20", # 'Device Busy',
|
|
43
|
+
"1c" => "21", # 'Rejecting',
|
|
44
|
+
"82" => "22" # 'Bill returned'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
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'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
NOMINALS = { "2" => 10, "3" => 50, "4" => 100, "5" => 500, "6" => 1000, "7" => 5000 }
|
|
66
|
+
|
|
67
|
+
def initialize(port)
|
|
68
|
+
@port = port
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def cassette?
|
|
72
|
+
return false if error == "24"
|
|
73
|
+
true
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def model
|
|
77
|
+
if answer = send([IDENTIFICATION], false)
|
|
78
|
+
answer = answer[2..answer.length]
|
|
79
|
+
return "#{answer[0..15]} #{answer[16..27]} #{answer[28..34].unpack("C*")}"
|
|
80
|
+
else
|
|
81
|
+
return "Unknown device answer"
|
|
82
|
+
end
|
|
83
|
+
rescue
|
|
84
|
+
-1
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def error
|
|
88
|
+
res = poll
|
|
89
|
+
ack
|
|
90
|
+
return false if res != nil and CCNET::STATUSES.keys.include?(res[3])
|
|
91
|
+
return false if res == nil
|
|
92
|
+
|
|
93
|
+
result = check_error(res)
|
|
94
|
+
rescue
|
|
95
|
+
-1
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def current_banknote
|
|
99
|
+
poll
|
|
100
|
+
ack
|
|
101
|
+
hold
|
|
102
|
+
res = poll
|
|
103
|
+
ack
|
|
104
|
+
|
|
105
|
+
result = check_error(res)
|
|
106
|
+
if res != nil and res[2] == "7" and res[3] == "80" and CCNET::NOMINALS.keys.include?(res[4]) # has money?
|
|
107
|
+
result = CCNET::NOMINALS[res[4]]
|
|
108
|
+
end
|
|
109
|
+
result
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def get_status
|
|
113
|
+
send([GET_STATUS])
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def reset
|
|
117
|
+
send([RESET])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def ack
|
|
121
|
+
send([ACK])
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def stack
|
|
125
|
+
send([STACK])
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def return
|
|
129
|
+
send([RETURN])
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def hold
|
|
133
|
+
send([])
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def poll
|
|
137
|
+
send([POLL])
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def accept
|
|
141
|
+
send([ENABLE_BILL_TYPES,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF])
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def cancel_accept
|
|
145
|
+
send([ENABLE_BILL_TYPES,0x00,0x00,0x00,0x00,0x00,0x00])
|
|
146
|
+
get_status
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
def check_error(res)
|
|
151
|
+
if CCNET::ERRORS.keys.include? res[3]
|
|
152
|
+
res[3] == "47" ? CCNET::ERRORS[res[4]] : CCNET::ERRORS[res[3]] # More details for 47 error
|
|
153
|
+
else
|
|
154
|
+
false
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def crc_for(msg)
|
|
159
|
+
polynom = 0x8408.to_i
|
|
160
|
+
msg = msg.collect{|o| o.to_i}
|
|
161
|
+
res = 0
|
|
162
|
+
tmpcrc = 0
|
|
163
|
+
0.upto(msg.length-1){|i|
|
|
164
|
+
tmpcrc = res ^ msg[i]
|
|
165
|
+
0.upto(7){
|
|
166
|
+
if tmpcrc & 1 != 0
|
|
167
|
+
tmpcrc = tmpcrc >> 1
|
|
168
|
+
tmpcrc = tmpcrc ^ polynom
|
|
169
|
+
else
|
|
170
|
+
tmpcrc = tmpcrc >> 1
|
|
171
|
+
end
|
|
172
|
+
}
|
|
173
|
+
res = tmpcrc
|
|
174
|
+
}
|
|
175
|
+
crc = tmpcrc
|
|
176
|
+
crc = ("%02x" % crc).rjust(4,"0")
|
|
177
|
+
crc = [Integer("0x"+crc[2..3]), Integer("0x"+crc[0..1])]
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def send(msg, parse_answer = true)
|
|
181
|
+
sp = SerialPort.new(@port, 9600, 8, 1, SerialPort::NONE)
|
|
182
|
+
sp.read_timeout = 100
|
|
183
|
+
message = [0x02, 0x03, 5 + msg.length]
|
|
184
|
+
message += msg
|
|
185
|
+
crc = crc_for(message)
|
|
186
|
+
message += crc
|
|
187
|
+
message = message.pack("C*")
|
|
188
|
+
sp.write message
|
|
189
|
+
ans = sp.gets
|
|
190
|
+
if ans
|
|
191
|
+
parse_answer ? res = ans.unpack("C*").map{|e| e.to_s(16) } : res = ans
|
|
192
|
+
else
|
|
193
|
+
res = nil
|
|
194
|
+
end
|
|
195
|
+
sp.close
|
|
196
|
+
res
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
module Smartware
|
|
3
|
+
module Driver
|
|
4
|
+
module CashAcceptor
|
|
5
|
+
|
|
6
|
+
class Dummy
|
|
7
|
+
|
|
8
|
+
def initialize(port)
|
|
9
|
+
@port = port
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def model
|
|
13
|
+
"Dummy cash acceptor v1.0"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def cassette?
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def error
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def current_banknote
|
|
25
|
+
return false if ( rand(9) < 6 )
|
|
26
|
+
[20, 40, 60, 80].sample
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def accept
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def cancel_accept
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def stack
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def return
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def reset
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
module Smartware
|
|
3
|
+
module Driver
|
|
4
|
+
module Modem
|
|
5
|
+
|
|
6
|
+
class Dummy
|
|
7
|
+
|
|
8
|
+
def initialize(port)
|
|
9
|
+
@port = port
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def model
|
|
13
|
+
'Dummy modem v1.0'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def error
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def signal_level
|
|
21
|
+
"-#{rand(90)*10+rand(9)} dbm"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# Method send ussd to operator and return only valid answer body
|
|
26
|
+
# Don`t call with method synchronously from app, method waits USSD answer 3 sec,
|
|
27
|
+
# Use some scheduler and buffer for balance value
|
|
28
|
+
#
|
|
29
|
+
def ussd(code="*100#")
|
|
30
|
+
"#{rand(90)*100+rand(9)} " + %w(руб. dollars тэньге).sample
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
require 'serialport'
|
|
3
|
+
|
|
4
|
+
module Smartware
|
|
5
|
+
module Driver
|
|
6
|
+
module Modem
|
|
7
|
+
|
|
8
|
+
class Standard
|
|
9
|
+
|
|
10
|
+
ERRORS = {
|
|
11
|
+
"-1" => -1, # invalid device answer
|
|
12
|
+
"10" => 10, # invalid ussd answer
|
|
13
|
+
"11" => 11, # invalid ussd answer size
|
|
14
|
+
"12" => 12, # invalid ussd body answer size
|
|
15
|
+
"20" => 20, # invalid modem model
|
|
16
|
+
"21" => 21, # invalid modem signal level
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def initialize(port)
|
|
20
|
+
@sp = SerialPort.new(port, 115200, 8, 1, SerialPort::NONE)
|
|
21
|
+
@sp.read_timeout = 100
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def error
|
|
25
|
+
@error ||= false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def model
|
|
29
|
+
res = send 'ATI'
|
|
30
|
+
res.shift
|
|
31
|
+
res.pop
|
|
32
|
+
res.join(' ')
|
|
33
|
+
rescue
|
|
34
|
+
@error = ERRORS["20"]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#
|
|
38
|
+
# Returns signal level in dbm
|
|
39
|
+
#
|
|
40
|
+
def signal_level
|
|
41
|
+
res = send 'AT+CSQ'
|
|
42
|
+
value = res[1].gsub("+CSQ: ",'').split(',')[0].to_i
|
|
43
|
+
"#{(-113 + value * 2)} dbm"
|
|
44
|
+
rescue
|
|
45
|
+
@error = ERRORS["21"]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
#
|
|
49
|
+
# Method send ussd to operator and return only valid answer body
|
|
50
|
+
# Returns modem balance by default, it works for MTS and Megafon, use *102# for Beeline
|
|
51
|
+
# Do not call with method synchronously from app, method waits USSD answer some time,
|
|
52
|
+
# Use some scheduler and buffer for balance value
|
|
53
|
+
#
|
|
54
|
+
# Valid ussd answer sample: ["", "+CUSD: 2,\"003100310035002C003000300440002E00320031002E00330031002004310430043B002E0020\",72", "OK"]
|
|
55
|
+
#
|
|
56
|
+
def ussd(code="*100#")
|
|
57
|
+
res = self.send "AT+CUSD=1,\"#{code}\",15"
|
|
58
|
+
ussd_body = res[1].split(",")[1].gsub('"','') # Parse USSD message body
|
|
59
|
+
ussd_body.scan(/\w{4}/).map{|i| [i.hex].pack("U") }.join.strip # Encode USSD message from broken ucs2 to utf-8
|
|
60
|
+
rescue
|
|
61
|
+
@error = ERRORS["10"]
|
|
62
|
+
return false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def send(cmd)
|
|
66
|
+
read_port @sp # Port clear
|
|
67
|
+
|
|
68
|
+
@sp.write "AT\r\n"
|
|
69
|
+
check_ability = read_port @sp
|
|
70
|
+
return ERRORS["-1"] unless check_ability == ["AT", "OK"]
|
|
71
|
+
|
|
72
|
+
@sp.write "#{ cmd }\r\n"
|
|
73
|
+
answer = read_port @sp
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def read_port(io, read_timeout = 0.25)
|
|
77
|
+
return ERRORS["-1"] unless io
|
|
78
|
+
answer = ''
|
|
79
|
+
while IO.select [io], [], [], read_timeout
|
|
80
|
+
chr = io.getc.chr
|
|
81
|
+
answer << chr
|
|
82
|
+
end
|
|
83
|
+
answer.split(/[\r\n]+/)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
#
|
|
3
|
+
# Custom tg2480h printer functions
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
require 'serialport'
|
|
7
|
+
|
|
8
|
+
module Smartware
|
|
9
|
+
module Driver
|
|
10
|
+
module Printer
|
|
11
|
+
|
|
12
|
+
class TG24XX
|
|
13
|
+
|
|
14
|
+
def initialize(port)
|
|
15
|
+
@sp = SerialPort.new(port, 115200, 8, 1, SerialPort::NONE)
|
|
16
|
+
@sp.read_timeout = 100
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def error
|
|
20
|
+
res = send "\x1Dr1"
|
|
21
|
+
return "1" unless res # No answer
|
|
22
|
+
return "" if res == "0" # No error
|
|
23
|
+
return "2" if res == "f" # Paper not present
|
|
24
|
+
return "3" if res == "3" # Paper near end
|
|
25
|
+
rescue
|
|
26
|
+
-1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def model
|
|
30
|
+
model_code = send "\x1DI1"
|
|
31
|
+
rom_version = send "\x1DI3", false
|
|
32
|
+
model_name = case model_code
|
|
33
|
+
when "a7" then 'Custom TG2460H'
|
|
34
|
+
when "a8" then 'Custom TG2480H'
|
|
35
|
+
when "ac" then 'Custom TL80'
|
|
36
|
+
when "ad" then 'Custom TL60'
|
|
37
|
+
else 'Unknown printer'
|
|
38
|
+
end
|
|
39
|
+
"#{model_name}, ROM v#{rom_version}"
|
|
40
|
+
rescue
|
|
41
|
+
-1
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
def send(message, parse_answer = true)
|
|
46
|
+
@sp.write message
|
|
47
|
+
ans = @sp.gets
|
|
48
|
+
if ans
|
|
49
|
+
parse_answer ? res = ans.unpack("C*").map{|e| e.to_s(16) } : res = ans
|
|
50
|
+
else
|
|
51
|
+
res = nil
|
|
52
|
+
end
|
|
53
|
+
res.is_a?(Array) ? res[0] : res
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require 'drb'
|
|
2
|
+
require 'smartware/drivers/cash_acceptor/ccnet'
|
|
3
|
+
require 'smartware/drivers/cash_acceptor/dummy'
|
|
4
|
+
|
|
5
|
+
module Smartware
|
|
6
|
+
module Interface
|
|
7
|
+
|
|
8
|
+
module CashAcceptor
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Init vars
|
|
12
|
+
#
|
|
13
|
+
@configured = false
|
|
14
|
+
@no_limit = true
|
|
15
|
+
@banknotes = {}
|
|
16
|
+
@commands = %w(monitor)
|
|
17
|
+
@status = { :cassette => true }
|
|
18
|
+
|
|
19
|
+
#
|
|
20
|
+
# (re)configure device
|
|
21
|
+
#
|
|
22
|
+
def self.configure!(port=nil, driver=nil)
|
|
23
|
+
@device = Smartware::Driver::CashAcceptor.const_get(
|
|
24
|
+
Smartware::Service.config['cash_acceptor_driver']).new(
|
|
25
|
+
Smartware::Service.config['cash_acceptor_port'])
|
|
26
|
+
@session.kill if @session and @session.alive?
|
|
27
|
+
@commands = %w(monitor)
|
|
28
|
+
@device.cancel_accept
|
|
29
|
+
@session = self.start_poll!
|
|
30
|
+
Smartware::Logging.logger.info "Cash acceptor monitor started"
|
|
31
|
+
@configured = true
|
|
32
|
+
rescue => e
|
|
33
|
+
@configured = false
|
|
34
|
+
Smartware::Logging.logger.error e.message
|
|
35
|
+
Smartware::Logging.logger.error e.backtrace.join("\n")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.open_session(limit_min, limit_max)
|
|
39
|
+
@commands << 'open'
|
|
40
|
+
@banknotes = {}
|
|
41
|
+
if !limit_min.nil? && !limit_max.nil?
|
|
42
|
+
@no_limit = false
|
|
43
|
+
@limit_min = limit_min
|
|
44
|
+
@limit_max = limit_max
|
|
45
|
+
else
|
|
46
|
+
@no_limit = true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.close_session
|
|
51
|
+
@commands << 'close'
|
|
52
|
+
@limit_min = nil
|
|
53
|
+
@limit_max = nil
|
|
54
|
+
@no_limit = true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.configured?
|
|
58
|
+
@configured
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.status
|
|
62
|
+
@status
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.model
|
|
66
|
+
@status[:model]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.cashsum
|
|
70
|
+
@banknotes.inject(0){ |result, (key, value)| result + key.to_i*value.to_i }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.banknotes
|
|
74
|
+
@banknotes
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
#
|
|
78
|
+
# Session private method
|
|
79
|
+
#
|
|
80
|
+
private
|
|
81
|
+
def self.start_poll!
|
|
82
|
+
t = Thread.new do
|
|
83
|
+
loop do
|
|
84
|
+
case @commands[0]
|
|
85
|
+
when 'open'
|
|
86
|
+
@device.reset
|
|
87
|
+
@device.accept
|
|
88
|
+
Smartware::Logging.logger.info "Cash acceptor open: #{@commands}"
|
|
89
|
+
|
|
90
|
+
@commands.shift
|
|
91
|
+
@commands << 'get_money'
|
|
92
|
+
when 'get_money'
|
|
93
|
+
res = @device.current_banknote
|
|
94
|
+
if res.is_a? Integer # Have a banknote
|
|
95
|
+
if @no_limit or (@limit_min..@limit_max).include?(self.cashsum + res)
|
|
96
|
+
@device.stack
|
|
97
|
+
@banknotes[res] = (@banknotes[res]||0) + 1
|
|
98
|
+
Smartware::Logging.logger.info "Cash acceptor bill stacked, #{res}"
|
|
99
|
+
else
|
|
100
|
+
@device.return
|
|
101
|
+
Smartware::Logging.logger.info "Cash acceptor limit violation, return #{res}"
|
|
102
|
+
end
|
|
103
|
+
elsif res.is_a? String # Have a error, errors always as String
|
|
104
|
+
Smartware::Logging.logger.error "Cash acceptor error #{res}"
|
|
105
|
+
@status[:error] = res
|
|
106
|
+
end
|
|
107
|
+
@device.cancel_accept if !@no_limit && (@limit_max == self.cashsum and @limit_max > 0) # Close cash acceptor if current cashsum equal max-limit
|
|
108
|
+
|
|
109
|
+
@commands.shift
|
|
110
|
+
@commands << 'get_money' if @commands.empty?
|
|
111
|
+
when 'close'
|
|
112
|
+
@device.cancel_accept
|
|
113
|
+
Smartware::Logging.logger.info "Cash acceptor close: #{@commands}"
|
|
114
|
+
|
|
115
|
+
@commands.shift
|
|
116
|
+
when 'monitor'
|
|
117
|
+
@status[:error] = @device.error || ''
|
|
118
|
+
@status[:model] = @device.model
|
|
119
|
+
@status[:cassette] = @device.cassette?
|
|
120
|
+
@commands.shift
|
|
121
|
+
else
|
|
122
|
+
@commands << 'monitor'
|
|
123
|
+
end
|
|
124
|
+
sleep 0.5
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
self.configure!
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
DRb.start_service('druby://localhost:6001', Smartware::Interface::CashAcceptor)
|
|
135
|
+
DRb.thread.join
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'drb'
|
|
2
|
+
require 'smartware/drivers/modem/standard'
|
|
3
|
+
require 'smartware/drivers/modem/dummy'
|
|
4
|
+
|
|
5
|
+
module Smartware
|
|
6
|
+
module Interface
|
|
7
|
+
|
|
8
|
+
module Modem
|
|
9
|
+
|
|
10
|
+
@configured = false
|
|
11
|
+
@status = {}
|
|
12
|
+
|
|
13
|
+
def self.configure!(port=nil, driver=nil)
|
|
14
|
+
@device = Smartware::Driver::Modem.const_get(
|
|
15
|
+
Smartware::Service.config['modem_driver']).new(
|
|
16
|
+
Smartware::Service.config['modem_port'])
|
|
17
|
+
@session.kill if @session
|
|
18
|
+
@session = self.poll_status!
|
|
19
|
+
@configured = true
|
|
20
|
+
Smartware::Logging.logger.info 'Modem monitor started'
|
|
21
|
+
@status = {}
|
|
22
|
+
rescue => e
|
|
23
|
+
Smartware::Logging.logger.error e.message
|
|
24
|
+
Smartware::Logging.logger.error e.backtrace.join("\n")
|
|
25
|
+
@configured = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.configured?
|
|
29
|
+
@configured
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.error
|
|
33
|
+
@status[:error] || ''
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.model
|
|
37
|
+
@status[:model]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.balance
|
|
41
|
+
@status[:balance]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.signal_level
|
|
45
|
+
@status[:signal_level]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
def self.poll_status!
|
|
50
|
+
t = Thread.new do
|
|
51
|
+
loop do
|
|
52
|
+
@status[:signal_level] = @device.signal_level
|
|
53
|
+
@status[:model] = @device.model
|
|
54
|
+
@status[:error] = @device.error
|
|
55
|
+
|
|
56
|
+
balance = @device.ussd("*100#")
|
|
57
|
+
@status[:balance] = balance unless balance
|
|
58
|
+
sleep 5
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
self.configure!
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
DRb.start_service('druby://localhost:6002', Smartware::Interface::Modem)
|
|
69
|
+
DRb.thread.join
|
|
70
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'drb'
|
|
2
|
+
require 'smartware/drivers/printer/dummy'
|
|
3
|
+
require 'smartware/drivers/printer/tg24xx'
|
|
4
|
+
|
|
5
|
+
module Smartware
|
|
6
|
+
module Interface
|
|
7
|
+
|
|
8
|
+
module Printer
|
|
9
|
+
|
|
10
|
+
@configured = false
|
|
11
|
+
@status = {:error => ''}
|
|
12
|
+
@queue = []
|
|
13
|
+
|
|
14
|
+
def self.configure!(port=nil, driver=nil)
|
|
15
|
+
@device = Smartware::Driver::Printer.const_get(
|
|
16
|
+
Smartware::Service.config['printer_driver']).new(
|
|
17
|
+
Smartware::Service.config['printer_port'])
|
|
18
|
+
@session.kill if @session and @session.alive?
|
|
19
|
+
@session = self.start_monitor!
|
|
20
|
+
Smartware::Logging.logger.info 'Printer monitor started'
|
|
21
|
+
@configured = true
|
|
22
|
+
rescue => e
|
|
23
|
+
@configured = false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.configured?
|
|
27
|
+
@configured
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.error
|
|
31
|
+
@status[:error]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.model
|
|
35
|
+
@status[:model]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.print(filepath)
|
|
39
|
+
@queue << filepath unless filepath.nil?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.test
|
|
43
|
+
@queue << '/usr/share/cups/data/testprint'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
def self.start_monitor!
|
|
48
|
+
t = Thread.new do
|
|
49
|
+
loop do
|
|
50
|
+
if @queue.empty?
|
|
51
|
+
@status[:error] = @device.error || ''
|
|
52
|
+
@status[:model] = @device.model
|
|
53
|
+
else
|
|
54
|
+
begin
|
|
55
|
+
`lpr #{@queue[0]}`
|
|
56
|
+
Smartware::Logging.logger.info "Printed #{@queue[0]}"
|
|
57
|
+
@queue.shift
|
|
58
|
+
sleep 5
|
|
59
|
+
rescue => e
|
|
60
|
+
Smartware::Logging.logger.error e.message
|
|
61
|
+
Smartware::Logging.logger.error e.backtrace.join("\n")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
sleep 0.2
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
self.configure!
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
DRb.start_service('druby://localhost:6005', Smartware::Interface::Printer)
|
|
75
|
+
DRb.thread.join
|
|
76
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
|
|
3
|
+
module Smartware
|
|
4
|
+
module Logging
|
|
5
|
+
|
|
6
|
+
def self.logdir=(dir)
|
|
7
|
+
@logdir = dir
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.logger
|
|
11
|
+
@logger ||= begin
|
|
12
|
+
log = Logger.new(STDOUT)
|
|
13
|
+
log.level = Logger::INFO
|
|
14
|
+
log
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.logger=(val)
|
|
19
|
+
@logger = (val ? val : Logger.new('/dev/null'))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.logfile=(val)
|
|
23
|
+
@logfile = val
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.logfile
|
|
27
|
+
@logfile
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
require 'yaml'
|
|
3
|
+
require 'smartware/logging'
|
|
4
|
+
|
|
5
|
+
module Smartware
|
|
6
|
+
module Service
|
|
7
|
+
|
|
8
|
+
def self.config
|
|
9
|
+
@config
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.start(config_file)
|
|
13
|
+
$stdout.sync = true
|
|
14
|
+
|
|
15
|
+
@config = YAML.load File.read(File.expand_path(config_file))
|
|
16
|
+
|
|
17
|
+
Smartware::Logging.logger = Logger.new($stdout)
|
|
18
|
+
Smartware::Logging.logger.info "Smartware started at #{Time.now}"
|
|
19
|
+
|
|
20
|
+
@threads = %w(smartware/interfaces/cash_acceptor
|
|
21
|
+
smartware/interfaces/printer
|
|
22
|
+
smartware/interfaces/modem).inject([]){|arr, iface| arr << Thread.new{ require iface } }
|
|
23
|
+
|
|
24
|
+
@threads.map(&:join)
|
|
25
|
+
rescue => e
|
|
26
|
+
Smartware::Logging.logger.fatal e.message
|
|
27
|
+
Smartware::Logging.logger.fatal e.backtrace.join("\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.stop
|
|
31
|
+
@threads.map(&:kill)
|
|
32
|
+
Smartware::Logging.logger.info "Smartware shutdown at #{Time.now}"
|
|
33
|
+
exit 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
data/smartware.gemspec
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'smartware/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "smartware"
|
|
8
|
+
gem.version = Smartware::VERSION
|
|
9
|
+
gem.authors = ["Evgeni Sudarchikov", "Boris Staal"]
|
|
10
|
+
gem.email = ["e.sudarchikov@roundlake.ru", "boris@roundlake.ru"]
|
|
11
|
+
gem.description = %q{Smartware is the Smartkiosk hardware control daemon}
|
|
12
|
+
gem.summary = gem.description
|
|
13
|
+
gem.homepage = "https://github.com/roundlake/smartware"
|
|
14
|
+
|
|
15
|
+
gem.files = `git ls-files`.split($/)
|
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
gem.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
gem.add_dependency 'serialport'
|
|
21
|
+
gem.add_dependency 'dante'
|
|
22
|
+
|
|
23
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smartware
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.18
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -48,10 +48,34 @@ description: Smartware is the Smartkiosk hardware control daemon
|
|
|
48
48
|
email:
|
|
49
49
|
- e.sudarchikov@roundlake.ru
|
|
50
50
|
- boris@roundlake.ru
|
|
51
|
-
executables:
|
|
51
|
+
executables:
|
|
52
|
+
- smartware
|
|
52
53
|
extensions: []
|
|
53
54
|
extra_rdoc_files: []
|
|
54
|
-
files:
|
|
55
|
+
files:
|
|
56
|
+
- .gitignore
|
|
57
|
+
- Gemfile
|
|
58
|
+
- LICENSE.txt
|
|
59
|
+
- README.md
|
|
60
|
+
- Rakefile
|
|
61
|
+
- bin/smartware
|
|
62
|
+
- lib/smartware.rb
|
|
63
|
+
- lib/smartware/clients/cash_acceptor.rb
|
|
64
|
+
- lib/smartware/clients/modem.rb
|
|
65
|
+
- lib/smartware/clients/printer.rb
|
|
66
|
+
- lib/smartware/drivers/cash_acceptor/ccnet.rb
|
|
67
|
+
- lib/smartware/drivers/cash_acceptor/dummy.rb
|
|
68
|
+
- lib/smartware/drivers/modem/dummy.rb
|
|
69
|
+
- lib/smartware/drivers/modem/standard.rb
|
|
70
|
+
- lib/smartware/drivers/printer/dummy.rb
|
|
71
|
+
- lib/smartware/drivers/printer/tg24xx.rb
|
|
72
|
+
- lib/smartware/interfaces/cash_acceptor.rb
|
|
73
|
+
- lib/smartware/interfaces/modem.rb
|
|
74
|
+
- lib/smartware/interfaces/printer.rb
|
|
75
|
+
- lib/smartware/logging.rb
|
|
76
|
+
- lib/smartware/service.rb
|
|
77
|
+
- lib/smartware/version.rb
|
|
78
|
+
- smartware.gemspec
|
|
55
79
|
homepage: https://github.com/roundlake/smartware
|
|
56
80
|
licenses: []
|
|
57
81
|
post_install_message:
|