smartware 0.1.23 → 0.1.24

Sign up to get free protection for your applications and to get access to all the features.
data/bin/smartware CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  trap 'TTIN' do
4
4
  Thread.list.each do |thread|
5
+ puts "Thread #{thread}:"
5
6
  if thread.backtrace
6
7
  puts thread.backtrace.join("\n")
7
8
  else
@@ -10,6 +11,12 @@ trap 'TTIN' do
10
11
  end
11
12
  end
12
13
 
14
+ require "rubygems"
15
+ begin
16
+ require "bundler/setup"
17
+ rescue
18
+ end
19
+
13
20
  require 'smartware/service'
14
21
  require 'dante'
15
22
 
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "bundler/setup"
5
+ require "cmux"
6
+
7
+ device, apn = ARGV
8
+
9
+ io = CMUX::IO::open_tty device
10
+ begin
11
+ chatter = CMUX::ModemChatter.new io
12
+
13
+ complete = false
14
+
15
+ chatter.command("+CGDCONT=1,\"IP\",\"#{apn}\";D*99***1#", 30) do |resp|
16
+ if resp.failure?
17
+ warn "GPRS activation failed: #{resp.error}"
18
+
19
+ exit 1
20
+ else
21
+ complete = true
22
+ end
23
+ end
24
+
25
+ until complete
26
+ CMUX::ModemChatter.poll [ chatter ]
27
+ end
28
+ ensure
29
+ io.close
30
+ end
31
+
32
+ Process.exec "/usr/sbin/pppd",
33
+ device, "115200",
34
+ "nodetach", "file", "/etc/ppp/options",
35
+ pgroup: true,
36
+ in: :close,
37
+ out: :close,
38
+ err: :close,
39
+ close_others: true
@@ -28,6 +28,10 @@ module Smartware
28
28
  @device.model
29
29
  end
30
30
 
31
+ def self.version
32
+ @device.version
33
+ end
34
+
31
35
  def self.banknotes
32
36
  @device.banknotes
33
37
  end
@@ -16,6 +16,10 @@ module Smartware
16
16
  @device.model
17
17
  end
18
18
 
19
+ def self.version
20
+ @device.version
21
+ end
22
+
19
23
  def self.balance
20
24
  @device.balance
21
25
  end
@@ -20,6 +20,12 @@ module Smartware
20
20
  'No device'
21
21
  end
22
22
 
23
+ def self.version
24
+ @device.version
25
+ rescue => e
26
+ 'No device'
27
+ end
28
+
23
29
  def self.test
24
30
  @device.test
25
31
  rescue => e
@@ -84,6 +84,11 @@ module Smartware
84
84
  -1
85
85
  end
86
86
 
87
+ def version
88
+ # TODO: implement this
89
+ "not implemented"
90
+ end
91
+
87
92
  def error
88
93
  res = poll
89
94
  ack
@@ -13,6 +13,11 @@ module Smartware
13
13
  "Generic cash acceptor"
14
14
  end
15
15
 
16
+ def version
17
+ # TODO: implement this
18
+ "not implemented"
19
+ end
20
+
16
21
  def cassette?
17
22
  true
18
23
  end
@@ -5,14 +5,18 @@ module Smartware
5
5
 
6
6
  class Dummy
7
7
 
8
- def initialize(port)
9
- @port = port
8
+ def initialize(config)
9
+ @port = config
10
10
  end
11
11
 
12
12
  def model
13
13
  'Generic modem'
14
14
  end
15
15
 
16
+ def version
17
+ '8 Ultimate'
18
+ end
19
+
16
20
  def error
17
21
  false
18
22
  end
@@ -22,10 +26,13 @@ module Smartware
22
26
  "#{res} dbm"
23
27
  end
24
28
 
25
- def ussd(code="*100#")
29
+ def balance
26
30
  "Сервис временно недоступен"
27
31
  end
28
32
 
33
+ def tick
34
+ sleep 10
35
+ end
29
36
  end
30
37
 
31
38
  end
@@ -1,85 +1,213 @@
1
1
  # coding: utf-8
