Radio Life - Работа с GPRS модемом Telit

Работа с GPRS модемом Bitcord СТ-2-05

1. С чего начать.

GPRS-модем Bitcord СТ2-05 представляет из себя небольшую коробочку с двумя слотами для сим-карт, индикаторами TIMER и NET и набором разъемов. На начальном этапе работы требуются три из них — разъем питания, RS232 и антенный разъем.

Рис. 1. Внешний вид модема

Задача, собственно, очень простая — обеспечить соединение модема с сервером TELNET и организовать прием/передачу данных через порт RS232 с помощью любой терминальной программы (Hyper Teminal, Putty и т. п.).

Соединение будем устанавливать через Мегафон с фиксированным IP.

Естественно, начать надо с того, чтобы вставить в слот для SIM1 сим-карту, присоединить антенну, затем подключить питание. Должен несколько раз мигнуть, затем загореться зеленый индикатор TIMER. В процессе работы он будет "подмигивать" с некоторым интервалом, который зависит от установленного времени таймера перезапуска модема.

Частое мигание индикатора NET показывает, что соединения с оператором нет. Если он не переходит в режим "помаргивания" с интервалом примерно 3 сек., то надо проверить антенну, сим-карту и вообще наличие соответствующего оператора в данном месте.

Конечно, требуется компьютер с нормальным 9-пиновым COM-портом. Если имеется только USB, то можно использовать преобразователь USB-COM.

Порт RS232 модема по умолчанию установлен в режим автоматического определения скорости передачи, поэтому на компьютере можно установить скорость передачи любую. Я поставил 115200, 8 бит, 1 стоповый.

Подключаем кабель модема к порту компьютера и приступаем к настройке.

2. Информация о модели, сброс к "заводским" настройкам

Сначала надо проверить "доступность" модема. Набираем команду AT:


AT
OK

Если получили ответ OK, можно продолжать. Для начала определим версию модели и прошивки. Можно заметить, что команды модему можно набирать как прописными, так и строчными буквами:


at+cgmm (модель)
GL865-DUAL-V3.1
OK
at+cgmr (прошивка)
16.01.173
OK
AT+GMI (информация о производителе)
telit
OK

В процессе работы может потребоваться сбросить модем к начальным установкам. Обнаружить, что сбрасывает стандартная команда AT&F0(1) не удалось (похоже, что ничего). Поэтому приходится использовать следующие команды:


at+cfun=4
OK
at+cmar=00000000
OK

Команда AT+CFUN=4 деактивирует SIM-карту и сбрасывает сетевое соединение. Команда AT+CMAR=00000000 сбрасывает данные пользователя. 00000000 — "секретный код". Возможно, на других модемах он может быть иным, но производитель данного устройства указал для сброса такой код.

При этом форматируется память, поэтому все пользовательские скрипты в модеме также будут стерты. После сброса перестает работать индикатор NET, поэтому необходимо ввести следующие команды:


at#sled=2,10,10
OK
at#sledsav
OK
at&w
OK

3. Настройка сетевых параметров

Прежде всего — проверим установки и подключение SIM-карты:


at#qss?    (запрос статуса SIM-карты)
#QSS: 0,1  (0 — запрос статуса по команде QSS, 1 — SIM-карта вставлена)
OK
at+cpin?      (запрос готовности PIN-кода)
+CPIN: READY  (ввод PIN-кода не требуется)
OK
at+cgreg?     (запрос статуса GPRS)
+CGREG: 0,1   (0 — отключен код регистрации в сети, 1 — модем зарегистрирован в сети)
OK
at+cops?      (запрос имени оператора)
+COPS: 0,0,"MegaFon RUS"
OK

Итак, SIM-карта подключилась к нужному оператору (в нашем случае — Мегафон) и теперь можно перейти к настройкам собственно соединения. Сначала проверим установки параметров сокетов:


at#scfg?

#SCFG: 1,1,300,90,600,50
#SCFG: 2,1,300,90,600,50
#SCFG: 3,1,300,90,600,50
#SCFG: 4,2,300,90,600,50
#SCFG: 5,2,300,90,600,50
#SCFG: 6,2,300,90,600,50

Команда выводит параметры для 6 возможных сокетов и двух (из 5 возможных) наборов контекстов PDP (Packet Data Protocol type). Нам требуется всего один сокет (для telnet-соединения), выберем для работы сокет 1 и контекст 1. Рассмотрим по порядку, что означают эти цифры для первого сокета.

