El servicio necesario para enviar o recibir SMS desde una computadora conectada a Internet se denomina gateway SMS, y si bien el servicio de envío lo proveen las mismas compañías de telefonía celular, la recepción es más delicada, ya que nadie provee un servicio de acceso sencillo para forwardearme los mensajes que lleguen a un determinado número a mi aplicación. A continuación voy a explicar como se puede fabricar un gateway SMS fatto in casa utilizando un celular común y silvestre.
Ingredientes
- Una computadora con un adaptador Bluetooth (o no, ver nota a continuación)
- Un celular con Bluetooth
- Python + python-bluez
Técnicamente no es necesario usar bluetooth puesto que lo que vamos a hacer es enviar comandos AT estándar sobre un enlace serial. Esto se puede hacer tanto por Bluetooth como con el cable adaptador USB o serial correspondiente, artículo del que no dispongo. Debería ser trivial portar las ideas de mi programa para funcionar con un enlace cableado.
Como sistema operativo utilicé Ubuntu 9.04, pero debería funcionar en casi cualquier sistema actual puesto que las librerías necesarias funcionan también en Windows.
Pasos a seguir
El funcionamiento del gateway que propongo es sencillo; se conecta al teléfono mediante la conexión inalámbrica y puede realizar una de tres acciones:
- Enviar un mensaje de texto a un número determinado
- Obtener todos los mensajes de texto almacenados en el teléfono
- Borrar todos los mensajes de texto almacenados en el teléfono
Tras ingresar dicho valor en el script que pongo a continuación, se realizará la conexión con el celular. Es probable que el celular requiera una aprobación manual para cada vez que se realice una conexión (un mensaje del tipo "¿Desea conectarse con el dispositivo XXX?"). Como esto no es muy cómodo en un servidor, en los teléfonos Nokia esta opción puede desactivarse una vez que se ha establecido la conexión (en el apartado "Conexiones activas" del menú Bluetooth, se puede indicar que un cierto dispositivo no requiere confirmación).
El código que adjunto a continuación hace el resto del trabajo, gran parte del cual consiste en parsear el extraño formato PDU en que el teléfono entrega los mensajes de texto cuando se le pide. El código que hace esto está fuertemente basado en smspdu, que modifiqué para hacer un poco más amigable. gateway.py
#!/usr/bin/python #Copyright (c) 2009 Gonzalo Sainz-Trapaga #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: # #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. # #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. import bluetooth import select import pdu DEBUG = False class Nokia6103: def __init__(self, hwaddr, port): self.sockfd = bluetooth.BluetoothSocket(bluetooth.RFCOMM) self.sockfd.connect((hwaddr, port)) self._send('ATZ') def _log(self, s): if DEBUG: print s def _read(self): i = [self.sockfd] w = [] e = [self.sockfd] out = '' while True: ir, wr, er = select.select(i, w, e, 3) if len(ir) == 0: self._log("Select: espera finalizada - saliendo.") break if len(er) > 0: self._log("Select: condicion de excepcion - saliendo.") break out += i[0].recv(1000) if out.find('OK\r\n') != -1: self._log("Select: OK alcanzado - saliendo.") break return out def _send(self, s): self.sockfd.send('%s\r' % s) out = self._read() self._log(out) return out def sendSMS(self, num, txt): self._send('AT+CMGF=1') self._send('AT+CMGS="%s"' % num) self.sockfd.send(txt + "\n") self.sockfd.send(chr(26)) def getAllSMS(self): s = self._send('AT+CMGL=4') lines = s.split('\r\n') lines.pop(0) msgs = [] for i, msg in enumerate(lines): if i % 2 == 0: if not msg.startswith('+CMGL'): break else: msgs.append(pdu.decodePdu(msg)) return msgs def deleteAllSMS(self): self._send('AT+CMGD=1,4') def close(self): self.sockfd.close() if __name__ == '__main__': port = 1 hwaddr = '00:19:B7:XX:XX:XX' n = Nokia6103(hwaddr, port) print n.sendSMS('+541150501234', 'Mensaje de prueba!') print n.getAllSMS()pdu.py
#Copyright (c) 2009 Eric Gradman, Gonzalo Sainz-Trapaga #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: # #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. # #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. from cStringIO import StringIO from math import ceil from binascii import unhexlify, hexlify from itertools import cycle from datetime import datetime def decodePdu(s): s = unhexlify(s) d = StringIO(s) # parse SMSC information p = {} p['smsc_len'] = d.read(1) p['type_of_address'] = d.read(1) p['sc_num'] = unsemi(d.read(ord(p['smsc_len'])-1)) p['msg_type'] = d.read(1) p['address_len'] = d.read(1) p['type_of_address'] = d.read(1) p['sender_num'] = unsemi(d.read(int(ceil(ord(p['address_len'])/2.0)))) p['pid'] = d.read(1) p['dcs'] = d.read(1) ts = d.read(7) p['ts'], p['tz'] = parseTimeStamp(ts) p['udl'] = d.read(1) p['user_data'] = d.read(ord(p['udl'])) p['user_data'] = decodeUserData(p['user_data']) for f in ['sc_num', 'sender_num']: if p[f].endswith('f'): p[f] = p[f][:-1] return p def unnibleSwapChar(c): c = ord(c) d1 = c & 0x0F d2 = c >> 4 return int(str(d1) + str(d2)) def parseTimeZone(c): c = ord(c) d1 = c & 0x0F d2 = c >> 4 neg = d1 >> 3 d1 = d1 & 0x7 units = int(str(d1) + str(d2)) if neg: zona = '-' else: zona = '' zona += str(units // 4) zona += ':' zona += "%.02d" % ((units % 4) * 15) return zona def parseTimeStamp(s): ts = s[:6] tz = s[-1:] f = [unnibleSwapChar(c) for c in ts] f[0] = f[0] + 2000 zona = parseTimeZone(tz) return datetime(*f), zona def decodeUserData(s): bytes = map(ord, s) strips = cycle(range(1,9)) out = "" c = 0 # carry clen = 0 # carry length in bits while len(bytes): strip = strips.next() if strip == 8: byte = 0 ms = 0 ls = 0 else: byte = bytes.pop(0) # take strip bytes off the top ms = byte >> (8-strip) ls = byte & (0xff >> strip) #print "%d byte %x ms %x ls %x" % (strip, byte, ms, ls) # append the previous byte = ((ls << clen) | c) & 0xff out += chr(byte) c = ms clen = strip % 8 if strip == 7: out += chr(ls) # changed 6/11/09 to incorporate Carl's suggestion in comments return out def unsemi(s): """turn PDU semi-octets into a string""" l = list(hexlify(s)) out = "" while len(l): out += l.pop(1) out += l.pop(0) return out
8 comentarios:
Groso, te felicito!
Saludos
justo con un amigo estabamos comenzando a investigar el tema... estabamos en su casa y empezamos a jugar mas que nada con el identificador de llamadas, realizar llamadas, y creo que llegamos a jugar un poco con sms, via bluetooth y comando at.
Ahora, mucho no recuerdo porque lo dejamos así nomas, pero creo que existia la posibilidad de obtener el string, sin ese formato feo pdu.
Chequeo y vuelvo.
Cuando arranque mi portatil para seguir jugando pero desde un entorno gnu/linux sinceramente me mareé, asique tu codigo me va a ser muy valioso.
Gracias por la data.
bueno, voli...
AT+CMGF=? retorna 1, 0 o ambos.
Ahora, lo que importa:
0: permite trabajar en modo PDU.
1: permite trabajar con texto.
En mi caso, mi nokia 6131, retorna (0,1).
Para cambiar entre un y otro modo: AT+CMGF=1
seguiremos jugando con este temilla.
Solo falta la pasarela de audio over bluetooth jajajaja
Hola, mi celular también retorna esas 2 opciones, pero me daba error si intentaba usar el formato texto para leer mensajes (no así para enviarlos). Esa es la razón por la que utilicé el decoder de formato PDU.
En caso de que les interese acá hay mucha información sobre comandos AT (y las miles de posibilidades que hay, de las cuales lo que yo hice es apenas una probadita):
http://www.developershome.com/sms/atCommandsIntro.asp
yo la opcion PDU no se porque pero no la estoy podiendo utilizar... la opcion texto tampoco, pero como llueve yd ecidi no ir a la facu me colgue toqueteando con esto... :)
Hola, una consulta.
Estaba probando esto en un 6131 tambien.
Pero el AT+CMGL=4 o bien en modo texto con "ALL", me devuelve ok con hyperterminal, pero nunca lee ningun mensaje.
Mis mensajes se almacenan en la memoria del equipo, no en el SIM. Y segun encontre en los foros de Nokia este telefono esta preparado para leer los mensajes solo del SIM. Tambien vi un comando para cambiar la ubicacion de lectura del storage pero no me da bola.
Cuando hiciste esta prueba tu Nokia permitia guardar los mensajes en la SIM?
Muchas gracias!
Cual es l SMS Gateway de la compañia Open Mobile
como lo ejecuto?? soy nuevo en esto O_o
Publicar un comentario