2
- require 'serialport'
2
+ require 'cmux'
3
3
 
4
4
  module Smartware
5
5
  module Driver
6
6
  module Modem
7
7
 
8
8
  class Standard
9
+ attr_reader :error, :model, :balance, :version
9
10
 
10
- ERRORS = {
11
- "-1" => -1, # invalid device answer
12
- "10" => 10, # invalid ussd answer
13
- "20" => 20, # invalid modem model
14
- "21" => 21, # invalid modem signal level
15
- }
16
-
17
- def initialize(port)
18
- @sp = SerialPort.new(port, 115200, 8, 1, SerialPort::NONE)
19
- @sp.read_timeout = 100
11
+ def initialize(config)
12
+ @config = config
13
+ @state = :closed
14
+ @error = "not initialized yet"
15
+ @mux = nil
16
+ @status_channel = nil
17
+ @info_requested = false
18
+ @model = "GSM modem"
19
+ @version = ""
20
+ @signal = "+CSQ: 99,99"
21
+ @ussd_interval = 0
22
+ @balance = nil
23
+ @ppp_state = :stopped
24
+ @ppp_pid = nil
20
25
  end
21
26
 
22
- def error
23
- @error ||= false
27
+ def signal_level
28
+ value = @signal.gsub("+CSQ: ",'').split(',')[0].to_i
29
+ "#{(-113 + value * 2)} dbm"
24
30
  end
25
31
 
26
- def model
27
- res = send 'ATI'
28
- res.shift
29
- res.pop
30
- res.join(' ')
31
- rescue
32
- @error = ERRORS["20"]
32
+ def tick
33
+ begin
34
+ modem_tick
35
+ ppp_tick
36
+
37
+ wait_for_event
38
+ rescue => e
39
+ Smartware::Logging.logger.error "uncatched exception in modem monitor: #{e}"
40
+ end
33
41
  end
34
42
 
35
- #
36
- # Returns signal level in dbm
37
- #
38
- def signal_level
39
- res = send 'AT+CSQ'
40
- value = res[1].gsub("+CSQ: ",'').split(',')[0].to_i
41
- "#{(-113 + value * 2)} dbm"
42
- rescue
43
- @error = ERRORS["21"]
44
- return false
43
+ def unsolicited(type, fields)
44
+ case type
45
+ when "CUSD"
46
+ ussd *fields
47
+ end
45
48
  end
46
49
 
47
- #
48
- # Method send ussd to operator and return only valid answer body
49
- # Returns modem balance by default, it works for MTS and Megafon, use *102# for Beeline
50
- # Do not call with method synchronously from app, method waits USSD answer some time,
51
- # Use some scheduler and buffer for balance value
52
- #
53
- # Valid ussd answer sample: ["", "+CUSD: 2,\"003100310035002C003000300440002E00320031002E00330031002004310430043B002E0020\",72", "OK"]
54
- #
55
- def ussd(code="*100#")
56
- res = self.send("AT+CUSD=1,\"*100#\",15").reject{|i| i[0..4] != '+CUSD'}[0]
57
- if res
58
- ussd_body = res.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
50
+ def ussd(mode, string = nil, dcs = nil)
51
+ if mode != "0"
52
+ Smartware::Logging.logger.warn "USSD completed with mode #{mode}, expected 0"
53
+ end
54
+
55
+ if string
56
+ @balance = string.scan(/\w{4}/)
57
+ .map! { |i| [ i.hex ].pack("U") }
58
+ .join
59
+ .strip
60
60
  else