1 — номер сокета; 1 — номер контекста PDP, который используется для этого сокета; 300 — размер пакета данных, передаваемых по TCP-соединению; 90 — таймаут ожидания сброса соединения при отсутствии активности (в секундах); 600 — таймаут ожидания установки соединения (в сотнях миллисекунд); 50 — таймаут передачи данных (в сотнях миллисекунд), в случае, если пакет для передачи меньше размера пакета (в данном случае — 300 байт).

Подробнее о значении этих параметров, возможно, скажу ниже, а пока можно оставить их "как есть".

Теперь настроим параметры контекста соединения. Это делает команда CGDCONT:


at+cgdcont=1,"IP","FixedIP.nw"

Здесь: 1 — идентификатор контекста; IP — тип протокола передачи данных (интернет протокол); FixedIP.nw — имя точки доступа APN, зависит от оператора и им же должно сообщаться. В нашем случае используется фиксированный IP Мегафона. Команда CGDCONT имеет еще ряд параметров, но для нашего случая достаточно и этих (остальные отсавим по умолчанию), поэтому ими и ограничимся. Значения всех параметров контекста можно посмотреть той же командой:


at+cgdcont?
+CGDCONT: 1,"IP","FixedIP.nw","",0,0
OK

У нас настроен только один контекст, поэтому выводится только одна строка. При настройке нескольких контекстов PDP будет столько строк, сколько есть настроенных контекстов.

Приведенные выше настройки сохраняются в памяти модема и при отключении питания, поэтому их достаточно ввести только один раз.

4. Установка соединения

Перед установкой соединения с сервером telnet еще раз проверим наличие соединения с сетевым оператором командой CREG:


at+creg?
+CREG: 0,1
OK

Результат аналогичен описанной выше команде CGREG.

Далее активируем собственно контекст командой SGACT:

 
at#sgact=1,1
#SGACT: 100.000.000.000
OK

В строке команды можно дополнительно указать пользователя и пароль, но в нашем случае этого не требуется. При успешном завершении команда выводит IP адрес модема, который в случае SIM-карты со статическим адресом будет всегда один и тот же.

И, наконец, устанавливаем telnet соединение с сервером с помощью команды SD:


at#sd=1,0,23,200.200.100.100
CONNECT
œœ œ#œ'

1 — номер сокета; 0 — протокол передачи TCP; 23 — номер порта; 200.200.100.100 — адрес сервера.

Возможно, в большинстве случаев приведенного выше набора команд для работы будет достаточно, однако в нашем случае сервер не только ответил загадочным набором символов, но и категорически отказывается воспринимать ввод с клавиатуры. Можно добавить, что в случае бездействия, через интервал, установленный командой SCFG (при указанных выше настройках этот таймаут равен 90 сек) соединение будет сброшено, и модем выведет сообщение NO CARRIER. В любом случае желательно решить две проблемы:

1. Набирать указанные команды при каждом подключении не очень удобно — всегда есть вероятность ошибки.

2. Для установки соединения с нашим сервером необходимо сделать еще что-то, чтобы обеспечить возможность обмениваться данными.

Для решения этих задач придется обратиться к скриптам, которые будем писать на языке Python, интерпретатор которого встроен в данный модем.

5. Подготовка к работе со скриптами

Для разработки скриптов необходим следующий минимум программного обеспечения:

1. Любой текстовой редактор. Я пользуюсь tsWebEditor.

2. Терминальный клиент HiperTerminal. Выбор этой программы обусловлен тем, что с ее помощью можно загружать в модем текстовые неоткомпилированные скрипты. Настройки программы для загрузки приведены в инструкции telit_easy_script_python_r18.pdf и будут приведены ниже.

3. Для сборки скриптов нужен Telit Python package (TelitPy1.5.2+_v4.1.exe), который содержит необходимые библиотеки.

4. Для загрузки в модем откомпилированных скриптов желателен Rsterm.

Желательность компиляции обусловлена тем, что время запуска текстового скрипта резко возрастает при увеличении его размера, и может достигать на совсем простеньком скрипте десятков минут. Однако скрипт в несколько строк загружается всего несколько минут, поэтому для коротких программ можно ограничиться двумя первыми пунктами.

6. Простой скрипт

Перейдем к практике. Сначала напишем самый простой скрипт, который будет автоматически запускаться при включении питания модема (или по команде с терминала).

Для общения с внешним миром в модемы Telit встроен ряд программных модулей, которые и будем использовать. Эти модули следующие:

MOD — некоторый набор функций, имеющих отношение к работе самого модема.

