adammck-rubygsm 0.4 → 0.41
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/rubygsm/core.rb +110 -22
- data/rubygsm.gemspec +2 -2
- metadata +3 -2
data/lib/rubygsm/core.rb
CHANGED
@@ -84,11 +84,17 @@ class Modem
|
|
84
84
|
# someone else, like a commander
|
85
85
|
@incoming = []
|
86
86
|
|
87
|
-
# initialize the modem
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
87
|
+
# initialize the modem; rubygsm is (supposed to be) robust enough to function
|
88
|
+
# without these working (hence the "try_"), but they make different modems more
|
89
|
+
# consistant, and the logs a bit more sane.
|
90
|
+
try_command "ATE0" # echo off
|
91
|
+
try_command "AT+CMEE=1" # useful errors
|
92
|
+
try_command "AT+WIND=0" # no notifications
|
93
|
+
|
94
|
+
# PDU mode isn't supported right now (although
|
95
|
+
# it should be, because it's quite simple), so
|
96
|
+
# switching to text mode (mode 1) is MANDATORY
|
97
|
+
command "AT+CMGF=1"
|
92
98
|
end
|
93
99
|
|
94
100
|
|
@@ -98,7 +104,9 @@ class Modem
|
|
98
104
|
|
99
105
|
|
100
106
|
INCOMING_FMT = "%y/%m/%d,%H:%M:%S%Z" #:nodoc:
|
101
|
-
|
107
|
+
CMGL_STATUS = "REC UNREAD" #:nodoc:
|
108
|
+
|
109
|
+
|
102
110
|
def parse_incoming_timestamp(ts)
|
103
111
|
# extract the weirdo quarter-hour timezone,
|
104
112
|
# convert it into a regular hourly offset
|
@@ -125,17 +133,17 @@ class Modem
|
|
125
133
|
next
|
126
134
|
end
|
127
135
|
|
128
|
-
# since this line IS a CMT string (an
|
136
|
+
# since this line IS a CMT string (an incoming
|
129
137
|
# SMS), parse it and store it to deal with later
|
130
138
|
unless m = lines[n].match(/^\+CMT: "(.+?)",.*?,"(.+?)".*?$/)
|
131
|
-
err = "Couldn't parse CMT data: #{
|
139
|
+
err = "Couldn't parse CMT data: #{lines[n]}"
|
132
140
|
raise RuntimeError.new(err)
|
133
141
|
end
|
134
142
|
|
135
143
|
# extract the meta-info from the CMT line,
|
136
144
|
# and the message from the FOLLOWING line
|
137
145
|
from, timestamp = *m.captures
|
138
|
-
|
146
|
+
msg_text = lines[n+1].strip
|
139
147
|
|
140
148
|
# notify the network that we accepted
|
141
149
|
# the incoming message (for read receipt)
|
@@ -153,12 +161,13 @@ class Modem
|
|
153
161
|
log "Receipt acknowledgement (CNMA) was rejected"
|
154
162
|
end
|
155
163
|
|
156
|
-
# we might abort if this
|
164
|
+
# we might abort if this part of a
|
165
|
+
# multi-part message, but not the last
|
157
166
|
catch :skip_processing do
|
158
167
|
|
159
168
|
# multi-part messages begin with ASCII char 130
|
160
|
-
if (
|
161
|
-
text =
|
169
|
+
if (msg_text[0] == 130) and (msg_text[1].chr == "@")
|
170
|
+
text = msg_text[7,999]
|
162
171
|
|
163
172
|
# ensure we have a place for the incoming
|
164
173
|
# message part to live as they are delivered
|
@@ -174,24 +183,25 @@ class Modem
|
|
174
183
|
|
175
184
|
# abort if this is not the last part
|
176
185
|
throw :skip_processing\
|
177
|
-
unless (
|
186
|
+
unless (msg_text[5] == 173)
|
178
187
|
|
179
188
|
# last part, so switch out the received
|
180
189
|
# part with the whole message, to be processed
|
181
190
|
# below (the sender and timestamp are the same
|
182
191
|
# for all parts, so no change needed there)
|
183
|
-
|
192
|
+
msg_text = @multipart[from].join("")
|
184
193
|
@multipart.delete(from)
|
185
194
|
end
|
186
195
|
|
187
196
|
# just in case it wasn't already obvious...
|
188
|
-
log "Received message from #{from}: #{
|
197
|
+
log "Received message from #{from}: #{msg_text.inspect}"
|
189
198
|
|
190
199
|
# store the incoming data to be picked up
|
191
200
|
# from the attr_accessor as a tuple (this
|
192
201
|
# is kind of ghetto, and WILL change later)
|
193
202
|
sent = parse_incoming_timestamp(timestamp)
|
194
|
-
|
203
|
+
msg = Gsm::Incoming.new(self, from, sent, msg_text)
|
204
|
+
@incoming.push(msg)
|
195
205
|
end
|
196
206
|
|
197
207
|
# drop the two CMT lines (meta-info and message),
|
@@ -309,7 +319,7 @@ class Modem
|
|
309
319
|
# then automatically re-try the command after
|
310
320
|
# a short delay. for others, propagate
|
311
321
|
rescue Error => err
|
312
|
-
log "Rescued: #{err.desc}"
|
322
|
+
log "Rescued (in #command): #{err.desc}"
|
313
323
|
|
314
324
|
if (err.type == "CMS") and (err.code == 515)
|
315
325
|
sleep 2
|
@@ -322,6 +332,24 @@ class Modem
|
|
322
332
|
end
|
323
333
|
|
324
334
|
|
335
|
+
# proxy a single command to #command, but catch any
|
336
|
+
# Gsm::Error exceptions that are raised, and return
|
337
|
+
# nil. This should be used to issue commands which
|
338
|
+
# aren't vital - of which there are VERY FEW.
|
339
|
+
def try_command(cmd, *args)
|
340
|
+
begin
|
341
|
+
log_incr "Trying Command: #{cmd}"
|
342
|
+
out = command(cmd, *args)
|
343
|
+
log_decr "=#{out}"
|
344
|
+
return out
|
345
|
+
|
346
|
+
rescue Error => err
|
347
|
+
log_then_decr "Rescued (in #try_command): #{err.desc}"
|
348
|
+
return nil
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
|
325
353
|
def query(cmd)
|
326
354
|
log_incr "Query: #{cmd}"
|
327
355
|
out = command cmd
|
@@ -754,7 +782,9 @@ class Modem
|
|
754
782
|
#
|
755
783
|
# Starts a new thread, which polls the device every _interval_
|
756
784
|
# seconds to capture incoming SMS and call _callback_method_
|
757
|
-
# for each
|
785
|
+
# for each, and polls the device's internal storage for incoming
|
786
|
+
# SMS that we weren't notified about (some modems don't support
|
787
|
+
# that).
|
758
788
|
#
|
759
789
|
# class Receiver
|
760
790
|
# def incoming(msg)
|
@@ -783,12 +813,17 @@ class Modem
|
|
783
813
|
# keep on receiving forever
|
784
814
|
while true
|
785
815
|
command "AT"
|
786
|
-
|
787
|
-
# enable new message notification mode
|
788
|
-
# every ten intevals, in case the
|
816
|
+
|
817
|
+
# enable new message notification mode every ten intevals, in case the
|
789
818
|
# modem "forgets" (power cycle, etc)
|
790
819
|
if (@polled % 10) == 0
|
791
|
-
|
820
|
+
try_command("AT+CNMI=2,2,0,0,0")
|
821
|
+
end
|
822
|
+
|
823
|
+
# check for new messages lurking in the device's
|
824
|
+
# memory (in case we missed them (yes, it happens))
|
825
|
+
if (@polled % 4) == 0
|
826
|
+
fetch_stored_messages
|
792
827
|
end
|
793
828
|
|
794
829
|
# if there are any new incoming messages,
|
@@ -822,5 +857,58 @@ class Modem
|
|
822
857
|
# threaded (like debugging handsets)
|
823
858
|
@thr.join if join_thread
|
824
859
|
end
|
860
|
+
|
861
|
+
|
862
|
+
def fetch_stored_messages
|
863
|
+
|
864
|
+
# fetch all/unread (see constant) messages
|
865
|
+
lines = command('AT+CMGL="%s"' % CMGL_STATUS)
|
866
|
+
n = 0
|
867
|
+
|
868
|
+
# if the last line returned is OK
|
869
|
+
# (and it SHOULD BE), remove it
|
870
|
+
lines.pop if lines[-1] == "OK"
|
871
|
+
|
872
|
+
# keep on iterating the data we received,
|
873
|
+
# until there's none left. if there were no
|
874
|
+
# stored messages waiting, this done nothing!
|
875
|
+
while n < lines.length
|
876
|
+
|
877
|
+
# attempt to parse the CMGL line (we're skipping
|
878
|
+
# two lines at a time in this loop, so we will
|
879
|
+
# always land at a CMGL line here) - they look like:
|
880
|
+
# +CMGL: 0,"REC READ","+13364130840",,"09/03/04,21:59:31-20"
|
881
|
+
unless m = lines[n].match(/^\+CMGL: (\d+),"(.+?)","(.+?)",*?,"(.+?)".*?$/)
|
882
|
+
err = "Couldn't parse CMGL data: #{lines[n]}"
|
883
|
+
raise RuntimeError.new(err)
|
884
|
+
end
|
885
|
+
|
886
|
+
# find the index of the next
|
887
|
+
# CMGL line, or the end
|
888
|
+
nn = n+1
|
889
|
+
nn += 1 until\
|
890
|
+
nn >= lines.length ||\
|
891
|
+
lines[nn][0,6] == "+CMGL:"
|
892
|
+
|
893
|
+
# extract the meta-info from the CMGL line, and the
|
894
|
+
# message text from the lines between _n_ and _nn_
|
895
|
+
index, status, from, timestamp = *m.captures
|
896
|
+
msg_text = lines[(n+1)..(nn-1)].join("\n").strip
|
897
|
+
|
898
|
+
# log the incoming message
|
899
|
+
log "Fetched stored message from #{from}: #{msg_text.inspect}"
|
900
|
+
|
901
|
+
# store the incoming data to be picked up
|
902
|
+
# from the attr_accessor as a tuple (this
|
903
|
+
# is kind of ghetto, and WILL change later)
|
904
|
+
sent = parse_incoming_timestamp(timestamp)
|
905
|
+
msg = Gsm::Incoming.new(self, from, sent, msg_text)
|
906
|
+
@incoming.push(msg)
|
907
|
+
|
908
|
+
# skip over the messge line(s),
|
909
|
+
# on to the next CMGL line
|
910
|
+
n = nn
|
911
|
+
end
|
912
|
+
end
|
825
913
|
end # Modem
|
826
914
|
end # Gsm
|
data/rubygsm.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "rubygsm"
|
3
|
-
s.version = "0.
|
4
|
-
s.date = "2009-
|
3
|
+
s.version = "0.41"
|
4
|
+
s.date = "2009-03-05"
|
5
5
|
s.summary = "Send and receive SMS with a GSM modem"
|
6
6
|
s.email = "adam.mckaig@gmail.com"
|
7
7
|
s.homepage = "http://github.com/adammck/rubygsm"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adammck-rubygsm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.41"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Mckaig
|
@@ -9,11 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-03-05 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: toholio-serialport
|
17
|
+
type: :runtime
|
17
18
|
version_requirement:
|
18
19
|
version_requirements: !ruby/object:Gem::Requirement
|
19
20
|
requirements:
|