61
- @error = ERRORS["10"]
62
- false
61
+ @balance = nil
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def modem_tick
68
+ case @state
69
+ when :closed
70
+ Smartware::Logging.logger.info "trying to open modem"
71
+
72
+ begin
73
+ @mux = CMUX::MUX.new @config["device"]
74
+ @state = :open
75
+ @status_channel = @mux.allocate(@config["status_channel"]).open
76
+ @chatter = CMUX::ModemChatter.new @status_channel
77
+ @chatter.subscribe "CUSD", self
78
+ @error = false
79
+ @ussd_interval = 0
80
+ Smartware::Logging.logger.info "modem ready"
81
+ rescue => e
82
+ close_modem "unable to open modem: #{e}"
83
+ end
84
+
85
+ when :open
86
+ modem_works = nil
87
+
88
+ begin
89
+ @chatter.command("+CGMM;+CGMR;+CSQ", 3) do |resp|
90
+ modem_works = resp.success?
91
+
92
+ if modem_works
93
+ resp.response.reject! &:empty?
94
+ @model, @version, @signal = resp.response
95
+ end
96
+ end
97
+
98
+ while modem_works.nil?
99
+ CMUX::ModemChatter.poll [ @chatter ]
100
+ end
101
+ rescue => e
102
+ modem_works = false
103
+ end
104
+
105
+ if modem_works
106
+ if @config.include?("balance_ussd") && @ussd_interval == 0
107
+ @ussd_interval = @config["balance_interval"]
108
+ begin
109
+ @chatter.command("+CUSD=1,\"#{@config["balance_ussd"]}\",15", 1)
110
+ rescue => e
111
+ close_modem "USSD request failed: #{e}"
112
+ end
113
+ else
114
+ @ussd_interval -= 1
115
+ end
116
+ else
117
+ close_modem "modem is not responding"
118
+ end
119
+ end
120
+ end
121
+
122
+ def ppp_tick
123
+ case @ppp_state
124
+ when :stopped
125
+ if @state == :open
126
+ Smartware::Logging.logger.info "trying to start pppd"
127
+ begin
128
+ @ppp_channel = @mux.allocate @config["ppp_channel"]
129
+
130
+ @ppp_pid = Process.spawn "smartware-ppp-helper",
131
+ @ppp_channel.device,
132
+ @config["apn"]
133
+ @ppp_state = :running
134
+
135
+ Smartware::Logging.logger.info "started pppd, PID #{@ppp_pid}"
136
+ Smartware::ProcessManager.track @ppp_pid, method(:ppp_died)
137
+
138
+ rescue => e
139
+ begin
140
+ @ppp_channel.close
141
+ rescue
142
+ end
143
+
144
+ @ppp_channel = nil
145
+ @ppp_pid = nil
146
+ @ppp_state = :stopped
147
+
148
+ Smartware::Logging.logger.warn "cannot start pppd: #{e.to_s}"
149
+ end
150
+ end
151
+
152
+ when :running
153
+ if @ppp_pid.nil?
154
+ Smartware::Logging.logger.warn "pppd died"
155
+ begin
156
+ @ppp_channel.close
157
+ rescue
158
+ end
159
+
160
+ @ppp_channel = nil
161
+ @ppp_state = :stopped
162
+ end
63
163
  end
64
164
  end
65
165
 
66
- def send(cmd)
67
- @error = false
68
- @sp.write "#{ cmd }\r\n"
69
- read_port(@sp)
166
+ def ppp_died(pid)
167
+ # will be handled by the event loop a moment later
168
+ @ppp_pid = nil
70
169
  end
71
170
 
72
- def read_port(io, read_timeout = 0.25)
73
- return ERRORS["-1"] unless io
74
- answer = ''
75
- while IO.select [io], [], [], read_timeout
76
- chr = io.getc.chr
77
- answer << chr
171
+ def wait_for_event
172
+ if @state == :open
173
+ begin
174
+ CMUX::ModemChatter.poll [ @chatter ], @config["poll_interval"]
175
+ rescue => e
176
+ close_modem "modem poll failed: #{e}"
177
+ end
178
+ else
179
+ sleep @config["poll_interval"]
78
180
  end
79
- answer.split(/[\r\n]+/)
181
+
80
182
  end
81
- end
82
183
 