MDM — интерфейс, который отвечает за обмен командами и данными с внешним миром.

SER — интерфейс последовательного порта, через который происходит прием/передача данных.

Пока достаточно этих модулей, что будет видно из текста программы. Для того, чтобы использовать эти модули, их необходимо "подключить", для чего в текст программы надо вставить следующие строки:


import MOD
import MDM
import SER

После подключения необходимых модулей, пишем собственно скрипт:


SER.send('GPRS CONTEXT activated ... \r\n')
MDM.send('AT#SGACT=1,1\r', 10)
MOD.sleep(20)
SER.send('GPRS Connect ...\r\n')
MOD.sleep(20)
MDM.send('AT#SD=1,0,23,200.200.100.100\r', 10)

Как видно, программа очень проста (даже не производится проверка результата выполнения команд), но для начала освоения работы этого достаточно. Разберем значение каждой команды.

MOD.sleep(20): простая задержка, выражающаяся в сотнях миллисекунд. Нужна для того, чтобы дать как модему, так и оператору время на выполнение операции.

SER.send('GPRS CONTEXT activated ... \r\n'): команда вывода на COM-порт информационного сообщения, чтобы мы могли хотя бы знать, что скрипт запустился.

MDM.send('AT#SGACT=1,1\r', 10): описанная выше команда активации контекста PDP.

MDM.send('AT#SD=1,0,23,200.200.100.100\r', 10): установка соединения с сервером.

Полный текст скрипта получается такой:


import MOD
import MDM
import SER

SER.send('GPRS CONTEXT activated ... \r\n')
MDM.send('AT#SGACT=1,1\r', 10)
MOD.sleep(20)
SER.send('GPRS Connect ...\r\n')
MOD.sleep(20)
MDM.send('AT#SD=1,0,23,200.200.100.100\r', 10)

Сохраняем его в виде текстового файла с расширением".py", например, test.py, и перейдем к процессу загрузки и запуска.

Рис. 2. Настройки параметров COM-порта

Прежде всего, приведем настройки HyperTerminal'а в соответствие с рекомендациями Telit. Рекомендованные настройки показаны на рис. 2—4.

Рис. 3. Свойства терминала

Рис. 4. Настройки параметров ASCII

Передача данных в модем производится в режиме отправки текстового файла (см. рис. 5). Для этого вводится следующая команда:


at#wscript="test.py",227

>>>

Где test.py — имя файла, а 227 — его размер. Естественно, у вас может быть и другой размер, в зависимости от оформления текста скрипта.

Рис. 5. Загрузка файла в модем.

После появления на экране терминала приглашения к передаче (>>>), следует успеть перейти к передаче файла. Обратите внимание, что для того, чтобы иметь возможность указать терминалу имя передаваемого файла в окошке Тип файлов следует указать Все файлы (*.*).

В случае успешной передачи будет получено стандартное подтверждение OK. Чтобы убедиться в том, что файл загружен в модем, можно посмотреть содержимое каталога модема:


at#lscript
#LSCRIPT: "test.py",227

OK

Далее настроим режим запуска скрипта. Это можно сделать с помощью команды STARTMODESCR, применив ее следующим образом:


at#startmodescr=1,30

OK

Первый параметр определяет режим запуска скрипта: 0 — запуск зависит от уровня сигнала DTR. На мой взгляд, это не совсем удобно. 1 — запуск скрипта произойдет автоматически через интервал, указанный во втором параметре команды (в нашем случае — 30 сек.), при условии, что в течение этого интервала не будет введена команда через порт RS232. Мне больше нравится этот вариант, т. к. он позволяет надежно остановить запуск скрипта, что вполне может понадобится при отладке.

Теперь надо указать модему, какой скрипт следует запустить (ведь скриптов в модем можно записать не один — памяти для этого достаточно). Это устанавливается следующей командой:


at#escript="test.py"

OK
at#escript?
#escript="test.py"

OK

В заключение добавлю, что если по каким-то причинам вы остановили запуск скрипта вводом команды через порт RS232, то его при необходимости можно запустить "вручную":


at#execscr

OK

При этом будет запущен скрипт, ранее указанный командой ESCRIPT.

Теперь можно перезапустить модем (например, выключив и включив питание) и посмотреть что у нас получилось. В общем, при нормальных условиях, на экране терминала, подключенного к модему, должно появиться примерно следующее: FF FD 18 FF FD 20 FF FD 23 FF FD 27 (я перевел "непечатные" символы в 16-ричный код, чтобы дальнейшее было понятнее).

