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 ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .idea
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in smartware.gemspec
4
+ gemspec
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,25 @@
1
+ # coding: utf-8
2
+ module Smartware
3
+ module Driver
4
+ module Printer
5
+
6
+ class Dummy
7
+
8
+ def initialize(port)
9
+
10
+ end
11
+
12
+ def error
13
+ false
14
+ end
15
+
16
+ def model
17
+ 'Dummy printer'
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+
@@ -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
+
@@ -0,0 +1,3 @@
1
+ module Smartware
2
+ VERSION = "0.1.18"
3
+ end
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.17
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: