pysms
changeset 1:f2d4cc3e5eac tip
Now with bling bling!
| author | Tobias Mueller (xbox) <muelli@auftrags-killer.org> |
|---|---|
| date | Sun, 04 Oct 2009 21:50:21 +0100 |
| parents | 2d5b10978ce7 |
| children | |
| files | send_sms.py |
| diffstat | 1 files changed, 209 insertions(+), 14 deletions(-) [+] |
line diff
1.1 --- a/send_sms.py Sat Oct 03 19:53:52 2009 +0100 1.2 +++ b/send_sms.py Sun Oct 04 21:50:21 2009 +0100 1.3 @@ -1,40 +1,235 @@ 1.4 #!/usr/bin/env python 1.5 # -*- coding: utf8 -*- 1.6 +import logging 1.7 +import os 1.8 import serial 1.9 +import sys 1.10 import time 1.11 1.12 DEVICE = '/dev/ttyACM0' 1.13 1.14 -def send_sms(nr, msg): 1.15 - ser = serial.Serial(DEVICE, 115200, timeout=1) 1.16 +log = logging.getLogger() 1.17 + 1.18 +def parse_o2sms_config(config): 1.19 + '''Parses a given string and returns a dict with its values, i.e. 1.20 + >>> parse_o2sms_config("alias foo +353123") 1.21 + {'Contacts': {'foo': ('+353123',)}} 1.22 + >>> parse_o2sms_config("alias \t foo\t+353123") 1.23 + {'Contacts': {'foo': ('+353123',)}} 1.24 + >>> parse_o2sms_config("alias foo +353123 +353456") 1.25 + {'Contacts': {'foo': ('+353123', '+353456')}} 1.26 + >>> parse_o2sms_config("alias foo +353123 bar") 1.27 + {'Contacts': {'foo': ('+353123', 'bar')}} 1.28 + >>> parse_o2sms_config("""username foo 1.29 + ... password\tbar""") 1.30 + {'username': 'foo', 'password': 'bar', 'Contacts': {}} 1.31 + ''' 1.32 + config = config.strip() 1.33 + ''' > 1.34 + ''' 1.35 + parsed = {} 1.36 + contacts = {} 1.37 + for line in config.splitlines(): 1.38 + log.debug('Looking at line: %s', line) 1.39 + if not line.startswith('#'): 1.40 + tokens = [t.strip() for t in line.strip().split()] 1.41 + 1.42 + if len(tokens) == 1: 1.43 + log.critical("Dunno what has only one token: %s", tokens) 1.44 + elif len(tokens) == 2: 1.45 + key, value = tokens 1.46 + if not key == 'alias': 1.47 + parsed[key] = value 1.48 + elif len(tokens) >= 3: 1.49 + log.debug("We must have found an alias! %s", tokens) 1.50 + assert tokens[0] == 'alias' 1.51 + key, values = tokens[1], tokens[2:] 1.52 + contacts[key] = tuple(values) 1.53 + 1.54 + parsed['Contacts'] = contacts 1.55 + return parsed 1.56 + 1.57 +def parse_o2sms_config_file(fname="~/.o2sms/config"): 1.58 + config_path = os.path.expanduser(fname) 1.59 + config = file(config_path, 'r').read() 1.60 + return parse_o2sms_config(config) 1.61 + 1.62 +def send_sms(nr, msg, device='/dev/ttyACM0'): 1.63 + log.debug('Sending SMS to %s', nr) 1.64 + ser = serial.Serial(device, 115200, timeout=1) 1.65 + assert ser.isOpen() 1.66 ser.write('AT\r') 1.67 line = ser.readline() 1.68 - print line 1.69 + log.debug('RECV: %s', line) 1.70 line = ser.readline() 1.71 - print line 1.72 + log.debug('RECV: %s', line) 1.73 assert line == "OK\r\n" 1.74 - 1.75 + log.debug('Sucessfully opened device') 1.76 ser.write('AT+CMGF=1\r') 1.77 line = ser.readline() 1.78 + log.debug('RECV: %s', line) 1.79 line = ser.readline() 1.80 + log.debug('RECV: %s', line) 1.81 assert line == "OK\r\n" 1.82 - 1.83 + 1.84 ser.write('AT+CMGS="%s"\r' % nr) 1.85 - ser.write('%s\n' % msg) 1.86 + ser.write('%s' % msg) 1.87 ser.write(chr(26)) 1.88 time.sleep(3) 1.89 lines = ser.readlines() #read a ā\nā terminated line 1.90 - print lines 1.91 - ser.close() 1.92 + log.debug('RECV: %s', lines) 1.93 + if any([line.startswith('ERROR') for line in lines]): 1.94 + log.critical('Reveiced an Error!\n%s', lines) 1.95 + #ser.close() 1.96 + log.debug('Sucessfully sent message!') 1.97 + 1.98 1.99 +def is_phone_number(number): 1.100 + '''Determines whether a given string looks like a fully qualified phone number 1.101 + >>> is_phone_number('+1234') 1.102 + True 1.103 + >>> is_phone_number('foo') 1.104 + False 1.105 + >>> is_phone_number('123') 1.106 + False 1.107 + >>> is_phone_number('+123sab') 1.108 + False 1.109 + ''' 1.110 + return number[0] == '+' and number[1:].isdigit() 1.111 + 1.112 +def resolve_candidate(cand, config=None): 1.113 + ''' Tries to resolve a given string by deciding whether it's a well 1.114 + formed number or looking it (recursively) up in the contacts 1.115 + >>> resolve_candidate('+353123') 1.116 + ('+353123',) 1.117 + >>> resolve_candidate('08123') 1.118 + ('+3538123',) 1.119 + >>> config = {'Contacts':{'foo1':('+353123',)}} 1.120 + >>> resolve_candidate('foo1', config) 1.121 + ('+353123',) 1.122 + >>> config = {'Contacts':{'bar':('+353456',), 'foo2':('+353123','bar')}} 1.123 + >>> cands = resolve_candidate('foo2', config) 1.124 + >>> '+353123' in cands 1.125 + True 1.126 + >>> '+353456' in cands 1.127 + True 1.128 + ''' 1.129 + if cand[0].isalpha(): 1.130 + 'Probably a name, so load config' 1.131 + if not config: 1.132 + config = parse_o2sms_config_file() 1.133 + contacts = config['Contacts'] 1.134 + numbers = contacts[cand] 1.135 + candidates = set() 1.136 + for number in numbers: 1.137 + if not is_phone_number(number): 1.138 + log.debug('%s is not a fully qualified number, so try to recursively look that up', number) 1.139 + resolved_numbers = resolve_candidate(number, config) 1.140 + for resolved_number in resolved_numbers: 1.141 + candidates.add(resolved_number) 1.142 + else: 1.143 + candidates.add(number) 1.144 + candidates = tuple(candidates) 1.145 + 1.146 + elif is_phone_number(cand): 1.147 + candidates = (cand,) 1.148 + elif cand.startswith('08'): 1.149 + candidates = ('+353' + cand[1:],) 1.150 + else: 1.151 + log.critical('Dunno what to do with candidate %s. Is it in your local numbers? Why doesnt start it with a + or 08?', cand) 1.152 + candidates = tuple() 1.153 + return candidates 1.154 + 1.155 + 1.156 +def split_string(message, threshold=160): 1.157 + '''Splits a string every $threshold characters 1.158 + >>> len(list(split_string('x'*150, 160))) 1.159 + 1 1.160 + >>> len(list(split_string('x'*150, 60))) 1.161 + 3 1.162 + >>> for m in split_string('x', 160): print m 1.163 + x 1.164 + >>> for m in split_string('12', 1): print m 1.165 + 1 1.166 + 2 1.167 + ''' 1.168 + m = message 1.169 + while len(m) > 0: 1.170 + yield m[:threshold] 1.171 + m = m[threshold:] 1.172 + 1.173 + 1.174 +def is_o2_number(number): 1.175 + '''Tries to determine whether a number is from O2Ireland by just 1.176 + looking at it's prefix. Pretty lame but still... 1.177 + >>> is_o2_number('086123') 1.178 + True 1.179 + >>> is_o2_number('+35386123') 1.180 + True 1.181 + >>> is_o2_number('087123') 1.182 + False 1.183 + >>> is_o2_number('+35387123') 1.184 + False 1.185 + >>> is_o2_number('') 1.186 + False 1.187 + >>> is_o2_number('foo') 1.188 + False 1.189 + ''' 1.190 + normalized = resolve_candidate(number) 1.191 + return normalized.startswith('+35386') 1.192 + 1.193 if __name__ == "__main__": 1.194 import optparse 1.195 parser = optparse.OptionParser( 1.196 - usage = "%prog number message", 1.197 + usage = "%prog number", 1.198 description = "Simple SMS Interface" 1.199 ) 1.200 + 1.201 + parser.add_option("-d", "--device", 1.202 + dest="device", default="/dev/rfcomm2", 1.203 + help="Use this serial device to send the SMS, you might want to add it to /etc/bluetooth/rfcomm") 1.204 + parser.add_option("-l", "--loglevel", dest="loglevel", 1.205 + help="Sets the loglevel to one of debug, info, warn, error, critical", 1.206 + default="warn") 1.207 + parser.add_option("-m", "--message", 1.208 + dest="message", default=None, 1.209 + help="Use this message and do not wait for stdin") 1.210 + parser.add_option("-t", "--test", 1.211 + action="store_true", dest="test", default=False, 1.212 + help="Run Tests") 1.213 (options, args) = parser.parse_args() 1.214 - 1.215 - nr, msg = args[0], args[1] 1.216 - 1.217 - send_sms(nr, msg) 1.218 + device = options.device 1.219 + 1.220 + loglevel = {'debug': logging.DEBUG, 'info': logging.INFO, 1.221 + 'warn': logging.WARN, 'error': logging.ERROR, 1.222 + 'critical': logging.CRITICAL}.get(options.loglevel, "warn") 1.223 + logging.basicConfig(level=loglevel) 1.224 + log = logging.getLogger("SMS Main") 1.225 + log.debug('running with %s', options) 1.226 + test = options.test 1.227 + if test: 1.228 + import doctest 1.229 + doctest.testmod() 1.230 + sys.exit(0) 1.231 + 1.232 + if not len(args) > 0: 1.233 + parser.error('Please specify at least one number') 1.234 + '''Will bailout here automatically''' 1.235 + candidates = args 1.236 + numbers = set() 1.237 + for cand in candidates: 1.238 + cands = resolve_candidate(cand) 1.239 + numbers.add(cands) 1.240 + if options.message is None: 1.241 + log.info('Trying to read from stdin') 1.242 + message = sys.stdin.read().strip() 1.243 + else: 1.244 + message = options.message 1.245 + log.debug('Taking message from argument: len %d', len(message)) 1.246 + 1.247 + for number in numbers: 1.248 + msgs = tuple(split_string(message)) 1.249 + log.info('Message has %d chars and is weigths %d sms', len(message), len(msgs)) 1.250 + for msg in msgs: 1.251 + send_sms(number, msg.strip(), device) 1.252 + #time.sleep(5)