Что с этим делать, тем более, что, повторю, никакого отклика на нажатие клавиш на терминале не происходит?

7. Работа с командами IAC

Дело в том, что в протоколе telnet предусмотрен запрос сервером у клиента параметров соединения или самого клиента. Обмен производится с помощью команд, предваряемых кодом IAC (Interpret As Command — интерпретировать как команду, код 0xFF).

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


IAC =  0xFF

DONT = 0xFE
DO =   0xFD
WONT = 0xFC
WILL = 0xFB
SB =   0xFA
AYT =  0xF6
SE =   0xF0

TELOPT_ECHO =        0x01
TELOPT_SGA =         0x03
TELOPT_STATUS =      0x05
TELOPT_SUPDUP =      0x15
TELOPT_TTYPE =       0x18
TELOPT_NAWS =        0x1F
TELOPT_TSPEED =      0x20
TELOPT_LFLOW =       0x21
TELOPT_XDISPLOC =    0x23
TELOPT_NEW_ENVIRON = 0x27

TELQUAL_IS =   0x00
TELQUAL_SEND = 0x01

Теперь можно "расшифровать" запрос сервера: WILL TTYPE — запрос типа терминала, WILL TSPEED — запрос скорости терминала, WILL XDISPLOC — что-то вроде запроса местоположения и, наконец, WILL NEW_ENVIRON — запрос переменных среды.

В большинстве случаев достаточно указать в ответ тип терминала:


#
#   посылка на сервер команды IAC
#   -----------------------------
def SendIAC(cmd, opt):
    s = ('%c%c%c' % (chr(IAC), chr(cmd), chr(opt)))
    MDM.send('%s' % s, 5)
#
#   обработка запросов сервера IAC
#   ------------------------------
def processIAC (s):
    global echoTln
    global sgaFlg
    global TelqualSend

    if (s[1] == chr(IAC)):
        return IAC
    else:
        cmd = ord(s[1])
        opt = ord(s[2])
        SER.sendbyte(ord('.'))
#       обработка TELOPT_ECHO
        if (opt == TELOPT_ECHO):
            if (cmd == WILL):
                if (echoTln == 1):
                    echoTln = 0
                    SendIAC(DO, TELOPT_ECHO)
            else:
                if (cmd == WONT):
                    if(echoTln == 0):
                        echoTln = 1
                        SendIAC(DONT, TELOPT_ECHO)
                else:
                    if (cmd == DO):
                        echoTln = 0
                        so = ('%c%c%c%c%c%c' % (chr(IAC),chr(WONT),chr(TELOPT_ECHO),chr(IAC),chr(DO),chr(TELOPT_ECHO)))
                        MDM.send('%s' % so, 5)
                    else:
                        if (cmd == DONT):
                            echoTln = 0
                            SendIAC(WONT, TELOPT_ECHO)
#       обработка TELOPT_SGA
        else:
            if (opt == TELOPT_SGA):
                if (cmd == WONT):
                    sgaFlg = 1
                    if (echoTln == 0):
                        echoTln = 1
                        SendIAC(DONT, TELOPT_SGA)
                else:
                    if (cmd == WILL):
                        sgaFlg = 0
                        if (echoTln != 0):
                            so = ('%c%c%c%c%c%c' % (chr(IAC),chr(DO),chr(TELOPT_SGA),chr(IAC),chr(DO),chr(TELOPT_ECHO)))
                            MDM.send('%s' % so, 5)
#           обработка TELOPT_TTYPE
            else:
                if (opt == TELOPT_TTYPE):
                    if (cmd == DO):
                        SendIAC(WILL, TELOPT_TTYPE)
                    else:
                        if (cmd == SB):
                            n = 0
                            flag = 0
                            while (n < 41):
                                y = s[n+3]
                                if (y == chr(IAC)):
                                    flag = 1
                                    n = n + 1
                                else:
                                    if (flag != 0 and y == chr(SE)):
                                        break
                                    else:
                                        flag = 0
                                        n = n + 1
                            if (flag == 0):
                                return n + 3
                            if (s[3] == chr(TELQUAL_SEND)):
                                so = ('%c%c%c%c%s%c%c' % (chr(IAC),chr(SB),chr(TELOPT_TTYPE),chr(TELQUAL_IS),TermType,chr(IAC),chr(SE)))
                                MDM.send('%s' % so, 5)
                                return n+3
