adammck-rubygsm 0.4 → 0.41

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/rubygsm/core.rb +110 -22
  2. data/rubygsm.gemspec +2 -2
  3. metadata +3 -2
@@ -84,11 +84,17 @@ class Modem
84
84
  # someone else, like a commander
85
85
  @incoming = []
86
86
 
87
- # initialize the modem
88
- command "ATE0" # echo off
89
- command "AT+CMEE=1" # useful errors
90
- command "AT+WIND=0" # no notifications
91
- command "AT+CMGF=1" # switch to text mode
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 incomming
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: #{buf}"
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
- msg = lines[n+1].strip
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 is
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 (msg[0] == 130) and (msg[1].chr == "@")
161
- text = msg[7,999]
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 (msg[5] == 173)
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
- msg = @multipart[from].join("")
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}: #{msg}"
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
- @incoming.push Gsm::Incoming.new(self, from, sent, msg)
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
- command "AT+CNMI=2,2,0,0,0"
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
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rubygsm"
3
- s.version = "0.4"
4
- s.date = "2009-01-29"
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"
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-01-29 00:00:00 -08:00
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: