3 # Send an SMS message using GSM.
6 # recipient - phone number
7 # message - no newlines
9 # We simply connect to the GSM module,
10 # check for a registration
16 # Sending multipart sms messages:
17 # ref: http://www.developershome.com/sms/cmgsCommand4.asp
18 # 1/ set PDU mode with AT+CMGF=0
19 # 2/ split message into 153-char bundles
20 # 3/ create messages as follows:
22 # 00 - this says we aren't providing an SMSC number
23 # 41 - TPDU header - type is SMS-SUBMIT, user-data header present
24 # 00 - please assign a message reference number
25 # xx - length in digits of phone number
26 # 91 for IDD, 81 for "don't know what sort of number this is"
27 # 164030... BCD phone number, nibble-swapped, pad with F at end if needed
28 # 00 - protocol identifier??
29 # 00 - encoding - 7 bit ascii
30 # XX - length of message in septets
31 # Then the message which starts with 7 septets of header that looks like 6 octets.
32 # 05 - length of rest of header
33 # 00 - multi-path with 1 byte id number
35 # idnumber - random id number
36 # parts - number of parts
37 # this part - this part, starts from '1'
39 # then AT+CMGS=len-of-TPDU in octets
42 # Structure: (n) = bits
43 # +--------+----------+---------+-------+-------+--------+
44 # | RP (1) | UDHI (1) | SRI (1) | X (1) | X (1) | MTI(2) |
45 # +--------+----------+---------+-------+-------+--------+
49 # User Data Header Indicator = Does the UD contains a header
50 # 0 : Only the Short Message
51 # 1 : Beginning of UD containsheader information
53 # Status Report Indication.
54 # The SME (Short Message Entity) has requested a status report.
60 import atchan, sys, re, random
62 def encode_number(recipient):
65 # 91 for international, 81 for local interpretation
66 # BCD digits, nibble-swapped, F padded.
67 if recipient[0] == '+':
69 recipient = recipient[1:]
72 leng = '%02X' % len(recipient)
73 if len(recipient) % 2 == 1:
77 swap += recipient[1] + recipient[0]
78 recipient = recipient[2:]
79 return leng + type + swap
82 # Encode the message as 8 chars in 7 bytes.
83 # pad with 'pad' 0 bits at the start (low in the byte)
85 # we have 'pad' low bits stored low in 'carry'
92 b = carry & ((1<<pad)-1)
98 # not a full byte yet, just a septet
102 # a few bits still to emit
103 b = carry & ((1<<pad)-1)
108 return ('%02X' % (len(mesg)/2)) + mesg
110 def send(chan, dest, mesg):
111 n,c = chan.chat('AT+CMGS=%d' % (len(mesg)/2), ['OK','ERROR','>'])
113 n,c = chan.chat('%s%s\r\n\032' % (dest, mesg), ['OK','ERROR'], 10000)
114 if n == 0 and atchan.found(c, re.compile('^\+CMGS: \d+') ):
118 recipient = sys.argv[2]
121 chan = atchan.AtChannel()
124 if not atchan.found(chan.cmdchat('get_power'), 'OK on'):
125 print 'GSM disabled - message not sent'
128 if not atchan.found(chan.cmdchat('connect'), 'OK'):
129 print 'system error - message not sent'
131 chan.chat1(None, [ 'AT-Command Interpreter ready' ], 500)
132 # clear any pending error status
133 chan.chat1('ATE0', ['OK', 'ERROR'])
134 if chan.chat1('ATE0', ['OK', 'ERROR']) != 0:
135 print 'system error - message not sent'
138 want = re.compile('^\+COPS:.*".+"')
139 n,c = chan.chat('AT+COPS?', ['OK', 'ERROR'], 2000 )
140 if n != 0 or not atchan.found(c, want):
141 print 'No Service - message not sent'
145 n,c = chan.chat('AT+CMGF=0', ['OK', 'ERROR'])
147 print 'Unknown error'
150 # SMSC header and TPDU header
151 SMSC = '00' # No SMSC number given, use default
152 REF = '00' # ask network to assign ref number
153 phone_num = encode_number(recipient)
154 proto = '00' # don't know what this means
155 encode = '00' # 7 bit ascii
159 m2 = '%02X'%len(mesg) + m1
160 coded = "01" + REF + phone_num + proto + encode + m2
161 if send(chan, SMSC, coded):
162 print "OK message sent"
165 print "ERROR message not sent"
168 elif len(mesg) <= 5 * 153:
170 packets = (len(mesg) + 152) / 153
172 mesgid = random.getrandbits(8)
178 UDH = add_len('00' + add_len('%02X%02X%02X'%(id, packets, packet)))
179 m1 = UDH + code7(1, m)
180 m2 = '%02X'%(7+len(m)) + m1
181 coded = "41" + REF + phone_num + proto + encode + m2
182 if not send(chan, SMSC, coded):
183 print "ERROR message not sent at part %d/%d" % (packet,packets)
185 print 'OK message sent in %d parts' % packets
188 print 'Message is too long'