#               обработка других опций
                else:
                    if (cmd == WILL):
                        SendIAC(DONT, opt)
                    else:
                        if (cmd == DO):
                            so = ('%c%c%c%c%c%c' % (chr(IAC),chr(WONT),chr(opt),chr(IAC),chr(DONT),chr(opt)))
                            MDM.send('%s' % so, 5)
                        else:
                            if (cmd == DONT):
                                SendIAC(WONT, opt)
    return 3

Это, конечно, далеко не полный обработчик запросов IAC, но для начала работы его вполне достаточно.

Осталось добавить конфигурационный файл (назвав его, например, settings.ini) в котором можно хранить настройки программы, например, такого вида:


AUTH_TYPE::1::
APN::FixedIP.nw::
GPRS_USER::user::
GPRS_PASSWD::password::
DEST_IP::200.200.100.100::
SECONDARY_ID::1234::
COM_SPEED::57600::

Можно обратить внимание на строчку SECONDARY_ID. Дело в том, что в некоторых случаях сервер может отправлять клиенту запрос для получения дополнительных параметров соединения. Код запроса — 0x05. При ответе на этот запрос клиент может отправить, кроме всего прочего, и собственный идентификатор. Это бывает полезно, если необходимо, например, идентифицировать клиента не только по IP или логину.

Для работы с файлом конфигурации создадим дополнительный класс Config, записав его в отдельный файл config.py.


class Config:
    def __init__(self):
        self.config = {}

    def get(self, k):
        return self.config[k]

    def set(self, k, v):
        self.config[k] = v

    def read(self):
        try:
            fh = open("settings.ini", "r")
            try:
                lines = fh.readlines()
                for l in lines:
                    kv = l.strip().split('::')
                    self.config[kv[0]] = kv[1]
            finally:
                fh.close()
        except IOError:
            print "Configuration file not found."

    def write(self):
        try:
            fh = open("settings.ini", "w")
            try:
                lines = []
                for k in self.config.keys():
                    lines.append(k + "::" + self.config[k] + "::\r\n")
                fh.writelines(lines)
            finally:
                fh.close()
        except IOError:
            print "Configuration file not found."

    def dump(self):
        for k in self.config.keys():
            print k + "::" + self.config[k]

И, наконец, полный текст программы:


import MOD
import MDM
import SER
import MDM2

import config
CONFIG = config.Config()
CONFIG.read()

version = '04.151'

IAC = 255

DONT = 254
DO = 253
WONT = 252
WILL = 251
SB = 250
AYT = 246
SE = 240

TELOPT_ECHO = 1
TELOPT_SGA = 3
TELOPT_STATUS = 5
TELOPT_SUPDUP = 21
TELOPT_TTYPE = 24
TELOPT_NAWS = 31
TELOPT_TSPEED = 32
TELOPT_LFLOW = 33
TELOPT_XDISPLOC = 35
TELOPT_NEW_ENVIRON = 39

TELQUAL_IS = 0
TELQUAL_SEND = 1

echoTln = 1
sgaFlg = 1
exitScript = 0
restartModem = 0
secondaryID = 0
isConnect = 0
TelqualSend = 0
TermType = 'VT220'
TermSpeed = '38400,38400'

autoLogin = 1
isLogin = 0
isPassword = 0
isEscape = 0

Interval = 0L
numInterval = 0
DELAY_INT = 850
IP_address = ''

#
#   проверка наличия сети
#   ---------------------
def checkNetwork():
    SER.send('Try to Find Net ...')
    MOD.sleep(20)
    REC_TIME = 200
    for _ in range(10):
        MDM.send("AT+CREG?\r",0)
        res = MDM.receive(REC_TIME)
        if (res.find('0,1')!=-1):
            SER.send(' OK\n\r')
            return 1
        else:
            MOD.sleep(50)
    SER.send(' Net Not Found\n\r')
    return 0
#
#   активация контекста (вхождение в сеть)
#   --------------------------------------
def activateContext():
    REC_TIME = 200
    s = ''
    SER.send('GPRS CONTEXT setup ... ')
    MDM.send('AT+CGDCONT=1,"IP","' + CONFIG.get('APN') + '"\r', 0)
    while(1):
        s = s + MDM.receive(10)
        if(s.find('OK')!=-1):
            SER.send(' OK\n\r')
            break
        if(s.find('ERROR')!=-1):
            SER.send(' ERROR\n\r')
            break
    SER.send('GPRS CONTEXT activated ...')
    MDM.send('AT#SGACT=1,1\r', 10)
    MOD.sleep(20)
    s = ''
    while(1):
        s = s + MDM.receive(10)
        if(s.find('OK')!=-1):
            result = 0
            break
        if(s.find('ERROR')!=-1):
            result = -1
            break
    if (result == 0):
        str_IP = s.split(chr(13))
        i_str_IP = str_IP[1].find(': ')
        My_IP = str_IP[1][i_str_IP + 2:]
        SER.send(' OK\n\r')
        return My_IP
    else:
        SER.send('ERROR.\r\n')
        return 0
#
#   посылка на сервер команды IAC
#   -----------------------------
def SendIAC(cmd, opt):
    s = ('%c%c%c' % (chr(IAC), chr(cmd), chr(opt)))
    MDM.send('%s' % s, 5)
#
#   обработка запросов сервера IAC
#   ------------------------------
def processIAC (s):
    global echoTln
    global sgaFlg
    global TelqualSend

    if (s[1] == chr(IAC)):
        return IAC
    else:
        cmd = ord(s[1])
        opt = ord(s[2])
        SER.sendbyte(ord('.'))
#       обработка TELOPT_ECHO
        if (opt == TELOPT_ECHO):
            if (cmd == WILL):
                if (echoTln == 1):
                    echoTln = 0
                    SendIAC(DO, TELOPT_ECHO)
            else:
                if (cmd == WONT):
                    if(echoTln == 0):
                        echoTln = 1
                        SendIAC(DONT, TELOPT_ECHO)
                else:
                    if (cmd == DO):
                        echoTln = 0
                        so = ('%c%c%c%c%c%c' % (chr(IAC),chr(WONT),chr(TELOPT_ECHO),chr(IAC),chr(DO),chr(TELOPT_ECHO)))
                        MDM.send('%s' % so, 5)
                    else:
                        if (cmd == DONT):
                            echoTln = 0
                            SendIAC(WONT, TELOPT_ECHO)
#       обработка TELOPT_SGA
        else:
            if (opt == TELOPT_SGA):
                if (cmd == WONT):
                    sgaFlg = 1
                    if (echoTln == 0):
                        echoTln = 1
                        SendIAC(DONT, TELOPT_SGA)
                else:
                    if (cmd == WILL):
                        sgaFlg = 0
                        if (echoTln != 0):
                            so = ('%c%c%c%c%c%c' % (chr(IAC),chr(DO),chr(TELOPT_SGA),chr(IAC),chr(DO),chr(TELOPT_ECHO)))
                            MDM.send('%s' % so, 5)
#           обработка TELOPT_TTYPE
            else:
                if (opt == TELOPT_TTYPE):
                    if (cmd == DO):
                        SendIAC(WILL, TELOPT_TTYPE)
                    else:
                        if (cmd == SB):
                            n = 0
                            flag = 0
                            while (n < 41):
                                y = s[n+3]
                                if (y == chr(IAC)):
                                    flag = 1
                                    n = n + 1
                                else:
                                    if (flag != 0 and y == chr(SE)):
                                        break
                                    else:
                                        flag = 0
                                        n = n + 1
                            if (flag == 0):
                                return n + 3
                            if (s[3] == chr(TELQUAL_SEND)):
                                so = ('%c%c%c%c%s%c%c' % (chr(IAC),chr(SB),chr(TELOPT_TTYPE),chr(TELQUAL_IS),TermType,chr(IAC),chr(SE)))
                                MDM.send('%s' % so, 5)
                                return n+3
#               обработка других опций
                else:
                    if (cmd == WILL):
                        SendIAC(DONT, opt)
                    else:
                        if (cmd == DO):
                            so = ('%c%c%c%c%c%c' % (chr(IAC),chr(WONT),chr(opt),chr(IAC),chr(DONT),chr(opt)))
                            MDM.send('%s' % so, 5)
                        else:
                            if (cmd == DONT):
                                SendIAC(WONT, opt)
    return 3
