smartware 0.1.23 → 0.1.24

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 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: