среда, 26 февраля 2014 г.

Шлем длинные SMS кириллицей. Описание API вызова библиотеки SMSPDU.DLL

Данная библиотека (далее DLL), для работы с GSM/3G-терминалами через физический или виртуальный СOM-порт over USB в ОС NT/2000/2003/XP/7/8, предназначена для осуществления отправки коротких и длинных SMS сообщений в формате PDU (для поддержки кириллицы , с возможностью транслитерации сообщения) посредством AT-команд, а также выполнения основных и произвольных команд по работе с модемом. DLL предоставляет универсальный доступ для других приложений вне зависимости от языка в среде Win32/64.


 Cистемные требования и зависимости:
  1. Win32/64 (NT/2003/XP/7/8).
  2. Наличие GSM/3G-терминала (модема) в режиме AT-команд.
Возможности
  1. Отправка SMS в формате PDU произвольной длины.
  2. Отправка произвольных AT-команд.
  3. Получение кода IMEI/IMSI.
  4. Получение информации о модеме (тип модема, модель, версия прошивки).
  5. Получение уровня сигнала базовой станции.
  6. Автоопределение присутствия в тексте отправляемой SMS символов кириллицы и задание порога на длину SMS в 70 символов для кириллицы и 140 для латиницы.
  7. Поддержка отправки длинных SMS (библиотека определяет количество SMS по автоматическому порогу п.6 и производит авторазбивку исходного SMS на короткие с идентификаторами длинной SMS для последующей склейки на стороне мобильного терминала).
  8. Поддержка транслитерации текста сообщения SMS.
Для использования DLL в своих проектах соблюдайте соглашение об stdcall-вызовах. Тип соглашения о вызове объявляется после прототипа функции, будь то объявление функционального типа или же объявление функции.

Таблица расшифровок параметров экспортируемой функции 'PDU()' библиотеки 'SMSPDU.DLL':

Таблица расшифровок параметров экспортируемой функции 'AT()' отправки произвольных AT-команд:

Таблица расшифровок параметров экспортируемой функции 'IMEI()' получения кода IMEI карточки:

Таблица расшифровок параметров экспортируемой функции 'IMSI()' получения кода IMSI карточки:

Таблица расшифровок параметров экспортируемой функции 'INFOMODEM()' получения информации о модеме (тип модема-модель-версию прошивки):

Таблица расшифровок параметров экспортируемой функции 'LEVELSIGNAL()' получения уровня сигнала базовой станции:

Пример динамического подключения (Delphi 6/7/2006/2009/2010/TDL/XE5-7):
var  pdu:function(com,
                  ksz,
                  sz,
                  num,
                  msg: pansichar;
                  ekran, translit: dword): pansichar; stdcall;
     at:function(com,msg: pansichar): pansichar; stdcall;    
     imei:function(com: pansichar): pansichar; stdcall;  
     imsi:function(com: pansichar): pansichar; stdcall;  
     infomodem:function(com: pansichar): pansichar; stdcall;  
     levelsignal:function(com: pansichar): pansichar; stdcall;  
                  
     LibHandle: THandle;

function LinkProc(ProcName: string):Pointer;
begin
 result:= GetProcAddress(LibHandle, PChar(ProcName))
end;

{ инициализация }
begin
 LibHandle:= LoadLibrary('smspdu.dll');
 if LibHandle <> 0 then begin
  pdu        := LinkProc('pdu');
  at         := LinkProc('at');
  imei       := LinkProc('imei');
  imsi       := LinkProc('imsi');
  infomodem  := LinkProc('infomodem');
  levelsignal:= LinkProc('levelsignal');
 end
...

{ пример отправки SMS }
begin
 
if LibHandle <> 0 then pdu('COM1', '+38063', '9010000', '+380505930593', 'Тест SMS', 0, 0);
...

{ деинициализация }
begin
 if LibHandle <> 0 then
  FreeLibrary(LibHandle);
...
Пример отправки SMS из скрипта VBS:
' регистрируем COM объект DynamicWrapperX в тихом режиме
Dim WshShell
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run ("regsvr32.exe dynwrapx.dll /s"),3, true
' создаем объект DynamicWrapperX.2
Set Wrap = CreateObject("DynamicWrapperX.2")
Wrap.Register "smspdu.dll", "pdu", "i=sssssll", "f=s", "r=s"
MSGBOX Wrap.pdu("COM1", "+38063", "9010000", "+380505930593", "Тест SMS", 0, 0)
ПОРЯДОК ИСПОЛЬЗОВАНИЯ
  1. При задании параметра 'ekran' <> 0, SMS будет закодировано для вывода сразу на экран телефона (бегущей строкой, как правило).
  2. При задании параметра 'translit'=1, будет активирован режим транслитерации для кириллицы в сообщении.
  3. Вам нет необходимости думать над длиной текста SMS, библиотека сама определит наличие кириллицы и произведет авторазбивку SMS.
  4. Для вызова библиотеки 'smspdu.dll' без программирования по 'горячей' клавише(ш) вынесите ярлык на рабочий стол для скрипта 'runsmspdu.vbs' и назначьте требуемые 'горячие' клавиши.
  5. На Windows Vista/7/8/8.1 (32/64 bit) скрипт регистрации OLE сервера 'regdynwrapx.bat' запускать правой кнопкой мыши от имени Администратора.
  6. Для работы с 3G модемами их следует предварительно перевести в режим AT-команд (см. документацию на модем).

    22 комментария:

    1. Выпущено обновление библиотеки 'SMSPDU.DLL' для поддержки отправки длинных SMS в PDU формате с авторазбивкой SMS на части по длине сообщения.

      Теперь пользователю не нужно думать о длине сообщения при наличии или отсутствии кириллицы. Библиотека сама определит ее наличие в тексте и задаст ограничение по длине для одиночного SMS на 70 символов для кириллицы и 140 для латиницы. При превышении данных ограничений библиотека активирует режим отправки длинных SMS и сама разобьет исходное сообщение на куски с идентификаторами для последующей склейки телефоном.

      ОтветитьУдалить
      Ответы
      1. Приветствую. Не найдется ли у вас нескольких минут для обсуждения вашей библиотеки? Дело в том, что я сам занимаюсь разработкой подобного проекта и столкнулся с проблемой нехватки информации в данной области. Крайне мало людей озадачиваются темой взаимодействия с GSM модемами, в основном все пользуются готовыми решениями, которые отнюдь не блещут надежностью. Поэтому мне было бы очень интересно узнать как вы реализовали этот механизм.

        Удалить
      2. Механизм основан на использовании стандартных AT-команд из гайда AT_Commands_User_Guide_Wavecom и возможности отсылки произвольных. Про PDU формат есть публикация в ж.Радиолюбитель.

        Удалить
      3. Это понятно. Вопрос идет глубже, к отсылке команд и получения ответа модема. Каким образом вы получаете ответ модема (простой ReadFile после отправки команды, или же что-то посложнее, например, WaitCommEvent)? Как парсите ответ (ведь всегда можно получить URC прямо в теле ответа)?

        Удалить
      4. Тонкости работы алгоритмов, которые я хочу раскрыть либо идут публикацией, либо в виде OpenSource на SF. В остальном мне нет резона их раскрывать, библиотеки предоставляются бесплатно без ограничений.

        Удалить
      5. Я ни в коем случае не хочу составлять вам конкуренцию, мой проект не публичный. Я бы с радостью воспользовался вашей библиотекой, но мне нужен несколько другой функционал. Если вы не хотите говорить о вашей реализации, возможно, вы согласитесь подтолкнуть в нужном направлении? Я ратую за надежность и мне важно важно, чтобы на работу программы не могли повлиять ни URC, ни низкая скорость передачи, ни физическая перезагрузка модема. Я перепробовал несколько походов и пока остановился на ожидании данных через WaitCommEvent, разбору данных на токены (разбивка по \r\n) и обработку этих токенов через коллбэки. Но с этим способом нельзя синхронно получить данные (к примеру, о качестве сигнала). Поэтому я ищу сейчас другие подходы.

        Удалить
      6. Работа с портом в любом случае должна быть асинхронной. Запись данных в порт не зависит от потока чтения. Если буфер не пустой, то идет вычитка. Поступающие запросы с верхнего уровня (будь-то пользовательские или служебные) должны накапливаться в динамическом буфере команд. При отправке запроса n из динамического списка - уменьшаем список на 1, активируем флаг ожидания и отсчитываем таймаут, если по истечении таймаута ответ (любой) не пришел - производим отсылку следующей команды запроса n-1. Поскольку присутствует парсер данных, учитывающий склейку строк, то не имеет значения когда какой ответ придет, так как ответ на запрос о том же уровне сигнала CSQ содержит соответствующие идентификаторы. И даже если первым запросом ушел запрос содержимого ячейки или поступившей SMS, таймаут прошел, ответа нет и была второй послана служебная команда об уровне сигнала и пришел ответ с уровнем, а потом данные, то парсер сие обработает.

        Все это еще до библиотеки было реализовано в ActiveX OPC-SMS.OCX шлюзе и опубликовано в ж.Радиолюбитель циклом материалов.

        Удалить
      7. Да, примерно так у меня все и работает. Но возникает проблема с возвратом данных обратно, после того как парсер их разберет.
        Насколько я понял, вы ведете речь о использовании отдельного потока для чтения и парсинга данных от модема (я так делал изначально). На этом примере приведу показательную ситуацию, чтобы было понятно о чем я говорю:
        Если мы запрашиваем данные о качестве сигнала, мы можем просто усыпить основной поток и подождать пока поток чтения получит данные и разбудит основной поток. Вроде все хорошо.
        Но нам нужно еще и обрабатывать URC, например, о новой SMS.
        Для этого мы добавляем callback, который будет вызван потоком чтения, когда придет соответствующий URC.
        И вот если в этом callback'е нам снова понадобится выполнить команду и получить данные (например, прочесть ту же SMS), то мы получим дедлок.
        Вокруг этой проблемы я, в общем то, и бьюсь.

        Удалить
      8. Нет, поток - событие приема данных одно и для парсинга данных. Никаких замораживаний потоков. Для запроса - отдельный поток с управлением глобальными флагами, доступными второму первому потоку. Никаких коллизий нет в принципе.

        Удалить
      9. То есть, после выполнения команды начинается прокрутка цикла, который читает данные и парсит их, верно? И в этом же цикле вызываются callback'и для URC? Этот как раз то решение к которому я пришел в итоге. Но и оно имеет проблемы.
        Если внутри callback тоже нужно выполнить команду и получить данные, то возникает рекурсия. Один callback вызывается из другого. Вложенность легко может дойти до 3-4 уровней.
        При определенных обстоятельствах можно нарваться на взаимный циклический вызов (а если не вызывать callback до того, пока его предыдущий вызов не будет завершен, то можно потерять данные).
        Кроме того, если один callback из подобной цепочки бросит исключение то потеряется прогресс выполнения всей цепочки.

        Удалить
      10. В потоке чтения данных и их парсинга нет никаких вызовов, вы излишне усложняете.

        Удалить
      11. Хм. Тогда я просто не понимаю как можно обработать URC не совершая обратных вызов.

        Удалить
      12. Обратные вызовы не нужны. Если нужно отслеживать появление новых SMS или статус доставки, то достаточно настроить уведомления через AT+CNMI, либо читать в потоке запросов принудительно ячейки памяти или последние принятые. Также следует учесть ситуацию, когда память модема может быть переполнена и очищать периодически, ибо при заполненной памяти - новые SMS не будут приняты.

        Удалить
      13. Так ведь я о обработке этих самых URC и говорю. На вашем примере команда AT+CNMI управляет URC +CMTI. И вот когда при разборе входных данных от модема встретится этот URC, то как раз таки и потребуется обратный вызов для его обработки.

        Удалить
      14. Вероятно, я запутал вас своей терминологией. Я говорю о обратных вызовах, так как считаю обработку URC задачей пользователя библиотеки, а не самой библиотеки. То есть, веду речь о разделении кода для обработки URC от кода который парсит ответы модема.

        Удалить
      15. Мне вообще непонятно как можно пользоваться своей терминологией при обсуждении работы со стандартом. Для меня URC - есть Unsolicited Result Code. Чаще всего под обратными вызовами понимают именно - URCS, отключенный по-умолчанию режим модема, выдающий системные логи-отчеты о выполнении тех или иных операций. Не то, что пришел отчет на пользовательскую команду (URC, это может быть и принятая SMS в ответ на команду чтения SMS), а то что сам модем выдает.

        Удалить
      16. "То есть, веду речь о разделении кода для обработки URC от кода который парсит ответы модема."
        - да кто ж мешает. Если нужно выдавать наверх из библиотеки сообщения, которые не вызвал сам пользователь, то используется обратный вызов в процедуре библиотеки, функции из приложения верхнего уровня, прописанной в этом приложении. Никакие зависания это вызвать не может, ибо прежде чем вызывать - должна быть проверка на существование объекта на не nil. Парсер же для всех сообщений модема - един и никаких гвоздей.

        Делается это так http://raxp2.blogspot.com/2015/01/blog-post_9.html. Напомню отличие от вызова функции из библиотеки в приложении верхнего уровня, тут все наоборот - сама библиотека вызывает функцию в приложении. Это и называется каллбэком.

        Удалить
      17. Эх, похоже объясняльщик из меня не важный, даже не могу донести о чем хочу спросить. :) Ладно, благодарю за беседу и уделенное мне время. Пойду обдумывать дальше.

        Удалить
      18. Обычно на форумах даю совет: взять бумажку, карандаш и разрисовать алгоритм на бумажке пошагово. Тогда многое проясняется, ибо восприятие человеческое больше визуальное, чем в словах.

        Удалить
    2. "ни физическая перезагрузка модема"
      - физическое отсутствие, переподключение модема можно отследить через SetupAPI.

      При перезагрузке модема, пока он инициализируется, модем не будет отвечать на запросы "AT?", а значит эту ситуацию тоже можно учитывать.

      ОтветитьУдалить
      Ответы
      1. Разве обязательно использовать SetupAPI? Не достаточно будет просто отследить CTS?

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

        Удалить
      2. 1. Недостаточно, даже не минимум миниморум. Модемы все разные, начиная от железных с аппаратным RS-232, так и USB и даже по RS-485 с виртуальным COM-портом, драйвер которого вовсе не обязан использовать служебные сигналы.

        2. SetupAPI как и WMI дает возможность отследить виртуальный COM-порт, который при отсутствии устройства исчезает из ОС. WMI имеет тот недостаток, что на пользовательских машинах с ОС сборками, служба может быть отключена. Также она может быть отключена самим пользователем, который об этом "помнил, да забыл".

        3. Много с чем. Начиная от кривых драйверов, заканчивая отсутствием гальванической развязки, если модем со своим питанием.

        Удалить

    В комментариях уважайте собеседника, внимательно читайте посты и не додумывайте. Просьбы и предложения из разряда: «можно ваш Skype/Viber/телефон», «напишите мне в vk/FB», а также другие им подобные — игнорируются. Выход новых версий ПО, внешняя ссылка, переставшая работать с течением времени и т.п. не является основанием для претензий. Желающие спокойно подискутировать и высказаться — Welcome. Желающие спонсировать блог — Donate. Нарушение этих простых правил ведет к бану и удалению комментариев без предупреждения.