#   соединение с сервером
#   ---------------------
def connectSocket():

    global Interval
    global numInterval


    s = ''
    REC_TIME = 200
    SER.send('\n\rTest CONTEXT ... ')
    MDM.send("AT#SGACT?\r",0)
    res = MDM.receive(REC_TIME)
    if (res.find('1,1') == -1):
        SER.send('ERROR!\n\rActivate CONTEXT ... ')
        MDM.send('AT#SGACT=1,1\r', 10)
        delay = MOD.secCounter() + 20
        while(1):
            if(delay < MOD.secCounter()):
                SER.send('Not activated')
                break
            s = s + MDM.receive(10)
            if(s.find('OK')!=-1):
                break
    else:
        SER.send(' OK')
    MDM.send("AT#SGACT?\r",0)
    res = MDM.receive(REC_TIME)
    if (res.find('1,1') != -1):
        SER.send('\n\r')
    s = ''
    SER.send('\n\rGPRS Connect ...')
    MDM.send('AT#SD=1,0,23,' + CONFIG.get('DEST_IP') + '\r', 10)
    MOD.sleep(5)
    while(1):
        s = s + MDM.receive(5)
        if(s.find('CONNECT')!=-1):
            result = 0
            break
        if(s.find('ERROR')!=-1):
            result = -1
            break
    if (result == 0):
        SER.send(' OK\r\n')
        SER.send('Try to Connect. Wait')
        SendIAC(WILL, TELOPT_TTYPE)
        SendIAC(DO, TELOPT_SGA)
        SendIAC(WONT, TELOPT_ECHO)
        SendIAC(DO, TELOPT_ECHO)
        Interval = MOD.secCounter() + DELAY_INT
        numInterval = 0
        return 1
    else:
        SER.send('ERROR. Connect failed\r\n')
        return 0
#
#   автологин на сервер
#   -------------------
#
def AutoLogin (s):
    global autoLogin
    global isLogin
    global isPassword

    if (len(s) > 5 and autoLogin == 1):
#   проверка на логин
        if (isLogin == 0):
            if(s.find('login:') != -1):
                MDM.send('' + CONFIG.get('GPRS_USER') + '\r', 5)
                isLogin = 1
                return 6
        else:
            if (isPassword == 0):
                if(s.find('Password:') != -1):
                    MDM.send('password\r', 5)
                    isPassword = 1
                    return 9
    return 0

#
#   чтение конфигурации
#   -------------------
#
def ReadConfig():
    return 0
#
#   соединение с сервером
#   ---------------------
#
def Connect():
    global isConnect
    global exitScript
    global restartModem

    if (exitScript == 1):
        isConnect = 0
        return
    if (isConnect != 1):
        if (restartModem == 0):
            SER.send('\r                          \r')
            SER.send('Connect to server (Y/N)? ')
        while (1):
            c = SER.readbyte()
# клавиша 'y'
            if (c != -1):
                SER.sendbyte(c)
                if (c == ord('y') or c == ord('Y')):
                    if (connectSocket() == 1):
                        isConnect = 1
                        break
                    else:
                        break
# клавиша 'q'
                else:
                    if (c == ord('!')):
                        exitScript = 1
                        break
# клавиша 's'
                    else:
                        if (c == ord('\\')):
                            SER.send('\r\nSet Login [' + CONFIG.get('GPRS_USER') + '] :')
                            s = ''
                            while (1):
                                c = SER.readbyte()
                                if (c != -1):
                                    SER.sendbyte(c)
                                    if (c == ord('\r')):
                                        break
                                    s = s + chr(c)
                            if (len(s) > 0):
                                CONFIG.set('GPRS_USER', s)
                            SER.send('\r\nSet ID [' + CONFIG.get('SECONDARY_ID') + '] :')
                            s = ''
                            while (1):
                                c = SER.readbyte()
                                if (c != -1):
                                    SER.sendbyte(c)
                                    if (c == ord('\r')):
                                        break
                                    s = s + chr(c)
                            if (len(s) > 0):
                                CONFIG.set('SECONDARY_ID', s.upper())
                            else:
                                SER.send('\r\n')
                            SER.send('\r\nWrite Config ...')
                            CONFIG.write()
                            break
# клавиша 'r'
                        else:
                            if (c == ord('$')):
                                SER.send(' \r\nReset modem ...             \r\n')
                                restartModem = 1
                                MOD.watchdogEnable(2)
                                break
# клавиша 'a'
                            else:
                                if (c == ord('|')):
                                    SER.send('\r\nIP: ' + IP_address + '\r\n')
                                    break
                                else:
                                    break
