Пользоваться им совсем просто:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import smsutils
# отправляем SMS
smsutils.SendSMS('Привет, мир!', '79031234567', '/dev/ttyUSB0')
# читаем SMS с карты
result = smsutils.GetSMS('/dev/ttyUSB0')
for r in result:
print r[0], ' ', r[1], ' ', r[2], '\n', r[3]
Исходный код smsutils.py таков:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
#
# Набор вспомогательных функций для работы с SMS
#
# большое спасибо:
# http://www.varesano.net/blog/fabio/serial%20rs232%20connections%20python
# http://www.dreamfabric.com/sms/
import serial, datetime, time, random
# Преобразование номера телефона в международном формате в формат SMS
#
# Исходная строка, содержащая телефон в международном формате 79130123456,
# дополняется справа символом F - 79130123456F,
# разбивается на пары символов - 79 13 01 23 45 6F,
# в каждой паре символы меняются местами - 97 31 10 32 54 F6,
# слева приписывается идентификатор международного формата (91) - 91 97 31 10 32 54 F6,
# слева приписывается количество цифр в телефоне, т.е. 11 в шестнадцатеричном формате (0B) - 0B 91 97 31 10 32 54 F6
#
# Возвращаемое значение - строка, содержащая закодированный номер телефона 0B919731103254F6
#
def PhoneNumberToSMS(number):
number += 'F'
result = '0B' + '91'
i = 0
while i < len(number):
result += number[i+1] + number[i]
i += 2
return result
# Преобразование строки символов в формат SMS
#
# Каждый двухбайтовый юникодный символ в строке разбивается на пару байт,
# и формируется новая строка, состоящая из шестнадцатеричных представлений этих байтов
#
# Возвращаемое значение - строка, содержащая строку символов в формате SMS
#
def TextToSMS(text):
b = text
result = ''
i = 0
while i < len(b):
o = ord(b[i])
result += ("%0.2X" % (o/256)) + ("%0.2X" % (o%256))
i += 1
return result
# Восстановление номера телефона в международном формате в из формата SMS
#
# Исходная строка, содержащая закодированный телефон 9731103254F6
# разбивается на пары символов - 97 31 10 32 54 F6,
# в каждой паре символы меняются местами - 79 13 01 23 46 6F,
# убирается символ F - 79130123456
#
# Возвращаемое значение - строка, содержащая номер телефона 79130123456
#
def SMSToPhoneNumber(data):
result = ""
i = 0
while i < len(data):
result += data[i+1] + data[i]
i += 2
return result[:-1]
# Восстановление строки символов в из формата SMS
#
# Исходная строка разбивается на четверки символов, которые преобразуются в целые числа
# и формируется строка, состоящая из соответствующих этим числам символов
#
# Возвращаемое значение - раскодированная строка
#
def SMSToText(text):
result = u''
i = 0
while i+3 < len(text):
result += unichr(int(text[i] + text[i+1] + text[i+2] + text[i+3],16))
i += 4
return result
# преобразование целого числа в строку из нулей и единиц, соответствующую его двоичной записи
# (использовалось для отладки)
def ByteToBitsString(byte, n):
result = ''
for i in range(0,n):
if byte & (1 << i) != 0:
result = '1' + result
else:
result = '0' + result
return result
# Восстановление строки из её семибитного кода
#
# На вход подается закодированная строка - 4DEA10
# эта строка разбивается на пары символов - 4D EA 10,
# каждая пара трактуется как шестнадцатиричное представление байта - 0x4D 0xEA 0x10 = 01001101 11101010 00010000
# из первого байта 01001101 берутся семь младших битов 1001101 и преобразуются в соответствующий символ ASCII - M
# оставшийся бит 0 дополняется слева шестью младшими битами второго байта 101010: 1010100 - T
# оставшиеся два бита 11 дополняются слева пятью младшими битами третьего байта 10000: 1000011 - С
# и т.д.
#
# Возвращаемое значение - раскодированная строка MTC
#
def Decode7bit(text):
result = ''
bytes = [int(text[i*2:i*2+2],16) for i in range(0,len(text)/2)]
symbol = 0
bits = 0
n = 0
while n < len(bytes):
if bits == 7:
result += chr(symbol)
symbol = 0
bits = 0
else:
symbol += (bytes[n] & (0x7F >> bits)) << bits
result += chr(symbol)
symbol = (bytes[n] & (0x7F << (7-bits))) >> (7-bits)
bits = (8-7) + bits
n += 1
if bits > 0 and symbol != 0:
result += chr(symbol)
return result
# Класс для работы с часовыми поясами (см следующую функцию)
class smsTZ(datetime.tzinfo):
hours = 0
def __init__(self, h):
self.hours = h
def utcoffset(self, dt):
return datetime.timedelta(hours=self.hours)
def dst(self, dt):
return datetime.timedelta(0)
# Восстановление даты и времени из их представления SMS
#
# На вход подается закодированная строка - 11113131516461
# эта строка разбивается на пары символов - 11 11 31 31 51 64 61,
# в каждой паре символы меняются местами - 11 11 13 13 15 46 16,
# получившиеся строки трактуются как шестнадцатиричные представления байтов - 0x11 0x11 0x13 0x13 0x15 0x46 0x16
# эти байты представляют собой соответственно год, месяц, день, часы, минуты, секунды, часовой пояс
# часовой пояс представляется как количество четвертей часа, т.е. 0x16 = GMT+4 (седьмой бит отвечает за знак)
#
# Возвращаемое значение - дата и время 2011-11-13 13:15:46+04:00
#
def SMSToTimeStamp(text):
year = int(text[1] + text[0]) + 2000
month = int(text[3] + text[2])
day = int(text[5] + text[4])
hour = int(text[7] + text[6])
minute = int(text[9] + text[8])
second = int(text[11] + text[10])
tz = int(text[13] + text[12])
tz = ( (tz & 0x7F) if (tz & 0x80 == 0) else -(tz & 0x7F) ) / 4
return datetime.datetime(year, month, day, hour, minute, second, 0, smsTZ(tz))
# Обмен с последовательным портом
def str_send (ser, textline):
ser.write(textline)
out = ''
# let's wait one second before reading output (let's give device time to answer)
N = 10
while N > 0:
time.sleep(1)
while ser.inWaiting() > 0:
out += ser.read(1)
if ('OK' in out) or ('ERROR' in out) or ('>' in out):
N = 1
N -= 1
return out
# отправка пин-кода в открытый порт
def SendPINToPort(ser, pin):
str_send(ser, 'AT+CPIN="%s"\r' % (pin))
# отправка пин-кода модему
def SendPIN(serial_name, pin):
# подключаемся к порту
ser = serial.Serial(serial_name, 115200, timeout=1)
ser.open()
# отправляем пин-код
SendPINToPort(ser, pin)
# закрываем порт
ser.close()
# отправка SMS-сообщения
def SendSMS(msg, phone, serial_name, pin=None):
# если нечего или некуда отправлять, выходим
if msg == '' or len(phone) != 11:
return
# декодируем сообщение в utf-8
message = msg.decode('utf-8')
# разрезаем сообщение на кусочки по 66 символов
chunks = []
if len(message) > 70:
while len(message) > 66:
chunks.append(message[:66])
message = message[66:]
if len(message) > 0:
chunks.append(message)
# инициализируем служебную информацию
SMS_SUBMIT_PDU = "11"
CSMS_reference_number = ""
# если сообщение требует конкатенации SMS, то подправляем служебную информацию
# и генерируем четырехсимвольный номер сообщения
if len(chunks) > 1:
SMS_SUBMIT_PDU = "51"
CSMS_reference_number = "%0.4X" % random.randrange(1,65536)
# подключаемся к порту
ser = serial.Serial(serial_name, 115200, timeout=1)
ser.open()
# устанавливаем формат передачи сообщения - PDU
status = str_send(ser, 'AT+CMGF=0\r')
# если в ответ пришел текст, содержащий SIM PIN REQUIRED, значит, модему нужен пин-код
if 'SIM PIN' in status:
SendPINToPort(ser, pin)
str_send(ser, 'AT+CMGF=0\r')
# отсылаем сообщение по кусочкам
i = 1
for chunk in chunks:
# кодируем кусочек
emessage = TextToSMS(chunk)
# если сообщение состоит из нескольких кусочков, то в каждом кусочке надо указать
# номер сообщения, количество кусочков и порядковый номер кусочка (1,2,3 и т.д.)
if CSMS_reference_number != "":
emessage = "06" + "08" + "04" + CSMS_reference_number + \
("%0.2X" % len(chunks)) + ("%0.2X" % i) + emessage
# готовим строку для отправки в порт
sms = \
"00" + \
SMS_SUBMIT_PDU + \
"00" + \
PhoneNumberToSMS(phone) + \
"00" + \
"08" + \
"AA" + \
"%0.2X" % (len(emessage)/2) + \
emessage
# подготавливаем модем - передаем ему длину отправляемой строки
str_send(ser, 'AT+CMGS=' + str(len(sms)/2-1) + '\r')
# отправляем строку
str_send(ser, sms + '\x1A')
i += 1
# закрываем порт
ser.close()
# чтение SMS сообщений с сим-карты
#
# возвращаемое значение: список, состоящий из кортежей, каждый из которых содержит
# номер слота на сим-карте, в котором находится сообщение (0-19),
# номер телефона отправителя,
# дату отправления сообщения,
# текст сообщения
#
def GetSMS(serial_name, pin=None):
result = []
# подключаемся к порту
ser = serial.Serial(serial_name, 115200, timeout=1)
ser.open()
# устанавливаем формат передачи сообщения - PDU
status = str_send(ser, 'AT+CMGF=0\r')
# если в ответ пришел текст, содержащий SIM PIN REQUIRED, значит, модему нужен пин-код
if 'SIM PIN' in status:
SendPINToPort(ser, pin)
str_send(ser, 'AT+CMGF=0\r')
# запрашиваем список сообщений (4 - все сообщения)
messages = str_send(ser, 'AT+CMGL=4\r')
if 'ERROR' not in messages:
strings = messages.split('\n')
i = 0
while i < len(strings):
if '+CMGL: ' in strings[i]:
message_header = strings[i][7:]
message_body = strings[i+1]
offset = 0
SMSC_length = int(message_body[offset:offset+2],16)
offset += 2
SMSC_address = message_body[offset:offset+2*SMSC_length]
SMSC_typeOfAddress = SMSC_address[:2]
SMSC_serviceCenterNumber = SMSToPhoneNumber( SMSC_address[2:] )
offset += 2*SMSC_length
SMS_deliverBits = int(message_body[offset:offset+2],16)
offset += 2
SMS_senderNumberLength = int(message_body[offset:offset+2],16)
offset += 2
SMS_senderNumberType = message_body[offset:offset+2]
offset += 2
SMS_senderNumber = message_body[offset:offset+SMS_senderNumberLength+(1 if SMS_senderNumberLength & 1 != 0 else 0) ]
if SMS_senderNumberType == '91':
SMS_senderNumber = SMSToPhoneNumber(SMS_senderNumber)
if int(SMS_senderNumberType[0],16) & 5 == 5:
SMS_senderNumber = Decode7bit(SMS_senderNumber)
offset += SMS_senderNumberLength+(1 if SMS_senderNumberLength & 1 != 0 else 0)
TP_protocolIdentifier = message_body[offset:offset+2]
offset += 2
TP_dataCodingScheme = int(message_body[offset:offset+2],16)
offset += 2
TP_serviceCenterTimeStamp = SMSToTimeStamp(message_body[offset:offset+14])
offset += 14
TP_userDataLength = int(message_body[offset:offset+2],16)
offset += 2
if SMS_deliverBits & 64 != 0:
SMS_userDataHeaderLength = int(message_body[offset:offset+2],16)
offset += 2
SMS_userDataHeader = message_body[offset:offset+2*SMS_userDataHeaderLength]
offset += 2*SMS_userDataHeaderLength
message_text = None
if (TP_dataCodingScheme == 0):
message_text = Decode7bit(message_body[offset:])
if (TP_dataCodingScheme & 8 != 0):
message_text = SMSToText(message_body[offset:])
if message_text is None:
message_text = message_body[offset:]
# добавляем в результирующий список кортеж, содержащий
# номер слота на сим-карте, в котором находится сообщение (0-19),
# номер телефона отправителя,
# дату отправления сообщения,
# текст сообщения
result.append((message_header.split(',')[0], SMS_senderNumber, TP_serviceCenterTimeStamp, message_text))
i += 2
else:
i += 1
# закрываем порт
ser.close()
return result
# удаление SMS сообщения в слоте с номером slot с сим-карты
def DeleteSMS(serial_name, slot, pin=None):
# подключаемся к порту
ser = serial.Serial(serial_name, 115200, timeout=1)
ser.open()
# удаляем сообщение
status = str_send(ser, 'AT+CMGD=%s\r' % (slot))
# если в ответ пришел текст, содержащий SIM PIN REQUIRED, значит, модему нужен пин-код
if 'SIM PIN' in status:
SendPINToPort(ser, pin)
str_send(ser, 'AT+CMGD=%s\r' % (slot))
# закрываем порт
ser.close()