smartware 0.4.5 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -21,11 +21,11 @@ begin
21
21
 
22
22
  Smartware::Logging.logger.info "Initializing Smartware"
23
23
 
24
- service.start
25
-
26
- Smartware::Logging.logger.info "Smartware ready"
24
+ [ 'INT', 'TERM' ].each do |sig|
25
+ trap(sig) { service.stop }
26
+ end
27
27
 
28
- service.join
28
+ service.start
29
29
  rescue => e
30
30
  Smartware::Logging.logger.error e.message
31
31
  Smartware::Logging.logger.error e.backtrace.join("\n")
@@ -5,8 +5,16 @@ require 'bundler/setup'
5
5
  require 'smartware'
6
6
 
7
7
  EventMachine.run do
8
- Smartware.subscribe do |key, *value|
9
- puts "#{key}: #{value.map(&:inspect).join(", ")}"
8
+ Smartware.subscribe do |message|
9
+ print "#{message.key}"
10
+
11
+ if message.reliable?
12
+ print " (reliable, #{message.id})"
13
+ end
14
+
15
+ puts ": #{message.args.map(&:inspect).join(", ")}"
16
+
17
+ message.acknowlege if ARGV[0] == "ack" && message.reliable?
10
18
  end
11
19
 
12
20
  puts "Receiving Smartware events."
@@ -29,11 +29,16 @@ ensure
29
29
  io.close
30
30
  end
31
31
 
32
- Process.exec "/usr/sbin/pppd",
33
- device, "115200",
34
- "nodetach", "file", "/etc/ppp/options",
32
+ cmdline = [
33
+ "/usr/sbin/pppd", device, "115200",
34
+ "nodetach", "logfd", "2", "file", "/etc/ppp/options"
35
+ ]
36
+
37
+ cmdline.unshift "sudo" if Process.euid != 0
38
+
39
+ Process.exec *cmdline,
35
40
  pgroup: true,
36
41
  in: :close,
37
42
  out: :close,
38
- err: :close,
43
+ err: :err,
39
44
  close_others: true
@@ -8,6 +8,7 @@ require 'eventmachine'
8
8
  require 'serialport'
9
9
  require 'json'
10
10
  require 'set'
11
+ require 'redis'
11
12
 
12
13
  require 'smartkiosk/common'
13
14
 
@@ -6,7 +6,38 @@ module Smartware
6
6
  module Modem
7
7
 
8
8
  class Standard
9
+ class LogConnection < EventMachine::Connection
10
+ include EventMachine::Protocols::LineProtocol
11
+
12
+ def initialize(device)
13
+ @device = device
14
+ end
15
+
16
+ def receive_line(line)
17
+ line.rstrip!
18
+
19
+ Logging.logger.debug "pppd: #{line}"
20
+
21
+ if line =~ /^Sent ([0-9]+) bytes, received ([0-9]+) bytes.$/
22
+ @sent = $1.to_i
23
+ @received = $2.to_i
24
+ elsif line =~ /^Connect time ([0-9.]+) minutes.$/
25
+ @time = ($1.to_f * 60).round
26
+ elsif line == "Connection terminated."
27
+ begin
28
+ @device.account.call @sent, @received, @time
29
+ ensure
30
+ @sent = nil
31
+ @received = nil
32
+ @time = nil
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+
9
39
  attr_reader :error, :model, :balance, :version
40
+ attr_accessor :account
10
41
 
11
42
  def initialize(config)
12
43
  @port = config["port"]
@@ -29,6 +60,25 @@ module Smartware
29
60
  @balance = nil
30
61
  @ppp_state = :stopped
31
62
  @ppp_pid = nil
63
+ @shutdown = false
64
+ @account = nil
65
+
66
+ logpipe_smartware, @logpipe_ppp = IO.pipe
67
+ EventMachine.attach logpipe_smartware, LogConnection, self
68
+ end
69
+
70
+ def shutdown(callback)
71
+ @shutdown_callback = callback
72
+
73
+ Logging.logger.info "stopping modem gracefully"
74
+ @shutdown = true
75
+ @shutdown_timer = EventMachine.add_periodic_timer(0.5) do
76
+ if @state == :closed && @ppp_state == :stopped
77
+ Logging.logger.info "modem stopped"
78
+ EventMachine.cancel_timer @shutdown_timer
79
+ @shutdown_callback.call
80
+ end
81
+ end
32
82
  end
