smsRuby 1.0.0-x86-linux
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +20 -0
- data/ext/Makefile +157 -0
- data/ext/RecieveSMS.o +0 -0
- data/ext/SendSMS.o +0 -0
- data/ext/extconf.rb +6 -0
- data/ext/sms.i +8 -0
- data/ext/sms.o +0 -0
- data/ext/sms.so +0 -0
- data/ext/sms_wrap.c +3606 -0
- data/ext/sms_wrap.o +0 -0
- data/lib/sms.so +0 -0
- data/lib/smsruby.rb +199 -0
- data/lib/smsruby.rb~ +199 -0
- data/lib/smsruby/adm_connection.rb +539 -0
- data/lib/smsruby/adm_connection.rb~ +539 -0
- data/lib/smsruby/connection.rb +141 -0
- data/lib/smsruby/connection.rb~ +141 -0
- data/lib/smsruby/error.rb +40 -0
- data/lib/smsruby/receive.rb +97 -0
- data/lib/smsruby/receive.rb~ +97 -0
- data/lib/smsruby/send.rb +182 -0
- data/lib/smsruby/send.rb~ +182 -0
- metadata +84 -0
data/ext/sms_wrap.o
ADDED
Binary file
|
data/lib/sms.so
ADDED
Binary file
|
data/lib/smsruby.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'smsruby/send'
|
2
|
+
require 'smsruby/receive'
|
3
|
+
|
4
|
+
#
|
5
|
+
# The Smsruby class represent the connection between the client and the SMS middleware.
|
6
|
+
# Defines the API functions of the SMS middleware
|
7
|
+
#
|
8
|
+
class Smsruby
|
9
|
+
|
10
|
+
# Reference an instance of the Sender class
|
11
|
+
attr_reader :sender
|
12
|
+
# Reference an instance of the Receive class
|
13
|
+
attr_reader :receiver
|
14
|
+
|
15
|
+
#
|
16
|
+
# Obtain the sender and receiver instances, allowing users to send and receive
|
17
|
+
# messages trought the API functions.
|
18
|
+
# The initialize function can receive 4 arguments: the first one is the send type
|
19
|
+
# to be used by the Sender class, the second one is the location of the configuration
|
20
|
+
# file used by the Sender class, the third one is the receivetype used in the Receive
|
21
|
+
# class and the fourth one is the time that receive threads will remain active. If 0 is
|
22
|
+
# past, receive threads will remain active until stop_receive method is called.
|
23
|
+
#
|
24
|
+
# A simple example of how to use smsRuby is shown below:
|
25
|
+
#
|
26
|
+
# require 'rubygems'
|
27
|
+
# require 'smsruby'
|
28
|
+
#
|
29
|
+
# sms = Smsruby.new
|
30
|
+
#
|
31
|
+
# sms.sender.dst=['0412123456']
|
32
|
+
# sms.send("Hello world")
|
33
|
+
#
|
34
|
+
# sms.receive{ |message,dest|
|
35
|
+
# puts "Hello world"
|
36
|
+
# }
|
37
|
+
#
|
38
|
+
# Other example:
|
39
|
+
#
|
40
|
+
# require 'rubygems'
|
41
|
+
# require 'smsruby'
|
42
|
+
#
|
43
|
+
# sms = Smsruby.new(:sendtype=> BDsend.new, :location=> 'config_sms.yml', :receivetype=> 0, :time=> 15, :ports=>['/dev/ttyACM0'])
|
44
|
+
#
|
45
|
+
# sms.send("Hello world")
|
46
|
+
#
|
47
|
+
# sms.receive(['358719846826017']){ |message,dest|
|
48
|
+
# puts "Message received: #{message.text}, from #{message.source_number}"
|
49
|
+
# puts "The phone receiving the message has imei number #{dest}"
|
50
|
+
# }
|
51
|
+
#
|
52
|
+
# A final example:
|
53
|
+
#
|
54
|
+
# require 'rubygems'
|
55
|
+
# require 'smsruby'
|
56
|
+
#
|
57
|
+
# sms = Smsruby.new
|
58
|
+
#
|
59
|
+
# sms.sender.sendtype = Configsend.new
|
60
|
+
# sms.send("Hello world")
|
61
|
+
#
|
62
|
+
# sms.receiver.receivetype=1
|
63
|
+
# sms.receive(['358719846826017']){ |message,dest|
|
64
|
+
# puts "Message received: #{message.text}, from #{message.source_number}"
|
65
|
+
# puts "The phone receiving the message has imei number #{dest}"
|
66
|
+
# }
|
67
|
+
#
|
68
|
+
def initialize(*args)
|
69
|
+
begin
|
70
|
+
params={}
|
71
|
+
params=args.pop if args.last.is_a? Hash
|
72
|
+
!params[:sendtype] ? sendtype=Plainsend.new : sendtype = params[:sendtype]
|
73
|
+
!params[:location] ? location = 'config_sms.yml' : location = params[:location]
|
74
|
+
!params[:receivetyoe] ? receivetype = 0 : receivetype = params[:receivetype]
|
75
|
+
!params[:time] ? time = 0 : time = params[:time]
|
76
|
+
!params[:ports] ? ports=nil : ports = params[:ports]
|
77
|
+
@sender=Sender.new(sendtype,location)
|
78
|
+
@receiver=Receive.new(receivetype,time)
|
79
|
+
@sender.adm.open_ports(ports) unless @sender.adm.avlconn
|
80
|
+
rescue Exception => e
|
81
|
+
puts "Instance of connection administrator fail. Detail: #{e.message}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# High level function to send an SMS message. The text to be sent must be passed
|
87
|
+
# as an argument. the values of the other options can be set trought the Sender
|
88
|
+
# class, the destination number(s) is required. A code block can be past to send
|
89
|
+
# function and will be executed if an error occurs sending the message. The code
|
90
|
+
# block can use one argument, this is the string error giving by Sender class
|
91
|
+
#
|
92
|
+
def send(msj)
|
93
|
+
@sender.send(msj){|e| yield e if block_given?}
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Hight level function to receive SMS message(s). The imei of the phones that are
|
98
|
+
# going to receive can be passed as an argument, if not, all the phones configured
|
99
|
+
# to receive ( trought config_sms.yml ) will be used. The values of the other options
|
100
|
+
# can be set trought the Receive class. A code block can be passed to the receive
|
101
|
+
# function and will be executed for every received message, the block can use 2
|
102
|
+
# given arguments. The first argument is a message object with the following structure:
|
103
|
+
#
|
104
|
+
# message{
|
105
|
+
# int error if error is different from 0 an error has ocurred
|
106
|
+
# int index the index memory in the phone of the received message
|
107
|
+
# string date reception date of the message
|
108
|
+
# string status the status of the received message (read, unread ,unknown,..)
|
109
|
+
# string source_number The number of the phone sending the message
|
110
|
+
# string text The text of the received message
|
111
|
+
# string type_sms The sms type of the received message (text, mms,...)
|
112
|
+
# }
|
113
|
+
#
|
114
|
+
# The second argument represent the imei of the phone receiving the message
|
115
|
+
#
|
116
|
+
def receive(imeis=nil)
|
117
|
+
@receiver.receive(imeis){|x,y| yield x,y if block_given? }
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# High level function to update and reload changes made to the configuration
|
122
|
+
# file used in the Sender and the Connection Administrator
|
123
|
+
#
|
124
|
+
def update_config
|
125
|
+
unless !@sender.adm.avlconn
|
126
|
+
@sender.adm.reload_file
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# High level function to update available connections in case a new one is
|
132
|
+
# attached or an available one is unattach. An update of the configuration
|
133
|
+
# file is also made when update_conn function is invoked
|
134
|
+
#
|
135
|
+
def update_conn
|
136
|
+
if !@sender.adm.avlconn
|
137
|
+
@sender.adm.open_profiles
|
138
|
+
end
|
139
|
+
@sender.adm.update_connections unless !@sender.adm.avlconn
|
140
|
+
update_config
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# High level function to get all available connection. The result is a hash
|
145
|
+
# containing the number of the connection and an object of the Connection class.
|
146
|
+
# A code block can be passed to get_conn function and will be executed for each
|
147
|
+
# available connection found by the Connection Administrator
|
148
|
+
#
|
149
|
+
def get_conn
|
150
|
+
unless !@sender.adm.avlconn
|
151
|
+
conn= @sender.adm.get_connections{ |pm|
|
152
|
+
yield pm if block_given?
|
153
|
+
}
|
154
|
+
end
|
155
|
+
return conn
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# High level function that wait for both receive and send threads and terminate
|
160
|
+
# all active connections. A call to close function must be made at the end
|
161
|
+
# of the user program
|
162
|
+
#
|
163
|
+
def close
|
164
|
+
sleep(1)
|
165
|
+
unless !@receiver.adm.avlconn
|
166
|
+
Thread.list.each{|t| t.join if (t[:type]=='r' or t[:type]=='sp') }
|
167
|
+
@receiver.adm.get_connections.each{|conn|
|
168
|
+
conn[1].close
|
169
|
+
}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# High level function that stops all receiving threads that are configured to
|
175
|
+
# obtain messages for a spicified period of time
|
176
|
+
#
|
177
|
+
def stop_receive
|
178
|
+
sleep(1)
|
179
|
+
unless !@receiver.adm.avlconn
|
180
|
+
Thread.list.each{|t| t.exit if t[:type]=='r'}
|
181
|
+
@receiver.adm.get_connections.each{|conn|
|
182
|
+
conn[1].status="available" if ((conn[1].typec=='r' and conn[1].status=="receiving") or (conn[1].typec=='sr' and conn[1].status=="receiving"))}
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# High level function that wait for all active receive threads without terminate
|
188
|
+
# the active connections.
|
189
|
+
#
|
190
|
+
def wait_receive
|
191
|
+
sleep(1)
|
192
|
+
unless !@receiver.adm.avlconn
|
193
|
+
Thread.list.each{|t| t.join if t[:type]=='r'}
|
194
|
+
@receiver.adm.get_connections.each{|conn|
|
195
|
+
conn[1].status="available" if ((conn[1].typec=='r' and conn[1].status=="receiving") or (conn[1].typec=='sr' and conn[1].status=="receiving"))}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
data/lib/smsruby.rb~
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'send'
|
2
|
+
require 'receive'
|
3
|
+
|
4
|
+
#
|
5
|
+
# The Smsruby class represent the connection between the client and the SMS middleware.
|
6
|
+
# Defines the API functions of the SMS middleware
|
7
|
+
#
|
8
|
+
class Smsruby
|
9
|
+
|
10
|
+
# Reference an instance of the Sender class
|
11
|
+
attr_reader :sender
|
12
|
+
# Reference an instance of the Receive class
|
13
|
+
attr_reader :receiver
|
14
|
+
|
15
|
+
#
|
16
|
+
# Obtain the sender and receiver instances, allowing users to send and receive
|
17
|
+
# messages trought the API functions.
|
18
|
+
# The initialize function can receive 4 arguments: the first one is the send type
|
19
|
+
# to be used by the Sender class, the second one is the location of the configuration
|
20
|
+
# file used by the Sender class, the third one is the receivetype used in the Receive
|
21
|
+
# class and the fourth one is the time that receive threads will remain active. If 0 is
|
22
|
+
# past, receive threads will remain active until stop_receive method is called.
|
23
|
+
#
|
24
|
+
# A simple example of how to use smsRuby is shown below:
|
25
|
+
#
|
26
|
+
# require 'rubygems'
|
27
|
+
# require 'smsruby'
|
28
|
+
#
|
29
|
+
# sms = Smsruby.new
|
30
|
+
#
|
31
|
+
# sms.sender.dst=['0412123456']
|
32
|
+
# sms.send("Hello world")
|
33
|
+
#
|
34
|
+
# sms.receive{ |message,dest|
|
35
|
+
# puts "Hello world"
|
36
|
+
# }
|
37
|
+
#
|
38
|
+
# Other example:
|
39
|
+
#
|
40
|
+
# require 'rubygems'
|
41
|
+
# require 'smsruby'
|
42
|
+
#
|
43
|
+
# sms = Smsruby.new(:sendtype=> BDsend.new, :location=> 'config_sms.yml', :receivetype=> 0, :time=> 15, :ports=>['/dev/ttyACM0'])
|
44
|
+
#
|
45
|
+
# sms.send("Hello world")
|
46
|
+
#
|
47
|
+
# sms.receive(['358719846826017']){ |message,dest|
|
48
|
+
# puts "Message received: #{message.text}, from #{message.source_number}"
|
49
|
+
# puts "The phone receiving the message has imei number #{dest}"
|
50
|
+
# }
|
51
|
+
#
|
52
|
+
# A final example:
|
53
|
+
#
|
54
|
+
# require 'rubygems'
|
55
|
+
# require 'smsruby'
|
56
|
+
#
|
57
|
+
# sms = Smsruby.new
|
58
|
+
#
|
59
|
+
# sms.sender.sendtype = Configsend.new
|
60
|
+
# sms.send("Hello world")
|
61
|
+
#
|
62
|
+
# sms.receiver.receivetype=1
|
63
|
+
# sms.receive(['358719846826017']){ |message,dest|
|
64
|
+
# puts "Message received: #{message.text}, from #{message.source_number}"
|
65
|
+
# puts "The phone receiving the message has imei number #{dest}"
|
66
|
+
# }
|
67
|
+
#
|
68
|
+
def initialize(*args)
|
69
|
+
begin
|
70
|
+
params={}
|
71
|
+
params=args.pop if args.last.is_a? Hash
|
72
|
+
!params[:sendtype] ? sendtype=Plainsend.new : sendtype = params[:sendtype]
|
73
|
+
!params[:location] ? location = 'config_sms.yml' : location = params[:location]
|
74
|
+
!params[:receivetyoe] ? receivetype = 0 : receivetype = params[:receivetype]
|
75
|
+
!params[:time] ? time = 0 : time = params[:time]
|
76
|
+
!params[:ports] ? ports=nil : ports = params[:ports]
|
77
|
+
@sender=Sender.new(sendtype,location)
|
78
|
+
@receiver=Receive.new(receivetype,time)
|
79
|
+
@sender.adm.open_ports(ports) unless @sender.adm.avlconn
|
80
|
+
rescue Exception => e
|
81
|
+
puts "Instance of connection administrator fail. Detail: #{e.message}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# High level function to send an SMS message. The text to be sent must be passed
|
87
|
+
# as an argument. the values of the other options can be set trought the Sender
|
88
|
+
# class, the destination number(s) is required. A code block can be past to send
|
89
|
+
# function and will be executed if an error occurs sending the message. The code
|
90
|
+
# block can use one argument, this is the string error giving by Sender class
|
91
|
+
#
|
92
|
+
def send(msj)
|
93
|
+
@sender.send(msj){|e| yield e if block_given?}
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Hight level function to receive SMS message(s). The imei of the phones that are
|
98
|
+
# going to receive can be passed as an argument, if not, all the phones configured
|
99
|
+
# to receive ( trought config_sms.yml ) will be used. The values of the other options
|
100
|
+
# can be set trought the Receive class. A code block can be passed to the receive
|
101
|
+
# function and will be executed for every received message, the block can use 2
|
102
|
+
# given arguments. The first argument is a message object with the following structure:
|
103
|
+
#
|
104
|
+
# message{
|
105
|
+
# int error if error is different from 0 an error has ocurred
|
106
|
+
# int index the index memory in the phone of the received message
|
107
|
+
# string date reception date of the message
|
108
|
+
# string status the status of the received message (read, unread ,unknown,..)
|
109
|
+
# string source_number The number of the phone sending the message
|
110
|
+
# string text The text of the received message
|
111
|
+
# string type_sms The sms type of the received message (text, mms,...)
|
112
|
+
# }
|
113
|
+
#
|
114
|
+
# The second argument represent the imei of the phone receiving the message
|
115
|
+
#
|
116
|
+
def receive(imeis=nil)
|
117
|
+
@receiver.receive(imeis){|x,y| yield x,y if block_given? }
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# High level function to update and reload changes made to the configuration
|
122
|
+
# file used in the Sender and the Connection Administrator
|
123
|
+
#
|
124
|
+
def update_config
|
125
|
+
unless !@sender.adm.avlconn
|
126
|
+
@sender.adm.reload_file
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# High level function to update available connections in case a new one is
|
132
|
+
# attached or an available one is unattach. An update of the configuration
|
133
|
+
# file is also made when update_conn function is invoked
|
134
|
+
#
|
135
|
+
def update_conn
|
136
|
+
if !@sender.adm.avlconn
|
137
|
+
@sender.adm.open_profiles
|
138
|
+
end
|
139
|
+
@sender.adm.update_connections unless !@sender.adm.avlconn
|
140
|
+
update_config
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# High level function to get all available connection. The result is a hash
|
145
|
+
# containing the number of the connection and an object of the Connection class.
|
146
|
+
# A code block can be passed to get_conn function and will be executed for each
|
147
|
+
# available connection found by the Connection Administrator
|
148
|
+
#
|
149
|
+
def get_conn
|
150
|
+
unless !@sender.adm.avlconn
|
151
|
+
conn= @sender.adm.get_connections{ |pm|
|
152
|
+
yield pm if block_given?
|
153
|
+
}
|
154
|
+
end
|
155
|
+
return conn
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# High level function that wait for both receive and send threads and terminate
|
160
|
+
# all active connections. A call to close function must be made at the end
|
161
|
+
# of the user program
|
162
|
+
#
|
163
|
+
def close
|
164
|
+
sleep(1)
|
165
|
+
unless !@receiver.adm.avlconn
|
166
|
+
Thread.list.each{|t| t.join if (t[:type]=='r' or t[:type]=='sp') }
|
167
|
+
@receiver.adm.get_connections.each{|conn|
|
168
|
+
conn[1].close
|
169
|
+
}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# High level function that stops all receiving threads that are configured to
|
175
|
+
# obtain messages for a spicified period of time
|
176
|
+
#
|
177
|
+
def stop_receive
|
178
|
+
sleep(1)
|
179
|
+
unless !@receiver.adm.avlconn
|
180
|
+
Thread.list.each{|t| t.exit if t[:type]=='r'}
|
181
|
+
@receiver.adm.get_connections.each{|conn|
|
182
|
+
conn[1].status="available" if ((conn[1].typec=='r' and conn[1].status=="receiving") or (conn[1].typec=='sr' and conn[1].status=="receiving"))}
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# High level function that wait for all active receive threads without terminate
|
188
|
+
# the active connections.
|
189
|
+
#
|
190
|
+
def wait_receive
|
191
|
+
sleep(1)
|
192
|
+
unless !@receiver.adm.avlconn
|
193
|
+
Thread.list.each{|t| t.join if t[:type]=='r'}
|
194
|
+
@receiver.adm.get_connections.each{|conn|
|
195
|
+
conn[1].status="available" if ((conn[1].typec=='r' and conn[1].status=="receiving") or (conn[1].typec=='sr' and conn[1].status=="receiving"))}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
@@ -0,0 +1,539 @@
|
|
1
|
+
require 'smsruby/connection'
|
2
|
+
require 'singleton'
|
3
|
+
require 'thread'
|
4
|
+
require 'logger'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
#
|
8
|
+
# The AdmConnection class represent the SMS connection management layer for
|
9
|
+
# handling and controling connections (in case a pool of connections exist).
|
10
|
+
# It will also control the flow and the balance of the messages to be
|
11
|
+
# deliver through all active connections
|
12
|
+
#
|
13
|
+
class AdmConnection
|
14
|
+
|
15
|
+
include Singleton
|
16
|
+
|
17
|
+
# Represent a hash containing all connections availables to send or receive
|
18
|
+
@@connections = {}
|
19
|
+
# Reference all the consumer threads
|
20
|
+
@@consu =[]
|
21
|
+
# Reference a syncronization object to control access to critical resources
|
22
|
+
attr_reader :sync
|
23
|
+
# Represent the number of items produced into de queue
|
24
|
+
attr_reader :produced
|
25
|
+
# Represent the number of items consumed of the total produced
|
26
|
+
attr_reader :consumed
|
27
|
+
# Represent a hash that groups all SMS delivery options
|
28
|
+
attr_reader :options
|
29
|
+
# Reference the log system to register the events
|
30
|
+
attr_reader :log
|
31
|
+
# Reference the config file for smsRuby
|
32
|
+
attr_reader :config
|
33
|
+
# Represent all the possible ports
|
34
|
+
attr_reader :ports
|
35
|
+
# Specify if there is iniialized connections or not
|
36
|
+
attr_reader :avlconn
|
37
|
+
|
38
|
+
|
39
|
+
#
|
40
|
+
# Initialize the admin variables, the system log and the synchronization object
|
41
|
+
#
|
42
|
+
def initialize
|
43
|
+
@sync=Synchronize.new
|
44
|
+
@options={}
|
45
|
+
@log=Logger.new('sms.log')
|
46
|
+
@ports=[]
|
47
|
+
@produced=0
|
48
|
+
@consumed=0
|
49
|
+
@avlconn=false
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# obtains all available connections an execute a code block for each connection
|
54
|
+
# found if a code block is received
|
55
|
+
#
|
56
|
+
def get_connections
|
57
|
+
@@connections.each{ |i,c| yield c if c.status!="disable"} if block_given?
|
58
|
+
return @@connections
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Set specific ports to checked. Try to open all specified connections and create.
|
63
|
+
# Raise an exception if not functional connection is found. A call to open_profile
|
64
|
+
# is made to open connections in the given ports
|
65
|
+
#
|
66
|
+
def open_ports(ports=nil)
|
67
|
+
open=false
|
68
|
+
#if RUBY_PLATFORM =~ /(win|w)32$/
|
69
|
+
#9.times { |i| @ports.push("COM#{i}")}
|
70
|
+
if RUBY_PLATFORM =~ /linux/ and ports.nil?
|
71
|
+
9.times { |i|
|
72
|
+
@ports.push("/dev/ttyUSB#{i}")
|
73
|
+
@ports.push("/dev/ttyACM#{i}")
|
74
|
+
}
|
75
|
+
else
|
76
|
+
if ports.nil?
|
77
|
+
@log.error "No ports were specified"
|
78
|
+
raise "No ports were specified"
|
79
|
+
else
|
80
|
+
ports.each{|p| @ports.push(p)}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
open = open_profiles
|
84
|
+
@log.error "No active connections found or available connections fail" unless open
|
85
|
+
raise 'No active connections found or available connections fail. See log for details' unless open
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Used to open connections and load configuration file. Return true if at least
|
90
|
+
# one connection could be open satisfactorily, false otherwise
|
91
|
+
#
|
92
|
+
def open_profiles
|
93
|
+
begin
|
94
|
+
open=false
|
95
|
+
save_file(@ports)
|
96
|
+
path = 'config_sms.yml'
|
97
|
+
parse=YAML::parse(File.open(path))
|
98
|
+
@config=parse.transform
|
99
|
+
@ports.size.times do |i|
|
100
|
+
open=open_conn("telf"+i.to_s,@ports[i]) || open
|
101
|
+
end
|
102
|
+
@avlconn=true if open
|
103
|
+
open
|
104
|
+
rescue Exception => e
|
105
|
+
@log.error "Error openning connections. Detail #{e.message}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Check if a new connection had been attach.
|
111
|
+
#
|
112
|
+
def update_connections
|
113
|
+
begin
|
114
|
+
@@connections.each{ |i,c| t=c.test; c.status = "disable" if (t!=0 and t!=22 and t!=23)}
|
115
|
+
@ports.select{ |i| (!@@connections.inject(false){|res,act| (act[1].port == i and act[1].status!="disable") || res }) }.each{|i|
|
116
|
+
open_conn("telf"+@ports.index(i).to_s,i)
|
117
|
+
}
|
118
|
+
rescue Exception => e
|
119
|
+
puts "An error has occurred updating connections. Exception:: #{e.message}\n"
|
120
|
+
@log.error "An error has occurred updating connections. Exception:: #{e.message}" unless @log.nil?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# reload the configuration file to update changes
|
126
|
+
#
|
127
|
+
def reload_file
|
128
|
+
parse=YAML::parse(File.open('config_sms.yml'))
|
129
|
+
@config=parse.transform
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Save the configuration file used for gnokii to load phone profiles and open
|
134
|
+
# connections. The required information is specifyed in array
|
135
|
+
#
|
136
|
+
def save_file (array)
|
137
|
+
begin
|
138
|
+
i = 0
|
139
|
+
if RUBY_PLATFORM =~ /(win|w)32$/
|
140
|
+
path = ENV['userprofile']+'/_gnokiirc'
|
141
|
+
elsif RUBY_PLATFORM =~ /linux/
|
142
|
+
path = ENV['HOME']+'/.gnokiirc'
|
143
|
+
end
|
144
|
+
|
145
|
+
File.open(path, 'w') do |f2|
|
146
|
+
array.each do |pos|
|
147
|
+
f2.puts "[phone_telf" + i.to_s + "]"
|
148
|
+
f2.puts "port = " + pos
|
149
|
+
f2.puts "model = AT"
|
150
|
+
f2.puts "connection = serial"
|
151
|
+
i = i+1
|
152
|
+
end
|
153
|
+
f2.puts "[global]"
|
154
|
+
f2.puts "port = COM3"
|
155
|
+
f2.puts "model = AT"
|
156
|
+
f2.puts "connection = serial"
|
157
|
+
f2.puts '[logging]'
|
158
|
+
f2.puts 'debug = off'
|
159
|
+
f2.puts 'rlpdebug = off'
|
160
|
+
end
|
161
|
+
rescue SystemCallError
|
162
|
+
@log.error "Problem writing the configuration file" unless @log.nil?
|
163
|
+
raise "Problem writing the configuration file"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
# Initialize a new connection with the specifyed name and port and add it to the
|
169
|
+
# connections hash, wich holds all available connections. If an exception is
|
170
|
+
# thrown an error will be register in the system log
|
171
|
+
#
|
172
|
+
def open_conn(name,port)
|
173
|
+
begin
|
174
|
+
open=true
|
175
|
+
n = @@connections.size
|
176
|
+
con = Connection.new(name,port)
|
177
|
+
(@config['send']['imeis']).each { |item|con.typec = 's' if con.phone_imei.eql?(item.to_s)}
|
178
|
+
(@config['receive']['imeis']).each {|item|
|
179
|
+
(con.typec == 's' ? con.typec ='sr' : con.typec = 'r') if con.phone_imei.eql?(item.to_s)
|
180
|
+
}
|
181
|
+
con.typec = 's' if (con.typec!= 'r' and con.typec!='sr')
|
182
|
+
puts ":: satisfactorily open a connection in port #{port} imei is #{con.phone_imei} and connection type is: #{con.typec} ::\n"
|
183
|
+
@@connections.merge!({n => con})
|
184
|
+
rescue ErrorHandler::Error => e
|
185
|
+
#@log.info "Openning connection in #{port}.. #{e.message} Not Device Found " unless @log.nil?
|
186
|
+
open=false
|
187
|
+
rescue Exception => e
|
188
|
+
@log.info "Openning connection in port #{port}.. #{e.message}" unless @log.nil?
|
189
|
+
open=false
|
190
|
+
end
|
191
|
+
open
|
192
|
+
end
|
193
|
+
|
194
|
+
#
|
195
|
+
# The internal send function for the Connection Administrator. The config value
|
196
|
+
# specify the option values for the SMS messages to be send. It starts producer
|
197
|
+
# and consumers to distribute the messages to the diferent available connections. A
|
198
|
+
# recovery send is started if at least one message fail to deliver in the first attempt.
|
199
|
+
#
|
200
|
+
def send(config)
|
201
|
+
@options = config
|
202
|
+
config[:dst].delete_if {|x| (!check_phone(x) and (@log.error "Incorrect phone number format for #{x}"if !check_phone(x)))} if !config[:dst].empty?
|
203
|
+
if !config[:dst].empty?
|
204
|
+
bool= !(@@connections.inject(true){|res,act| (act[1].status == "disable" or (act[1].typec=='r' or (act[1].typec=='sr' and act[1].status=="receiving"))) and res }) if !@@connections.empty?
|
205
|
+
if !@@connections.empty? and bool
|
206
|
+
@log.info "Starting send.. #{config[:dst].size} messages to be sent. " unless @log.nil?
|
207
|
+
prod = Thread.new{producer(config[:dst])}
|
208
|
+
conn=Thread.new{verify_connection(5)}
|
209
|
+
pos=0
|
210
|
+
@@connections.each do |i,c|
|
211
|
+
unless c.typec=='r' or (c.typec=='sr' and c.status=="receiving") or c.status =="disable"
|
212
|
+
@@consu[pos] = Thread.new{consumer(i,config[:dst].size)}
|
213
|
+
pos+=1
|
214
|
+
end
|
215
|
+
end
|
216
|
+
prod.join
|
217
|
+
@@consu.each { |c|
|
218
|
+
(c.stop? and c[:wfs]==1) ? (@@connections[c[:connid]].status="available"; c.exit) : c.join
|
219
|
+
}
|
220
|
+
@@consu.clear
|
221
|
+
unless @sync.eq.empty?
|
222
|
+
pos=0
|
223
|
+
@@connections.each do |i,c|
|
224
|
+
unless c.typec=='r' or (c.typec=='sr' and c.status=="receiving" ) or c.status =="disable"
|
225
|
+
@@consu[pos]=Thread.new{send_emergency(i,config[:dst].size)}
|
226
|
+
pos+=1
|
227
|
+
end
|
228
|
+
end
|
229
|
+
@@consu.each { |c|
|
230
|
+
(c.stop? and c[:wfs]==1) ? (@@connections[c[:connid]].status="available"; c.exit) : c.join
|
231
|
+
}
|
232
|
+
check=0
|
233
|
+
while(!@sync.eq.empty?)
|
234
|
+
check=1
|
235
|
+
@log.error "Message: #{config[:msj][0,15]}... to #{@sync.eq.pop} couldn't be sent." unless @log.nil?
|
236
|
+
end
|
237
|
+
conn.exit
|
238
|
+
error = "Message #{config[:msj][0,15]}... couldn't be sent to some destinations. See log for details." if !check
|
239
|
+
yield error if block_given?
|
240
|
+
raise error if check
|
241
|
+
end
|
242
|
+
conn.exit
|
243
|
+
else
|
244
|
+
warn = "Message: #{config[:msj][0,15]}... couldn't be sent. There are no active or available to send connections"
|
245
|
+
@log.warn warn unless @log.nil?
|
246
|
+
yield warn if block_given?
|
247
|
+
raise warn
|
248
|
+
end
|
249
|
+
else
|
250
|
+
warn = "Message: #{config[:msj][0,15]}... couldn't be sent. Bad format or no destination number were specified"
|
251
|
+
@log.warn warn unless @log.nil?
|
252
|
+
yield warn if block_given?
|
253
|
+
raise warn
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
#
|
258
|
+
# Put all destination numbers (produce) into a shared buffer. A synchronization
|
259
|
+
# with all active consumers is required to avoid data loss and incoherence. The
|
260
|
+
# buffer has a limited size, so is up to the producer to handle this matter
|
261
|
+
#
|
262
|
+
def producer(dest)
|
263
|
+
dest.each do |i|
|
264
|
+
begin
|
265
|
+
@sync.mutex.synchronize{
|
266
|
+
@sync.full.wait(@sync.mutex) if (@sync.count == @sync.max)
|
267
|
+
@sync.queue.push i.to_s
|
268
|
+
#puts "Producer: #{i} produced"+"\n"
|
269
|
+
@sync.mutexp.synchronize{
|
270
|
+
@produced += 1
|
271
|
+
}
|
272
|
+
@sync.empty.signal if @sync.count == 1
|
273
|
+
}
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
#
|
279
|
+
# Extract a destination number from the shared buffer and passed along with SMS
|
280
|
+
# message option values to the excetute connection function. A synchronization
|
281
|
+
# with the producer and all other consumers is required to avoid data loss and
|
282
|
+
# incoherence
|
283
|
+
#
|
284
|
+
def consumer(n,max)
|
285
|
+
Thread.current[:wfs]=0
|
286
|
+
Thread.current[:type]='s'
|
287
|
+
Thread.current[:connid]=n
|
288
|
+
loop do
|
289
|
+
@sync.mutexp.synchronize{
|
290
|
+
(@@connections[n].status="available"; Thread.exit) if (@produced >= max && @sync.queue.empty?)
|
291
|
+
}
|
292
|
+
begin
|
293
|
+
@sync.mutex.synchronize{
|
294
|
+
while (@sync.count == 0)
|
295
|
+
Thread.current[:wfs]=1
|
296
|
+
@sync.empty.wait(@sync.mutex)
|
297
|
+
Thread.current[:wfs]=0
|
298
|
+
end
|
299
|
+
Thread.current[:v] = @sync.queue.pop
|
300
|
+
#puts ":: Consumer: in connection #{n} #{Thread.current[:v]} consumed \n"
|
301
|
+
@sync.full.signal if (@sync.count == (@sync.max - 1))
|
302
|
+
}
|
303
|
+
retryable(:tries => 2, :on => ErrorHandler::Error) do
|
304
|
+
@@connections[n].execute(to_hash(Thread.current[:v].to_s))
|
305
|
+
end
|
306
|
+
@consumed+=1
|
307
|
+
@log.info "Message: #{@options[:msj][0,15]}... to #{Thread.current[:v].to_s} sent succsefull from connection with imei #{@@connections[n].phone_imei}." unless @log.nil?
|
308
|
+
rescue ErrorHandler::Error => ex
|
309
|
+
@log.error "Connection in port #{@@connections[n].port} fail sending message to #{Thread.current[:v]}, Message sent to emergency queue. Exception:: #{ex.message}" unless @log.nil?
|
310
|
+
@sync.eq.push(Thread.current[:v])
|
311
|
+
rescue Exception => ex
|
312
|
+
@log.error "Connection in port #{@@connections[n].port} fail sending message to #{Thread.current[:v]}. Exception:: #{ex.message}" unless @log.nil?
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
#
|
318
|
+
# Handles all unsend messages from the consumers due to diferent exceptions (no
|
319
|
+
# signal in phone, error in sim card..). Try to send the messages recovering it
|
320
|
+
# from an emergency queue and discarting it only if none of the active connections
|
321
|
+
# is able to send the message.
|
322
|
+
#
|
323
|
+
def send_emergency(n,max)
|
324
|
+
Thread.current[:wfs]=0
|
325
|
+
Thread.current[:type]='s'
|
326
|
+
Thread.current[:connid]=n
|
327
|
+
loop do
|
328
|
+
begin
|
329
|
+
@sync.mutexe.synchronize{
|
330
|
+
if (@sync.eq.size == 0 and @consumed < max)
|
331
|
+
Thread.current[:wfs] = 1
|
332
|
+
@sync.emptye.wait(@sync.mutexe)
|
333
|
+
Thread.current[:wfs] = 0
|
334
|
+
elsif (@consumed == max)
|
335
|
+
@@connections[n].status="available";
|
336
|
+
Thread.exit
|
337
|
+
end
|
338
|
+
unless @sync.eq.empty?
|
339
|
+
Thread.current[:v]=@sync.eq.pop
|
340
|
+
retryable(:tries => 2, :on => ErrorHandler::Error) do
|
341
|
+
@@connections[n].execute(to_hash(Thread.current[:v].to_s))
|
342
|
+
end
|
343
|
+
@consumed+=1
|
344
|
+
@log.info "EMessage: #{@options[:msj][0,15]}... to #{Thread.current[:v].to_s} sent succsefull from connection with imei #{@@connections[n].phone_imei}." unless @log.nil?
|
345
|
+
p ':: Emergency message consumed '+(max-@consumed).to_s+' left'
|
346
|
+
(Thread.list.each{|t| @sync.emptye.signal if (t!=Thread.current and t!=Thread.main and t[:wfs]==1)}) if @consumed == max
|
347
|
+
end
|
348
|
+
}
|
349
|
+
rescue Exception => e
|
350
|
+
@sync.mutexe.synchronize{
|
351
|
+
@log.error "Connection in port #{@@connections[n].port} fail sending message to #{Thread.current[:v]} at emergency function. Exception:: #{e.message}" unless @log.nil?
|
352
|
+
@sync.eq << Thread.current[:v]
|
353
|
+
@sync.emptye.signal if @sync.eq.size==1
|
354
|
+
}
|
355
|
+
@@connections[n].status="available";
|
356
|
+
Thread.exit
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
#
|
362
|
+
# Check if a new connection had been attach while sending a message. This allows
|
363
|
+
# to start new consumers dinamically and increase the performance
|
364
|
+
#
|
365
|
+
def verify_connection(seconds)
|
366
|
+
begin
|
367
|
+
n=0
|
368
|
+
verify(0,seconds){
|
369
|
+
@ports.select{ |i| (!@@connections.inject(false){|res,act| (act[1].port == i) || res }) }.each{|i|
|
370
|
+
n=@@connections.size
|
371
|
+
if open_conn("telf"+@ports.index(i).to_s,i)
|
372
|
+
unless @@connections[n].typec=='r' or (@@connections[n].typec=='sr' and @@connections[n].status="receiving")
|
373
|
+
@@consu[n]=Thread.new{consumer(n,@options[:dst].size)}
|
374
|
+
end
|
375
|
+
end
|
376
|
+
}
|
377
|
+
}
|
378
|
+
rescue Exception => e
|
379
|
+
puts "An error has occurred during execution of verify connection function. Exception:: #{e.message}"
|
380
|
+
@log.error "An error has occurred during execution of verify connection function. Exception:: #{e.message}" unless @log.nil?
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
#
|
385
|
+
# Excecute every "seconds" for a period of "total" seconds a given code block.
|
386
|
+
# If "total" is 0 the function will loop forever
|
387
|
+
#
|
388
|
+
def verify(total,seconds)
|
389
|
+
start_total=Time.now
|
390
|
+
loop do
|
391
|
+
start_time = Time.now
|
392
|
+
puts "Task started. #{start_time}"
|
393
|
+
yield
|
394
|
+
time_spent=Time.now - start_time
|
395
|
+
puts "Task donde. #{Time.now}"+ "and spend #{time_spent}"
|
396
|
+
break if ((Time.now - start_total) >= total and total != 0)
|
397
|
+
sleep(seconds - time_spent) if time_spent < seconds
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
#
|
402
|
+
# The internal receive function for the Connection Administrator.
|
403
|
+
#
|
404
|
+
def receive(hash)
|
405
|
+
begin
|
406
|
+
conn=nil
|
407
|
+
@@connections.each{|i,c|
|
408
|
+
if c.phone_imei==hash[:imei]
|
409
|
+
@log.info "Start receiving messages from connection with imei: #{hash[:imei]}." unless log.nil?
|
410
|
+
conn=c
|
411
|
+
break
|
412
|
+
end
|
413
|
+
}
|
414
|
+
unless !conn
|
415
|
+
if hash[:receivetype]==0
|
416
|
+
verify(hash[:time],10){
|
417
|
+
list = conn.execute(hash)
|
418
|
+
unless !list
|
419
|
+
list.each do |msj|
|
420
|
+
@log.info "Message received in connection with imei #{conn.phone_imei} from #{msj.source_number}. #{msj.text}."
|
421
|
+
yield msj,conn.phone_imei if block_given?
|
422
|
+
end
|
423
|
+
end
|
424
|
+
}
|
425
|
+
else
|
426
|
+
list = conn.execute(hash)
|
427
|
+
unless !list
|
428
|
+
list.each do |msj|
|
429
|
+
@log.info "Message received in connection with imei #{conn.phone_imei} from #{msj.source_number}. #{msj.text}."
|
430
|
+
yield msj,conn.phone_imei if block_given?
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
conn.status='available'
|
435
|
+
end
|
436
|
+
rescue ErrorHandler::Error => e
|
437
|
+
error = "Fail to receive more messages from connecion with imei #{hash[:imei]}. Detail: #{e.message}"
|
438
|
+
@log.error error unless @log.nil?
|
439
|
+
conn.status='available'
|
440
|
+
raise error
|
441
|
+
rescue Exception => e
|
442
|
+
error = "Exception receiving messages from connecion with imei #{hash[:imei]}. Detail: #{e.message}"
|
443
|
+
@log.error error unless @log.nil?
|
444
|
+
conn.status='available'
|
445
|
+
raise error
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
#
|
450
|
+
# Handles retry for a particular code block. The default numbers of retrys is set
|
451
|
+
# to 1. The retry will be executed on any exception unless a type of error is
|
452
|
+
# specified
|
453
|
+
#
|
454
|
+
def retryable(options = {}, &block)
|
455
|
+
|
456
|
+
opts = { :tries => 1, :on => Exception }.merge(options)
|
457
|
+
|
458
|
+
retry_exception, retries = opts[:on], opts[:tries]
|
459
|
+
|
460
|
+
begin
|
461
|
+
return yield
|
462
|
+
rescue retry_exception
|
463
|
+
retry if (retries -= 1) > 0
|
464
|
+
end
|
465
|
+
yield
|
466
|
+
end
|
467
|
+
|
468
|
+
#
|
469
|
+
# Check the validity of the phone number format
|
470
|
+
#
|
471
|
+
def check_phone(phone)
|
472
|
+
phone_re = /^(\+\d{1,3}\d{3}\d{7})|(0\d{3})\d{7}$/
|
473
|
+
m = phone_re.match(phone.to_s)
|
474
|
+
m ? true : false
|
475
|
+
end
|
476
|
+
|
477
|
+
#
|
478
|
+
# Combine all option values into a hash to relate them
|
479
|
+
#
|
480
|
+
def to_hash(num)
|
481
|
+
{ :type => 'send',
|
482
|
+
:dst => num,
|
483
|
+
:msj => self.options[:msj],
|
484
|
+
:smsc =>self.options[:smsc],
|
485
|
+
:report => self.options[:report],
|
486
|
+
:validity => self.options[:validity]}
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
|
491
|
+
#
|
492
|
+
# The Synchronize class contains all required variables to handle synchronization
|
493
|
+
# between producer - consumers and to protect critical resourses from concurrent
|
494
|
+
# access.
|
495
|
+
#
|
496
|
+
class Synchronize
|
497
|
+
|
498
|
+
# Handle mutual exclution for queue
|
499
|
+
attr_accessor :mutex
|
500
|
+
# Handle mutual exclution for the produced variable (protect the variable)
|
501
|
+
attr_accessor :mutexp
|
502
|
+
# Handle mutual exclution for eq
|
503
|
+
attr_accessor :mutexe
|
504
|
+
# Represent the condition variable for handling an empty queue "queue"
|
505
|
+
attr_accessor :empty
|
506
|
+
# Represent the condition variable for handling an empty queue "eq"
|
507
|
+
attr_accessor :emptye
|
508
|
+
# Represent the condition variable for handling a full queue "queue"
|
509
|
+
attr_accessor :full
|
510
|
+
# Reference a queue that contains all produced items by the producer
|
511
|
+
attr_accessor :queue
|
512
|
+
# Reference a queue that contains destination numbers to wich the SMS messages couldn't be send
|
513
|
+
attr_accessor :eq
|
514
|
+
# Represent the max number of items that queue can retain
|
515
|
+
attr_accessor :max
|
516
|
+
|
517
|
+
#
|
518
|
+
# initialize all variables for the class
|
519
|
+
#
|
520
|
+
def initialize
|
521
|
+
@mutex = Mutex.new
|
522
|
+
@mutexp=Mutex.new
|
523
|
+
@mutexe=Mutex.new
|
524
|
+
@empty = ConditionVariable.new
|
525
|
+
@emptye = ConditionVariable.new
|
526
|
+
@full = ConditionVariable.new
|
527
|
+
@queue = Queue.new
|
528
|
+
@eq = Queue.new
|
529
|
+
@max = 10
|
530
|
+
end
|
531
|
+
|
532
|
+
#
|
533
|
+
# Get the number of items for the queue "queue"
|
534
|
+
#
|
535
|
+
def count
|
536
|
+
@queue.size
|
537
|
+
end
|
538
|
+
|
539
|
+
end
|