smsRuby 1.0.0-x86-linux
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/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
|