184
+ def close_modem(reason)
185
+ Smartware::Logging.logger.warn "#{reason}"
186
+
187
+ @error = reason
188
+
189
+ begin
190
+ @mux.close
191
+ rescue
192
+ end
193
+
194
+ begin
195
+ @chatter.unsubscribe "CUSD", self
196
+ rescue
197
+ end
198
+
199
+ @info_requested = false
200
+ @mux = nil
201
+ @chatter = nil
202
+ @status_channel = nil
203
+ @ppp_state = :stopped
204
+ unless @ppp_pid.nil?
205
+ Smartware::ProcessManager.untrack @ppp_pid
206
+ @ppp_pid = nil # PPP will die by itself, because we closed the mux.
207
+ end
208
+ @state = :closed
209
+ end
210
+ end
83
211
  end
84
212
  end
85
213
  end
@@ -70,6 +70,10 @@ module Smartware
70
70
  @status[:model]
71
71
  end
72
72
 
73
+ def self.version
74
+ @status[:version]
75
+ end
76
+
73
77
  def self.cashsum
74
78
  @banknotes.inject(0){ |result, (key, value)| result + key.to_i*value.to_i }
75
79
  end
@@ -120,6 +124,7 @@ module Smartware
120
124
  when 'monitor'
121
125
  @status[:error] = @device.error || ''
122
126
  @status[:model] = @device.model
127
+ @status[:version] = @device.version
123
128
  @status[:cassette] = @device.cassette?
124
129
  @commands.shift
125
130
  else
@@ -1,19 +1,20 @@
1
1
  require 'drb'
2
- require 'smartware/drivers/modem/standard'
3
2
  require 'smartware/drivers/modem/dummy'
3
+ require 'smartware/drivers/modem/standard'
4
4
 
5
5
  module Smartware
6
6
  module Interface
7
7
 
8
8
  module Modem
9
-
10
9
  @configured = false
11
10
  @status = {}
12
11
 
13
- def self.configure!(port=nil, driver=nil)
12
+ def self.configure!
13
+ Smartware::Logging.logger.debug "Creating modem"
14
14
  @device = Smartware::Driver::Modem.const_get(
15
15
  Smartware::Service.config['modem_driver']).new(
16
- Smartware::Service.config['modem_port'])
16
+ Smartware::Service.config['modem_config'])
17
+ Smartware::Logging.logger.debug "Starting modem"
17
18
  @session.kill if @session
18
19
  @session = self.poll_status!
19
20
  @configured = true
@@ -37,6 +38,10 @@ module Smartware
37
38
  @status[:model]
38
39
  end
39
40
 
41
+ def self.version
42
+ @status[:version]
43
+ end
44
+
40
45
  def self.balance
41
46
  @status[:balance]
42
47
  end
@@ -48,16 +53,19 @@ module Smartware
48
53
  private
49
54
  def self.poll_status!
50
55
  t = Thread.new do
51
- loop do
52
- @status[:signal_level] = @device.signal_level
53
- sleep 1
54
- @status[:model] = @device.model
55
- sleep 1
56
- @status[:error] = @device.error || ''
57
- sleep 3
58
- balance = @device.ussd("*100#")
59
- @status[:balance] = balance if balance
60
- sleep 10
56
+ begin
57
+ loop do
58
+ @device.tick
59
+
60
+ @status[:signal_level] = @device.signal_level
61
+ @status[:model] = @device.model
62
+ @status[:version] = @device.version
63
+ @status[:error] = @device.error || ''
64
+ @status[:balance] = @device.balance
65
+ end
66
+ rescue => e
67
+ Smartware::Logging.logger.error e.message
68
+ Smartware::Logging.logger.error e.backtrace.join("\n")
61
69
  end
62
70
  end
63
71
  end
@@ -35,6 +35,10 @@ module Smartware
35
35
  @status[:model]
36
36
  end
37
37
 
38
+ def self.version
39
+ @status[:version]
40
+ end
41
+
38
42
  def self.print(filepath)
39
43
  @queue << filepath unless filepath.nil?
40
44
  end
@@ -50,6 +54,7 @@ module Smartware
50
54
  if @queue.empty?
51
55
  @status[:error] = @device.error || ''
52
56
  @status[:model] = @device.model