#
#   читаем COM-порт
#   ---------------
#
def ReadCom():
    global isEscape
    global isConnect
    global echoTln
    global sgaFlg
    global TelqualSend
    global isLogin
    global isPassword
    global Interval
    global numInterval

    delay = 0L

    s = SER.read()
    if(len(s) > 0):
        if (s.find('\x07') != -1):
            MOD.watchdogEnable(2)
            SER.send(' \r\nReset modem ...             \r\n')
        Interval = MOD.secCounter() + DELAY_INT
        numInterval = 0
        MDM.send(s, 2)
        if (s.find('%') != -1):
            isEscape = isEscape + 1
            if (isEscape == 4):
                SER.send('\x1b[H\x1b[JEscape found.                \r\n')
                MOD.sleep(50)
                while(1):
                    MOD.sleep(50)
                    MDM.send('+', 1)
                    MDM.send('+', 1)
                    MDM.send('+', 1)
                    MOD.sleep(50)
                    delay = MOD.secCounter() + 20
                    while(1):
                      if(delay < MOD.secCounter()):
                        SER.send('Escape timeout.  Reset module...        \r\n')
                        MOD.watchdogEnable(2)
                        break
                      s = s + MDM.receive(5)
                      if(s.find('OK')!=-1):
                        break
                    if(s.find('OK')!=-1):
                        break
                SER.send('Socket shutdown.              \r\n')
                MDM.send('AT#SH=1\r', 10)
                MOD.sleep(20)
                delay = MOD.secCounter() + 20
                while(1):
                  if(delay < MOD.secCounter()):
                    SER.send('Shutdown timeout.              \r\n')
                    break
                  s = s + MDM.receive(5)
                  if(s.find('OK')!=-1):
                    break
                SER.send('Wait disconnect ... ')
                while(1):
                  MDM.send('AT#SS\r', 10)
                  MOD.sleep(40)
                  s = s + MDM.receive(10)
                  if(s.find('1,0')!=-1):
                    break
                SER.send('OK\r\n')
                SER.send('Connection closed.            \r\n')
                isConnect = 0
                echoTln = 1
                sgaFlg = 1
                TelqualSend = 0
                isLogin = 0
                isPassword = 0
                isEscape = 0
        else:
            isEscape = 0
    else:
        if(Interval < MOD.secCounter() and numInterval == 0):
            MDM.send('%c' % chr(0x7), 5)
            SER.send('%c' % chr(0x7))
            numInterval = 1
#
#   проверка соединения
#   -------------------
#
def TestConnection(s):
    global isConnect
    global echoTln
    global sgaFlg
    global TelqualSend
    global isLogin
    global isPassword
    global Interval
    global numInterval

    if(s.find('NO CARRIER') != -1):
        SER.send('\r\n\r\n\r\n\r\nConnection closed\r\n')
        isConnect = 0
        echoTln = 1
        sgaFlg = 1
        TelqualSend = 0
        isLogin = 0
        isPassword = 0
        return 0
    else:
        Interval = MOD.secCounter() + DELAY_INT
        numInterval = 0
        return 1
#
# ==================
# основная программа
# ==================
#

SER.set_speed(CONFIG.get('COM_SPEED'),'8N1')
SER.send('\r\nStart Telnet Script. Ver. ' + version + ' (2019)\r\n')
ReadConfig()
if (checkNetwork() == 0):
    exitScript = 1
IP_address = activateContext()
while (1):
#   проверка наличия соединения с сервером
    Connect()
#   если есть соединение обрабатываем данные
    if (isConnect == 1):
        ReadCom()
#       читаем telnet
        rcv = MDM.read()
        j = len(rcv)
        if (j > 0):
            if (TestConnection(rcv) == 1):
                AutoLogin(rcv)
                if (rcv.find('\xff') != -1):
                    res = ''
                    i = 0
                    while (i < j):
                        sym = ord(rcv[i])
                        if (sym == IAC):
                            k = processIAC(rcv[i:j])
                            if (k == IAC):
                                SER.sendbyte(sym)
                                i = i + 1
                            else:
                                if (k == 0):
                                    SER.sendbyte(sym)
                                    i = i + 1
                                else:
                                    i = i + k
                        else:
                            SER.sendbyte(sym)
                            i = i + 1
                else:
                    SER.send(rcv)
                    if (rcv.find('\x05') != -1):
                        MDM.send('LXE/q/%d/APLUS/%s\r' % (24, CONFIG.get('SECONDARY_ID')), 10)
#   проверка на завершение работы скрипта
    else:
        if (exitScript == 1):
            break
# =========================
# завершение работы скрипта
# =========================
SER.send('\r\nStop Script\r\n')
MDM.send('AT#SGACT=1,0\r', 10)

Следует сразу отметить, что загружать текстовой вариант программы (с расширением .py) в модем практически бессмысленно. Время запуска текстового скрипта составляет несколько десятков минут (мне терпения дождаться так и нехватило). Поэтому следующий шаг — компиляция скрипта и получение файла с расширением .pyo.