33
83
 
34
84
  def signal_level
@@ -43,7 +93,7 @@ module Smartware
43
93
 
44
94
  wait_for_event
45
95
  rescue => e
46
- Smartware::Logging.logger.error "uncatched exception in modem monitor: #{e}"
96
+ Logging.logger.error "uncatched exception in modem monitor: #{e}"
47
97
  end
48
98
  end
49
99
 
@@ -56,7 +106,7 @@ module Smartware
56
106
 
57
107
  def ussd(mode, string = nil, dcs = nil)
58
108
  if mode != "0"
59
- Smartware::Logging.logger.warn "USSD completed with mode #{mode}, expected 0"
109
+ Logging.logger.warn "USSD completed with mode #{mode}, expected 0"
60
110
  end
61
111
 
62
112
  if string
@@ -74,19 +124,21 @@ module Smartware
74
124
  def modem_tick
75
125
  case @state
76
126
  when :closed
77
- Smartware::Logging.logger.info "trying to open modem"
127
+ if !@shutdown
128
+ Logging.logger.info "trying to open modem"
78
129
 
79
- begin
80
- @mux = CMUX::MUX.new @port
81
- @state = :open
82
- @status_channel = @mux.allocate(@status_channel_id).open
83
- @chatter = CMUX::ModemChatter.new @status_channel
84
- @chatter.subscribe "CUSD", self
85
- @error = nil
86
- @balance_timer = 0
87
- Smartware::Logging.logger.info "modem ready"
88
- rescue => e
89
- close_modem "unable to open modem: #{e}"
130
+ begin
131
+ @mux = CMUX::MUX.new @port
132
+ @state = :open
133
+ @status_channel = @mux.allocate(@status_channel_id).open
134
+ @chatter = CMUX::ModemChatter.new @status_channel
135
+ @chatter.subscribe "CUSD", self
136
+ @error = nil
137
+ @balance_timer = 0
138
+ Logging.logger.info "modem ready"
139
+ rescue => e
140
+ close_modem "unable to open modem: #{e}"
141
+ end
90
142
  end
91
143
 
92
144
  when :open
@@ -110,7 +162,9 @@ module Smartware
110
162
  end
111
163
 
112
164
  if modem_works
113
- if !@balance_ussd.nil? && @balance_timer == 0
165
+ if @shutdown && @ppp_state == :stopped
166
+ close_modem "service shutdown"
167
+ elsif !@balance_ussd.nil? && @balance_timer == 0
114
168
  @balance_timer = @balance_interval
115
169
  begin
116
170
  @chatter.command("+CUSD=1,\"#{@balance_ussd}\",15", 1)
@@ -129,43 +183,45 @@ module Smartware
129
183
  def ppp_tick
130
184
  case @ppp_state
131
185
  when :stopped
132
- if @state == :open
133
- Smartware::Logging.logger.info "trying to start pppd"
186
+ if @state == :open && !@shutdown
187
+ Logging.logger.info "trying to start pppd"
134
188
  begin
135
189
  @ppp_channel = @mux.allocate @ppp_channel_id
136
190
 
137
191
  @ppp_pid = Process.spawn "smartware-ppp-helper",
138
192
  @ppp_channel.device,
139
- @apn
193
+ @apn,
194
+ err: @logpipe_ppp
140
195
  @ppp_state = :running
141
196
 
142
- Smartware::Logging.logger.info "started pppd, PID #{@ppp_pid}"
143
- Smartware::ProcessManager.track @ppp_pid, method(:ppp_died)
197
+ Logging.logger.info "started pppd, PID #{@ppp_pid}"
198
+ ProcessManager.track @ppp_pid, method(:ppp_died)
144
199
 
145
200
  rescue => e
146
- begin
147
- @ppp_channel.close
148
- rescue
149
- end
201
+ @ppp_channel.close rescue nil
150
202
 
151
203
  @ppp_channel = nil
152
204
  @ppp_pid = nil
153
205
  @ppp_state = :stopped
154
206
 
155
- Smartware::Logging.logger.warn "cannot start pppd: #{e.to_s}"
207
+ Logging.logger.warn "cannot start pppd: #{e.to_s}"
156
208
  end
157
209
  end
158
210
 
159
211
  when :running
160
212
  if @ppp_pid.nil?
161
- Smartware::Logging.logger.warn "pppd died"
162
- begin
163
- @ppp_channel.close
164
- rescue
165
- end
213
+ Logging.logger.warn "pppd died"
214
+ @ppp_channel.close rescue nil
166
215
 
167
216
  @ppp_channel = nil
168
217
  @ppp_state = :stopped
218
+ elsif @shutdown
219
+ Logging.logger.debug "Terminating pppd #{@ppp_pid}"
220
+ if Process.euid == 0
221
+ Process.kill 'TERM', @ppp_pid
222
+ else
223
+ system "sudo", "kill", "-TERM", @ppp_pid.to_s
224
+ end
169
225
  end
170
226
  end
171
227
  end
@@ -189,19 +245,12 @@ module Smartware
189
245
  end
190
246
 
191
247
  def close_modem(reason)
192
- Smartware::Logging.logger.warn "#{reason}"
248
+ Logging.logger.warn "#{reason}"
193
249
 
194
250
  @error = Interface::Modem::MODEM_NOT_AVAILABLE
195
251
 
196
- begin
197
- @mux.close
198
- rescue
199
- end
200
-
201
- begin
202
- @chatter.unsubscribe "CUSD", self
203
- rescue
204
- end
252
+ @mux.close rescue nil
253
+ @chatter.unsubscribe "CUSD", self rescue nil
205
254
 
206
255
  @info_requested = false
207
256
  @mux = nil
@@ -209,7 +258,7 @@ module Smartware
209
258
  @status_channel = nil
210
259
  @ppp_state = :stopped
211
260
  unless @ppp_pid.nil?
212
- Smartware::ProcessManager.untrack @ppp_pid
261
+ ProcessManager.untrack @ppp_pid
213
262
  @ppp_pid = nil # PPP will die by itself, because we closed the mux.
214
263
  end
215
264
  @state = :closed
@@ -26,6 +26,16 @@ module Smartware
26
26
  .new(config)
27
27
  end
28
28
 
29
+ def shutdown(callback)
30
+ if @device.respond_to? :shutdown
31
+ @device.shutdown callback
32
+
33
+ true
34
+ else
35
+ false
36
+ end
37
+ end
38
+
29
39
  def repush_events(connection)
30
40
  @status_mutex.synchronize do
31
41
  @status.each do |key, value|
@@ -64,6 +74,10 @@ module Smartware
64
74
  def publish_event(key, *data)
65
75
  @service.publish_event "#{@iface_id}.#{key}", *data
66
76
  end
77
+
78
+ def publish_reliable_event(key, *data)
79
+ @service.publish_reliable_event "#{@iface_id}.#{key}", *data
80
+ end
67
81
  end
68
82
  end
69
83
  end
@@ -6,11 +6,12 @@ module Smartware
6
6
  def initialize(config, service)
7
7
  super
8
8
 
9
+ @device.account = method(:account)
10
+
9
11
  update_status :balance, ''
10
12
  update_status :signal_level, ''
11
13
 
12
14
  @session = Thread.new &method(:poll)
13
- Smartware::Logging.logger.info 'Modem monitor started'
14
15
  end
15
16
 
16
17
  def balance
@@ -39,6 +40,12 @@ module Smartware
39
40
  Smartware::Logging.logger.error e.backtrace.join("\n")
40
41
  end
41
42
  end
43
+
44
+ def account(sent, received, time)
45
+ Smartware::Logging.logger.debug "recording session: #{sent} upstream #{received} downstream, #{time} seconds"
46
+
47
+ publish_reliable_event 'accounting', sent, received, time
48
+ end
42
49
  end
43
50
  end
44
51
  end
@@ -2,6 +2,39 @@ require "em/protocols/line_protocol"
2
2
 
3
3
  module Smartware
4
4
  class PubSubClient
5
+ class Message
6
+ attr_reader :key, :args
7
+
8
+ def initialize(reliable_id, key, args, callback)
9
+ @reliable_id = reliable_id
10
+ @key = key
11
+ @args = args
12
+ @callback = callback
13
+ end
14
+
15
+ def reliable?
16
+ !@reliable_id.nil?
17
+ end
18
+
19
+ def id
20
+ @reliable_id
21
+ end
22
+
23
+ def acknowlege
24
+ raise "message is not reliable" if @reliable_id.nil?
25
+
26
+ @callback.call @reliable_id
27
+ end
28
+
29
+ def [](index)
30
+ @args[index]
31
+ end
32
+
33
+ def to_a
34
+ @args
35
+ end
36
+ end
37
+
5
38
  class PubSubHandler < EventMachine::Connection
6
39
  include EventMachine::Protocols::LineProtocol
7
40
 
@@ -16,7 +49,11 @@ module Smartware
16
49
  def receive_line(line)
17
50
  data = JSON.load line
18
51
 
19
- @client.deliver data["key"], *data["args"]
52
+ @client.receive data
53
+ end
54
+
55
+ def send_record(data)
56
+ send_data(JSON.dump(data) + "\r\n")
20
57
  end
21
58
  end
22
59
 
@@ -55,8 +92,16 @@ module Smartware
55
92
  @reconnect_timer = EventMachine.add_timer 1, &method(:attempt)
56
93
  end
57
94
 
58
- def deliver(key, *args)
59
- @receiver.call key, *args
95
+ def receive(data)
96
+ message = Message.new data["reliable_id"],
97
+ data["key"],
98
+ data["args"],
99
+ ->(id) do
100
+
101
+ @connection.send_record command: "acknowlege", id: id
102
+ end
103
+
104
+ @receiver.call message
60
105
  end
61
106
 
62
107
  private
@@ -1,6 +1,10 @@
1
+ require "em/protocols/line_protocol"
2
+
1
3
  module Smartware
2
4
  class PubSubServer
3
5
  class PubSubConnection < EventMachine::Connection
6
+ include EventMachine::Protocols::LineProtocol
7
+
4
8
  def initialize(server)
5
9
  @server = server
6
10
  end
@@ -13,13 +17,30 @@ module Smartware
13
17
  @server.remove_connection self
14
18
  end
15
19
 
16
- def receive_data(data)
20
+ def receive_line(line)
21
+ begin
22
+ data = JSON.load line
17
23
 
24
+ case data["command"]
25
+ when "acknowlege"
26
+ @server.acknowlege_reliable data["id"]
27
+
28
+ else
29
+ raise "unsupported command #{data["command"]}"
30
+ end
31
+ rescue => e
32
+ Logging.logger.warn e.to_s
33
+ e.backtrace.each { |line| Logging.logger.warn line }
34
+ end
18
35
  end
19
36
 
20
37
  def publish_event(key, *args)
21
38
  send_data(JSON.dump({ key: key, args: args }) + "\r\n")
22
39
  end
40
+
41
+ def publish_reliable_event(id, key, *args)
42
+ send_data(JSON.dump({ reliable_id: id, key: key, args: args }) + "\r\n")
43
+ end
23
44
  end
24
45
 
25
46
  attr_accessor :repush
@@ -27,6 +48,7 @@ module Smartware
27
48
  def initialize(host, port)
28
49
  @repush = nil
29
50
  @connections = Set.new
51
+ @redis = Redis.new
30
52
 
31
53
  EventMachine.start_server host, port, PubSubConnection, self
32
54
  end
@@ -34,6 +56,12 @@ module Smartware
34
56
  def add_connection(connection)
35
57
  @connections.add connection
36
58
  @repush.call connection
59
+
60
+ @redis.hgetall("smartware:reliable_events").each do |key, data|
61
+ data = JSON.load(data)
62
+
63
+ connection.publish_reliable_event key, data["key"], data["args"]
64
+ end
37
65
  end
38
66
 
39
67
  def remove_connection(connection)
@@ -45,5 +73,19 @@ module Smartware
45
73
  connection.publish_event key, *args
46
74
  end
47
75
  end
76
+
77
+ def publish_reliable_event(key, *args)
78
+ id = (Time.now.to_f * 1000000).round.to_s
79
+
80
+ @redis.hset "smartware:reliable_events", id, JSON.dump({ key: key, args: args })
81
+
82
+ @connections.each do |connection|
83
+ connection.publish_reliable_event id, key, args
84
+ end
85
+ end
86
+
87
+ def acknowlege_reliable(id)
88
+ @redis.hdel "smartware:reliable_events", id
89
+ end
48
90
  end
49
91
  end
@@ -6,6 +6,7 @@ module Smartware
6
6
  @config = YAML.load File.read(config_file)
7
7
  @interfaces = []
8
8
  @devices = []
9
+ @shutdown_holders = 0
9
10
  end
10
11
 
11
12
  def start
@@ -16,6 +17,11 @@ module Smartware
16
17
  @pubsub.publish_event key, *args
17
18
  end
18
19
 
20
+ @reliable_event_channel = EventMachine::Channel.new
21
+ @reliable_event_channel.subscribe do |(key, args)|
22
+ @pubsub.publish_reliable_event key, *args
23
+ end
24
+
19
25
  @pubsub = PubSubServer.new "localhost", (@config["pubsub_port"] || 6100)
20
26
  @pubsub.repush = ->(connection) do
21
27
  @devices.each do |device|
@@ -51,16 +57,30 @@ module Smartware
51
57
  @event_channel.push [key, args]
52
58
  end
53
59
 
60
+ def publish_reliable_event(key, *args)
61
+ @reliable_event_channel.push [key, args]
62
+ end
63
+
54
64
  def stop
55
65
  @interfaces.each &:stop_service
56
66
 
57
- EventMachine.stop
58
- end
67
+ callback = method :decr_shutdown_holders
59
68
 
60
- def join
61
- @interfaces.each do |server|
62
- server.thread.join
69
+ @devices.each do |interface|
70
+ if interface.shutdown callback
71
+ @shutdown_holders += 1
72
+ end
63
73
  end
74
+
75
+ EventMachine.stop if @shutdown_holders == 0
76
+ end
77
+
78
+ private
79
+
80
+ def decr_shutdown_holders
81
+ @shutdown_holders -= 1
82
+
83
+ EventMachine.stop if @shutdown_holders == 0
64
84
  end
65
85
  end
66
86
  end
@@ -1,3 +1,3 @@
1
1
  module Smartware
2
- VERSION = "0.4.5"
2
+ VERSION = "0.4.6"
3
3
  end
@@ -27,4 +27,5 @@ Gem::Specification.new do |gem|
27
27
  gem.add_dependency 'eventmachine'
28
28
  gem.add_dependency 'chunky_png'
29
29
  gem.add_dependency 'oily_png'
30
+ gem.add_dependency 'redis'
30
31
  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.4.5
4
+ version: 0.4.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-02-15 00:00:00.000000000 Z
14
+ date: 2013-02-16 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: smartkiosk-common
@@ -173,6 +173,22 @@ dependencies:
173
173
  - - ! '>='
174
174
  - !ruby/object:Gem::Version
175
175
  version: '0'
176
+ - !ruby/object:Gem::Dependency
177
+ name: redis
178
+ requirement: !ruby/object:Gem::Requirement
179
+ none: false
180
+ requirements:
181
+ - - ! '>='
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ type: :runtime
185
+ prerelease: false
186
+ version_requirements: !ruby/object:Gem::Requirement
187
+ none: false
188
+ requirements:
189
+ - - ! '>='
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
176
192
  description: Smartware is the Smartkiosk hardware control daemon
177
193
  email:
178
194
  - e.sudarchikov@roundlake.ru