57
+ @status[:version] = @device.version
53
58
  else
54
59
  begin
55
60
  `lpr #{@queue[0]}`
@@ -11,15 +11,27 @@ module Smartware
11
11
 
12
12
  def self.start(config_file)
13
13
  $stdout.sync = true
14
-
14
+
15
15
  @config = YAML.load File.read(File.expand_path(config_file))
16
16
 
17
17
  Smartware::Logging.logger = Logger.new($stdout)
18
18
  Smartware::Logging.logger.info "Smartware started at #{Time.now}"
19
19
 
20
- @threads = %w(smartware/interfaces/cash_acceptor
21
- smartware/interfaces/printer
22
- smartware/interfaces/modem).inject([]){|arr, iface| arr << Thread.new{ require iface } }
20
+
21
+ @threads = %w(smartware/interfaces/modem
22
+ smartware/interfaces/cash_acceptor
23
+ smartware/interfaces/printer
24
+ ).inject([]) do |arr, iface|
25
+ arr << Thread.new do
26
+ begin
27
+ require iface
28
+ rescue => e
29
+ Smartware::Logging.logger.fatal "During startup of #{iface}:"
30
+ Smartware::Logging.logger.fatal e.message
31
+ Smartware::Logging.logger.fatal e.backtrace.join("\n")
32
+ end
33
+ end
34
+ end
23
35
 
24
36
  @threads.map(&:join)
25
37
  rescue => e
@@ -34,5 +46,43 @@ module Smartware
34
46
  end
35
47
 
36
48
  end
49
+
50
+ module ProcessManager
51
+ def self.track(pid, method)
52
+ @tracked ||= {}
53
+ @tracked[pid] = method
54
+ end
55
+
56
+ def self.untrack(pid)
57
+ @tracked ||= {}
58
+ @tracked.delete pid
59
+ end
60
+
61
+ def self.handle_sigchld(signal)
62
+ pids = []
63
+ @tracked ||= {}
64
+
65
+ begin
66
+ loop do
67
+ pid = Process.wait(-1, Process::WNOHANG)
68
+
69
+ break if pid == 0
70
+
71
+ pids << pid
72
+ end
73
+ rescue
74
+ end
75
+
76
+ pids.each do |pid|
77
+ tracker = @tracked[pid]
78
+ unless tracker.nil?
79
+ @tracked.delete pid
80
+ tracker.call pid
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ trap 'CHLD', ProcessManager.method(:handle_sigchld)
37
87
  end
38
88
 
@@ -1,3 +1,3 @@
1
1
  module Smartware
2
- VERSION = "0.1.23"
2
+ VERSION = "0.1.24"
3
3
  end
data/smartware.gemspec CHANGED
@@ -19,5 +19,5 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_dependency 'serialport'
21
21
  gem.add_dependency 'dante'
22
-
22
+ gem.add_dependency 'cmux'
23
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.23
4
+ version: 0.1.24
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-01-09 00:00:00.000000000 Z
13
+ date: 2013-01-13 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: serialport
@@ -44,12 +44,29 @@ dependencies:
44
44
  - - ! '>='
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: cmux
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
47
63
  description: Smartware is the Smartkiosk hardware control daemon
48
64
  email:
49
65
  - e.sudarchikov@roundlake.ru
50
66
  - boris@roundlake.ru
51
67
  executables:
52
68
  - smartware
69
+ - smartware-ppp-helper
53
70
  extensions: []
54
71
  extra_rdoc_files: []
55
72
  files:
@@ -59,6 +76,7 @@ files:
59
76
  - README.md
60
77
  - Rakefile
61
78
  - bin/smartware
79
+ - bin/smartware-ppp-helper
62
80
  - lib/smartware.rb
63
81
  - lib/smartware/clients/cash_acceptor.rb
64
82
  - lib/smartware/clients/modem.rb
@@ -101,4 +119,3 @@ signing_key:
101
119
  specification_version: 3
102
120
  summary: Smartware is the Smartkiosk hardware control daemon
103
121
  test_files: []
104
- has_rdoc: