Показаны сообщения с ярлыком SMS. Показать все сообщения
Показаны сообщения с ярлыком SMS. Показать все сообщения

воскресенье, 12 августа 2012 г.

SMS+python - закрывая тему

Наконец-то руки дошли разобраться с получением списка SMS-сообщений из моего Huawei E1550. Всё это дело (и отправку, и прием) свёл в один маленький модуль с претенциозным названием smsutils.py.

Пользоваться им совсем просто:
#!/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()

воскресенье, 24 апреля 2011 г.

Отправка SMS через usb-модем с использованием Python

После окончательного перехода на убунту отказалась работать .NET-фреймворковская программка, через которую работала отправка SMS-сообщений. Возникла естественная мысль переписать её, скажем, на питоне. Благодаря подсказкам это оказалось совсем тривиально:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import serial
import time
import random

# процедура для отправки строки в модем и получения ответа
def str_send (ser, textline):
print "<<" + textline
ser.write(textline)

out = ''
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):
print ">>" + out
N = 1

N -= 1

# функция преобразования телефонного номера в формат, пригодный для SMS
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
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

# вводим с консоли сообщение и переводим его в юникод
message = raw_input('Текст сообщения:\n')
message = message.decode('utf-8')

# если сообщение большое - режем его на кусочки для механизма конкатенации SMS
chunks = []

if len(message) > 70:
while len(message) > 66:
chunks.append(message[:66])
message = message[66:]

if len(message) > 0:
chunks.append(message)

# готовим номер группы сообщений и устанавливаем 6-й бит SMS_SUBMIT_PDU
SMS_SUBMIT_PDU = "11"
CSMS_reference_number = ""
if len(chunks) > 1:
SMS_SUBMIT_PDU = "51"
CSMS_reference_number = "%0.4X" % random.randrange(1,65536)

# связываемся с модемом
ser = serial.Serial("/dev/ttyUSB0", 115200, timeout=1)
ser.open()

# устанавливаем нужный формат передачи данных
str_send(ser, 'AT+CMGF=0\r')

# передаем кусочки сообщения
i = 1
for chunk in chunks:
emessage = TextToSMS(chunk)
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("7383XXXXXXX") + \
"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()

NB. import serial заработает только если будет установлен модуль pySerial (aptitude install python-serial)

Настройка Huawei E1750 в Debian Lenny

Настройка Huawei E1750 в Debian Lenny. Ну, у меня e1550, но тоже проканало ^_^

вкратце:
1. смотрим с помощью команды lsusb, что модем вообще виден
2. устанавливаем libusb в соответствии с рекомендациями:
aptitude install tcl libusb-dev make gcc
3. стаскиваем с http://www.draisberghof.de/usb_modeswitch архивы usb-modeswitch и usb-modeswitch-data, распаковываем их и устанавливаем командой sudo make install
4. перезапускаем udev:
sudo invoke-rc.d udev restart
5. переподключаем модем и смотрим, что получилось, командой:
ls -l /dev/ttyUSB*
(у меня какого-то черта вместо /dev/ttyUSB0 получилось: /dev/ttyUSB_utps_modem)

Кстати, ещё, возможно, придется дать права на этот девайс: в /etc/udev/rules.d/50-udev-default.rules прописать примерно такое:
KERNEL=="ttyUSB_utps_modem", MODE="0666"
И применить правила командой:
udevadm control --reload-rules && udevadm trigger

понедельник, 1 ноября 2010 г.

Чокнулся GSM-модем

Начал выдавать с завидной периодичностью вот такое:
^BOOT:78236782,0,0,0,87
^RSSI:7
^RSSI:7
^RSSI:7

Вылечилось командой AT^CURC=0 благодаря подсказке отсюда:
You might try to use this command
AT^CURC? Current setting of periodic status messages
AT^CURC=? See what you possible values are
AT^CURC=0 turn off periodic status messages

суббота, 31 июля 2010 г.

Отправлять длинные SMS из своей программы

Чудо, чудо! Если использовать конкатенацию SMS, то можно обойти жуткое ограничение в 70 символов на сообщение!

Но, конечно, пришлось разбивать сообщение на кусочки, к каждому из которых в начало прописывать по шесть байтов:

text =
"06" + // Length of User Data Header
"08" + // Concatenated short messages, 16-bit reference number
"04" + // Length of the header, excluding the first two fields
CSMS_reference_number + // уникальный номер длинного сообщения
chunks.Count.ToString("X2") + // число кусков сообщения
(i+1).ToString("X2") + // номер куска
text;

и в SMS-SUBMIT PDU устанавливать 6-й бит, так что он теперь стал не "11", а "51".

воскресенье, 25 июля 2010 г.

Отправлять SMS из своей программы

В общем-то, C# рулит...

1. Самой большой проблемой было добиться от сотового телефона на команду "AT" внятный ответ "OK".
Использовал System.IO.Ports.SerialPort:

System.IO.Ports.SerialPort serialPort = new SerialPort("COM5");
serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);
serialPort.BaudRate = 115200;
serialPort.Open();
serialPort.WriteLine("AT\r");

В обработчике serialPort_DataReceived получилось примерно следующее:

void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
this.SetText(serialPort.ReadExisting());
}

Соответственно, основные пляски с бубном - это позволить написать что-то в textBox1 из другой нитки:

delegate void SetTextCallback(string text);
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text += "\r\nrcv: " + text;
}
}

2. Теперь о собственно передаче сообщений. Очень заманчиво было бы воспользоваться советами, приведенными тут. В самом деле,

AT+CSCS="UCS2"\r
AT+CSMP=1,167,0,8\r
AT+CMGS="+31638740161"\r
06450631062D06280627 + Convert.ToChar(26)

и всё, sms-ка ушла, что может быть проще? Но почему-то оказалось не судьба. На второй команде Siemens CX75 взбрыкнул, и пришлось пойти длинным путем.

Вот тут и тут написана куча полезного. Получилось такое:

AT+CMGF=0\r
AT+CMGS= + (sms.Length/2-1).ToString() + \r
sms + Convert.ToChar(26)

Здесь строка sms конструируется так:

string text = TextToSMS("АБРАКАДАБРА");
string sms =
"00" + // длина SMSC информации (пусть телефон ее додумает сам)
"11" + // first octet of the SMS-SUBMIT PDU
// (установлен бит 0 -> вид сообщения=SMS-SUBMIT)
// (установлен бит 3 -> есть поле TP-VP )
"00" + // TP-Message-Reference. "00" lets the phone set the message reference number itself.
PhoneNumberToSMS("79030123456") + // номер получателя
"00" + // PID - идентификатор номера протокола
"08" + // DCS схема кодирования данных (08 - UCS2=кириллица)
"AA" + // TP-VP - Validity Period, время жизни сообщения на сервере (AA - установлен в 4 дня)
(text.Length/2).ToString("X2") + // длина сообшения
text;


Две функции, PhoneNumberToSMS и TextToSMS, служат для кодирования номера телефона и текста сообщения соответственно:

private string PhoneNumberToSMS(string number)
{
number += "F";
string result =
"0B" + // длина номера (12 цифр, включая F)
"91"; // международный формат номера
int i = 0;
while (i < number.Length)
{
result += number[i + 1].ToString() + number[i].ToString();
i += 2;
}
return result;
}

private string TextToSMS(string text)
{
byte[] b = (new System.Text.UnicodeEncoding()).GetBytes(text);
string result = "";
int i = 0;
while (i < b.Length)
{
result += b[i + 1].ToString("X2") + b[i].ToString("X2");
i += 2;
}
return result;
}