]> git.neil.brown.name Git - freerunner.git/blob - gsm/gsm-sms.py
contacts app
[freerunner.git] / gsm / gsm-sms.py
1 #!/usr/bin/env python
2
3 # Send an SMS message using GSM.
4 # Args are:
5 #   sender - ignored
6 #   recipient - phone number
7 #   message - no newlines
8 #
9 # We simply connect to the GSM module,
10 # check for a registration
11 # and
12 #   AT+CMGS="recipient"
13 #   >  message
14 #   >  control-Z
15 #
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:
21 #
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
34 #  03   -  length of rest
35 #  idnumber - random id number
36 #  parts    - number of parts
37 #  this part - this part, starts from '1'
38 #
39 # then AT+CMGS=len-of-TPDU in octets
40 #
41 # TPDU header byte:
42 # Structure: (n) = bits
43 # +--------+----------+---------+-------+-------+--------+
44 # | RP (1) | UDHI (1) | SRI (1) | X (1) | X (1) | MTI(2) |
45 # +--------+----------+---------+-------+-------+--------+
46 #       RP:
47 #               Reply path
48 #       UDHI:
49 #               User Data Header Indicator = Does the UD contains a header
50 #               0 : Only the Short Message
51 #               1 : Beginning of UD containsheader information
52 #       SRI:
53 #               Status Report Indication.
54 #               The SME (Short Message Entity) has requested a status report.
55 #       MTI:
56 #               00 for SMS-Deliver
57 #               01 for SMS-SUBMIT
58
59
60 import atchan, sys, re, random
61
62 def encode_number(recipient):
63     # encoded number is
64     # number of digits
65     # 91 for international, 81 for local interpretation
66     # BCD digits, nibble-swapped, F padded.
67     if recipient[0] == '+':
68         type = '91'
69         recipient = recipient[1:]
70     else:
71         type = '81'
72     leng = '%02X' % len(recipient)
73     if len(recipient) % 2 == 1:
74         recipient += 'F'
75     swap = ''
76     while recipient:
77         swap += recipient[1] + recipient[0]
78         recipient = recipient[2:]
79     return leng + type + swap
80
81 def code7(pad, mesg):
82     # Encode the message as 8 chars in 7 bytes.
83     # pad with 'pad' 0 bits at the start (low in the byte)
84     carry = 0
85     # we have 'pad' low bits stored low in 'carry'
86     code = ''
87     while mesg:
88         c = ord(mesg[0])
89         mesg = mesg[1:]
90         if pad + 7 >= 8:
91             # have a full byte
92             b = carry & ((1<<pad)-1)
93             b += (c << pad) & 255
94             code += '%02X' % b
95             pad -= 1
96             carry = c >> (7-pad)
97         else:
98             # not a full byte yet, just a septet
99             pad = 7
100             carry = c
101     if pad:
102         # a few bits still to emit
103         b = carry & ((1<<pad)-1)
104         code += '%02X' % b
105     return code
106
107 def add_len(mesg):
108     return ('%02X' % (len(mesg)/2)) + mesg
109
110 def send(chan, dest, mesg):
111     n,c = chan.chat('AT+CMGS=%d' % (len(mesg)/2), ['OK','ERROR','>'])
112     if n == 2:
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+') ):
115             return True
116     return False
117
118 recipient = sys.argv[2]
119 mesg = sys.argv[3]
120
121 chan = atchan.AtChannel()
122 chan.connect()
123
124 if not atchan.found(chan.cmdchat('get_power'), 'OK on'):
125     print 'GSM disabled - message not sent'
126     sys.exit(1)
127
128 if not atchan.found(chan.cmdchat('connect'), 'OK'):
129     print 'system error - message not sent'
130     sys.exit(1)
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'
136     sys.exit(1)
137
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'
142     sys.exit(1)
143
144 # use PDU mode
145 n,c = chan.chat('AT+CMGF=0', ['OK', 'ERROR'])
146 if n != 0:
147     print 'Unknown error'
148     sys.exit(1)
149
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
156 if len(mesg) <= 160:
157     # single SMS mesg
158     m1 = code7(0,mesg)
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"
163         sys.exit(0)
164     else:
165         print "ERROR message not sent"
166         sys.exit(1)
167
168 elif len(mesg) <= 5 * 153:
169     # Multiple messsage
170     packets = (len(mesg) + 152) / 153
171     packet = 0
172     mesgid = random.getrandbits(8)
173     while len(mesg) > 0:
174         m = mesg[0:153]
175         mesg = mesg[153:]
176         id = mesgid
177         packet = packet + 1
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)
184             sys.exit(1)
185     print 'OK message sent in %d parts' % packets
186     sys.exit(0)
187 else:
188     print 'Message is too long'
189     sys.exit(1)
190