perl скрипт производящий голосовой обзвон (оповещение) через usb модем huawei e1550

    В свое время, так как я много времени проводил в командировках, мной была приобретена замечательная игрушка — usb модем Huawei e1550. Но времена лихой молодости прошли, и необходимость в использовании данного девайса по прямому назначению отпала. Так он и пылился у меня на полке в течении нескольких лет. И пылился бы и дальше, но возникла задача сделать систему оповещения. Тут я и вспомнил про модем. Рассмотрев поставленную задачу — был вынужден отказаться от SMS оповещения в пользу голосового дозвона по причине невозможности получить уведомление о прочтении SMS. Решения на базе Asterisk показались мне несколько громоздкими, и почитав доку по модему я решил написать звонилку самостоятельно.

    Причина публикации.

    Несмотря на обилие статей по работе с USSD и SMS запросами, я не нашел ни одной реализации голосовых вызовов на скриптовых языках (таких как Perl, PHP, Node.js). Надеюсь данная статья будет для Вас хорошим подспорьем.

    Среда разработки

    операционная система: Linux
    Дистрибутив: openSUSE 12.3
    Ядро: 3.7.10-1.16-desktop #1 SMP PREEMPT Fri May 31 20:21:23 UTC 2013 (97c14ba) i686 i686 i386 GNU/Linux
    Язык программирования: Perl
    usb модем: Huawei e1550

    Немного теории.

    В большинстве дистрибутивов Linux, при подключении данного модема в /dev создаются 3 usb интерфейса. обычно это:
    /dev/ttyUSB0 — командный интерфейс модема
    /dev/ttyUSB1 — голосовой(при включенном голосовом режиме) интерфейс модема
    /dev/ttyUSB2 — командный интерфейс модема. Отличается от /dev/ttyUSB0 тем что с него можно читать не только ответы модема на команды, а также служебные сообщения. Такие как данные о качестве сигнала, вывод ^CEND и прочее.

    Для начала работы с модемом достаточно открыть как файл один из командных интерфейсов на чтение и запись.
    Чтобы отправить модему команду — нужно записать ее в открытый файл интерфейса.
    Чтобы получить ответ модема на данную команду — нужно прочесть его из открытого файла интерфейса.

    Команды которые можно подавать модему — это AT команды
    Команды для модема Huawei e1550 и ответы которые на них можно получить описаны в его спецификации:
    HUAWEI CDMA Datacard Modem AT Command Interface Specification
    HUAWEI UMTS Datacard Modem AT Command Interface Specification

    Для того чтобы активировать голосовые функции модема нужно подать команду AT^CVOICE=0
    Голосовые функции будут активированы до тех пор пока не будут отключены командой AT^CVOICE=1

    Для того чтобы начать прием/передачу в модем аудио информации нужно при каждом звонке переключать режим работы аудио порта модема командой AT^DDSETEX=2

    Аудиоданные для передачи модему должны иметь формат:
    частота оцифровки: 8000 Герц.
    количество каналов: 1 (mono).
    бит на оцифровку: 16 signed.

    Аудиоданные должны подаваться в аудио порт модема порциями по 320 байт каждые 0.02 секунды.

    По завершении вызова модем через 2-й командный интерфейс выдает информацию о вызове в виде сообщения CEND
    формат вывода ^CEND:call_index, duration, end_status, cc_cause
    где:
    call_index — уникальный идентификатор вызова
    duration — длительность вызова в секундах
    end_status — код статуса устройства после завершения вызова
    cc_cause — код причины завершения вызова

    Итак. Начнем.

    звонилка будет состоять из 3-х файлов:
    huawey_voice_call.pl — непосредственно сам скрипт голосового дозвона.
    list.01.pl — файл с данными абонентов.
    test.voice.raw — файл с голосовым сообщением записанным в нужном формате.

    также в конце статьи будут представлены 2 дополнительных файла:
    cc_cause.pl — содержит коды причин завершения вызова (cc_cause)
    end_status.pl — содержит коды статуса устройства после завершения вызова (end_status)

    все файлы одним архивом (выложил на своем компе, изредка комп бывает выключен)

    рассмотрим huawey_voice_call.pl
    1. #!/usr/bin/perl
    2.  
    3. # подключаем модуль Time::HiRes и импортируем
    4. # в текущее пространство имен функцию sleep
    5. # особенность данной функции - возможность указывать
    6. # задержку меньше секунды
    7. use Time::HiRes qw(sleep);
    8.  
    9.  
    10.  
    11. # подключаем файл cc_cause.pl содержащий коды Disconnect cause codes
    12. # Данные коды отображают причину завершения вызова
    13. # В данном скрипте не используются, но для написания
    14. # полноценной звонилки необходимо анализировать данный
    15. # параметр в выводе ^CEND:call_index, duration, end_status, cc_cause
    16. # my %cc_cause = do 'cc_cause.pl'; 
    17.  
    18. # подключаем файл end_status.pl содержащий коды Call endind cause codes
    19. # Данные коды отображают статус устройства после завершения вызова
    20. # В данном скрипте не используются, но для написания
    21. # полноценной звонилки необходимо анализировать данный
    22. # параметр в выводе ^CEND:call_index, duration, end_status, cc_cause
    23. # my %end_status = do 'end_status.pl';
    24.  
    25. # Для информации:
    26. # Сообщения типа CEND выдаются модемом при завершении вызова
    27. # и содержат в себе информацию о вызове, о причине завершения вызова
    28. # и о состоянии устройства.
    29. # формат вывода ^CEND:call_index, duration, end_status, cc_cause
    30. # где:
    31. # call_index - уникальный идентификатор вызова
    32. # duration - длительность вызова в секундах
    33. # end_status - код статуса устройства после завершения вызова
    34. # cc_cause - код причины завершения вызова
    35.  
    36. # при подключении модема к компьютеру с OS Linux
    37. # создаются 3 usb интерфейса для обмена данными с модемом
    38. # обычно это:
    39. # /dev/ttyUSB0 - командный интерфейс модема
    40. # /dev/ttyUSB1 - голосовой(при включенном голосовом режиме) интерфейс модема
    41. # /dev/ttyUSB2 - командный интерфейс модема. Отличается от /dev/ttyUSB0 тем
    42. # что с него можно читать не только ответы модема на команды, а также служебные
    43. # сообщения. Такие как данные о качестве сигнала, вывод ^CEND и прочее
    44.  
    45. # указываем порт для отсылки модему звука
    46. $VOICE_PORT = "/dev/ttyUSB1";
    47.  
    48. # указываем порт для подачи модему команд
    49. $COMMAND_PORT = "/dev/ttyUSB2";
    50.  
    51. # устанавливаем в:
    52. # 0 - чтобы отключить вывод отладочной информации
    53. # 1 - чтобы включить вывод отладочной информации
    54. $VERBOSE = 1;
    55.  
    56. # Открываем командный порт модема на чтение и запись
    57. open my $SENDPORT, '+<', $COMMAND_PORT or die "Can't open '$COMMAND_PORT': $!\n";
    58.  
    59. # Открываем голосовой  порт модема на чтение и запись
    60. # чтение аудиопотока из порта в данной программе не используется
    61. # но вам ничто не мешает превратить данный скрипт в автоответчик например
    62. open my $SENDPORT_WAV, '+<', $VOICE_PORT or die "Can't open '$VOICE_PORT': $!\n";
    63.  
    64.  
    65.  
    66. # подключаем файл list.01.pl содержащий данные об абонентах
    67. my @user_list = do 'list.01.pl'; 
    68.  
    69.  
    70. # вызываем функцию обзвона, которой передаются 2 параметра:
    71. # 1-й - имя файла с голосовым сообщением
    72. # 2-й - массив с данными об абонентах
    73. call_list("test.voice.raw", \@user_list );
    74.  
    75. # по окончании обзвона закрываем все открытые файлы/порты
    76. exit_call();
    77.  
    78.  
    79.  
    80. # данная функция производит обзвон абонентов по списку
    81. sub call_list{
    82.     # получаем имя файла с голосовым сообщением
    83.     my $l_file = shift;
    84.  
    85.     # получаем ссылку на список с данными об абонентах
    86.     my $l_list = shift;
    87.  
    88.     # загружаем данные из файла с голосовым сообщением
    89.     my $l_voice = load_voice($l_file);
    90.  
    91.     # данный цикл пробегает по списку абонентов
    92.     # и пытается произвести дозвон
    93.     foreach $l_info (@{$l_list}){
    94.  
    95. # вызываем функцию дозвона до абонента
    96. my $l_msg = call_one($l_info,$l_voice);
    97.  
    98. # Выводим полученное сообщение
    99. print $l_msg;
    100.  
    101. # прежде чем звонить следующему абоненту
    102. # ждем 3 секунды.
    103. sleep 3;
    104.     }
    105. }
    106.  
    107.  
    108. # данная функция производит попытку вызова указаного номера
    109. # и в случае успеха - транслирует голосовое сообщение
    110. sub call_one{
    111.  
    112.     my $l_info = shift;  # ХЭШ с данными текущего абонента
    113.     my $l_bufer = shift; # массив с 320 байтными кусками головового сообщения
    114.  
    115.     # данная команда включает в модеме голосовой режим
    116.     # один раз включив его можно удалить/заремарить
    117.     # эту команду. Модем запомнит состояние.
    118.     #at_send('AT^CVOICE=0'); 
    119.  
    120.     # отдаем модему команду дозвониться до номера $l_info->{phone}
    121.     # и ожидать ответа от модема:
    122.     # OK - дозвон прошел успешно
    123.     # NO CARRIER - нет соединения с сотовой сетью
    124.     my $l_rec = at_send("ATD$l_info->{phone};",qr/(OK|NO CARRIER)/);
    125.     # в случае если дозвон не произошел - выходим из функции и возвращаем соответствующее сообщение
    126.     return "Абонент $l_info->{name} [$l_info->{phone}] не оповещен. НЕТ СЕТИ\n"  if  $l_rec eq 'NO CARRIER';
    127.  
    128.     # ожидаем когда абонент поднимет трубку
    129.     # CONN:.... - абонент поднял трубку
    130.     # CEND:.... - абонент недоступен, занят или сбросил вызов
    131.     $l_rec = at_rec(qr/\^(CONN\:1\,0|CEND\:)/);
    132.     # в случае если абонент не поднял трубку - выходим из функции и возвращаем соответствующее сообщение
    133.     return "Абонент $l_info->{name} [$l_info->{phone}] не оповещен. НЕДОСТУПЕН или СБРОСИЛ\n" if  $l_rec eq 'CEND:';
    134.  
    135.     # переключаем модем в режим приема/передачи голоса
    136.     # OK - переключение прошло успешно
    137.     # ERROR - переключение не произведено
    138.     # CEND:.... - абонент недоступен, занят или сбросил вызов
    139.     $l_rec = at_send('AT^DDSETEX=2',qr/(OK|ERROR|CEND\:)/);
    140.     # в случае если не удалось переключится в голосовой режим или абонент не поднял
    141.     # трубку - выходим из функции и возвращаем соответствующее сообщение
    142.     return "Абонент $l_info->{name} [$l_info->{phone}] не оповещен. НЕДОСТУПЕН или СБРОСИЛ\n" if  $l_rec ne 'OK';
    143.  
    144.     # Если дошли до сюда - значит вызов установлен и абонент поднял трубку
    145.     # Звук модему должен передаваться порциями по 320 байт каждые 0.02 секунды
    146.  
    147.     # Устанавливаем служебную переменную $| в единицу это отключает буферизацию.
    148.     # Таким образом данные в звуковой порт будут отправляться незамедлительно.
    149.     $|=1;
    150.  
    151.     # Цикл по буферу с 320 байтными кусками голосового сообщения
    152.     foreach my $c (@{$l_bufer}) {
    153.     # Запись очередного куска в голосовой порт модема
    154.     syswrite  $SENDPORT_WAV, $c, 320;
    155.  
    156.     # Ожидаем 0.02 секунды перед тем как продолжить цикл
    157.     sleep(0.02);
    158.     }
    159.  
    160.     # Вешаем трубку.
    161.     at_send('AT+CHUP');
    162.  
    163.     # Возвращаем сообщение об успешном оповещении.
    164.     return "Абонент $l_info->{name} [$l_info->{phone}] УСПЕШНО ОПОВЕЩЕН\n";
    165. }
    166.  
    167. # данная функция загружает голосовое сообщение в массив кусками по 320 байт
    168. # принимает 1 параметр - имя файла
    169. # формат звуковых данных - pcm, моно, 8000 Герц, 16 бит, signed
    170. sub load_voice{
    171.     my $l_file_name = shift;
    172.     my $l_fh = new IO::File "< $l_file_name" or die "Cannot open $l_file_name : $!";
    173.     binmode($l_fh);
    174.     my @l_bufer = ();
    175.     while (read($l_fh,$l_bufer[$i],320)) { $i++; }
    176.     close $l_fh;
    177.     return \@l_bufer;
    178. }
    179.  
    180.  
    181. # данная функция отправляет команду в командный порт модема
    182. # и ждет ответа указанного в регулярном выражении
    183. # принимает 2 параметра:
    184. # 1-й - команда
    185. # 2-й - регулярное выражение описывающее варианты ожидаемых ответов (по умолчанию OK)
    186. sub at_send{
    187.     my $l_cmd = shift;
    188.     my $l_rx = shift || qr/(OK)/;
    189.     print $SENDPORT "$l_cmd\r";
    190.     print "SEND: [$l_cmd]\n" if $VERBOSE;
    191.     return at_rec($l_rx);
    192. }
    193.  
    194.  
    195. # данная функция ждет от модема ответа указанного в регулярном выражении 
    196. # принимает 1 параметр - регулярное выражение описывающее варианты ожидаемых ответов (по умолчанию OK)
    197. sub at_rec{
    198.     my $l_rx = shift || qr/OK/;
    199.     my $recive='';
    200.     while ( !($recive=~$l_rx) ) {
    201. $recive=<$SENDPORT>;
    202. $recive=~s/[\n\r]+//msg;
    203. print "RECIVE: [$recive]\n" if $VERBOSE && $recive;
    204.     }
    205.     $recive=~$l_rx;
    206.     print "END RECIVE: [$recive] [$1] [$l_rx]\n" if $VERBOSE;
    207.     return $1;
    208. }
    209.  
    210.  
    211. # данная функция закрывает ранее открытые порты модема
    212. sub exit_call{
    213.     print "ОПОВЕЩЕНИЕ ОКОНЧЕНО\n";
    214.     close $SENDPORT_WAV;
    215.     at_send('AT+CHUP');
    216.     close $SENDPORT;
    217. }
    218.  


    рассмотрим list.01.pl
    # Список абонентов.
    # Это массив хэш массивов в котором каждая запись содержит
    # данные о абоненте:
    # phone - телефон абонента
    # name  - ФИО абонента
    # Также возможно хранение и других данных об абоненте
    (
        { phone => '+79111234567', name => 'Петров Петр Петрович' },
        { phone => '+79117654321', name => 'Васильев Василий Васильевич' }
    );


    рассмотрим test.voice.raw
    Для создания данного файла использовался аудиоредактор Audacity как показано на картинках:

    image

    image

    image

    image

    image

    Также привожу дополнительные файлы cc_cause.pl и end_status.pl. Они не используются в представленной версии скрипта, но в случае доработки будут полезны.

    cc_cause.pl
    # коды disconnect cause (cc)
    # English http://www.eversoft.net/dcc.html
    # по Русски http://ru.wikipedia.org/wiki/Q.931
    # маны по huawei
    # HUAWEI CDMA Datacard Modem AT Command Interface Specification
    # "http://www.letswireless.com.cn/asp_bin/downfile/2009929121443234.pdf"
    #
    # HUAWEI UMTS Datacard Modem AT Command Interface Specification
    # "http://www.net139.com/UploadFile/menu/HUAWEI%20UMTS%20Datacard%20Modem%20AT%20Command%20Interface%20Specification_V2.3.pdf"
    (
    '1' => 'UNASSIGNED_CAUSE',
    '3' => 'NO_ROUTE_TO_DEST',
    '6' => 'CHANNEL_UNACCEPTABLE',
    '8' => 'OPERATOR_DETERMINED_BARRING',
    '16' => 'NORMAL_CALL_CLEARING',
    '17' => 'USER_BUSY',
    '18' => 'NO_USER_RESPONDING',
    '19' => 'USER_ALERTING_NO_ANSWER',
    '21' => 'CALL_REJECTED',
    '22' => 'NUMBER_CHANGED',
    '26' => 'NON_SELECTED_USER_CLEARING',
    '27' => 'DESTINATION_OUT_OF_ORDER',
    '28' => 'INVALID_NUMBER_FORMAT',
    '29' => 'FACILITY_REJECTED',
    '30' => 'RESPONSE_TO_STATUS_ENQUIRY',
    '31' => 'NORMAL_UNSPECIFIED',
    '34' => 'NO_CIRCUIT_CHANNEL_AVAILABLE',
    '38' => 'NETWORK_OUT_OF_ORDER',
    '41' => 'TEMPORARY_FAILURE',
    '42' => 'SWITCHING_EQUIPMENT_CONGESTION',
    '43' => 'ACCESS_INFORMATION_DISCARDED',
    '44' => 'REQUESTED_CIRCUIT_CHANNEL_NOT_AVAILABLE',
    '47' => 'RESOURCES_UNAVAILABLE_UNSPECIFIED',
    '49' => 'QUALITY_OF_SERVICE_UNAVAILABLE',
    '50' => 'REQUESTED_FACILITY_NOT_SUBSCRIBED',
    '55' => 'INCOMING_CALL_BARRED_WITHIN_CUG',
    '57' => 'BEARER_CAPABILITY_NOT_AUTHORISED',
    '58' => 'BEARER_CAPABILITY_NOT_PRESENTLY_AVAILABLE',
    '63' => 'SERVICE_OR_OPTION_NOT_AVAILABLE',
    '65' => 'BEARER_SERVICE_NOT_IMPLEMENTED',
    '68' => 'ACM_GEQ_ACMMAX',
    '69' => 'REQUESTED_FACILITY_NOT_IMPLEMENTED',
    '70' => 'ONLY_RESTRICTED_DIGITAL_INFO_BC_AVAILABLE',
    '79' => 'SERVICE_OR_OPTION_NOT_IMPLEMENTED',
    '81' => 'INVALID_TRANSACTION_ID_VALUE',
    '87' => 'USER_NOT_MEMBER_OF_CUG',
    '88' => 'INCOMPATIBLE_DESTINATION',
    '91' => 'INVALID_TRANSIT_NETWORK_SELECTION',
    '95' => 'SEMANTICALLY_INCORRECT_MESSAGE',
    '96' => 'INVALID_MANDATORY_INFORMATION',
    '97' => 'MESSAGE_TYPE_NON_EXISTENT',
    '98' => 'MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROT_STATE',
    '99' => 'IE_NON_EXISTENT_OR_NOT_IMPLEMENTED',
    '100' => 'CONDITIONAL_IE_ERROR',
    '101' => 'MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE',
    '102' => 'RECOVERY_ON_TIMER_EXPIRY',
    '111' => 'PROTOCOL_ERROR_UNSPECIFIED',
    '127' => 'INTERWORKING_UNSPECIFIED',
    '160' => 'REJ_UNSPECIFIED',
    '161' => 'AS_REJ_RR_REL_IND',
    '162' => 'AS_REJ_RR_RANDOM_ACCESS_FAILURE',
    '163' => 'AS_REJ_RRC_REL_IND',
    '164' => 'AS_REJ_RRC_CLOSE_SESSION_IND',
    '165' => 'AS_REJ_RRC_OPEN_SESSION_FAILURE',
    '166' => 'AS_REJ_LOW_LEVEL_FAIL',
    '167' => 'AS_REJ_LOW_LEVEL_FAIL_REDIAL_NOT_ALLOWD',
    '168' => 'MM_REJ_INVALID_SIM',
    '169' => 'MM_REJ_NO_SERVICE',
    '170' => 'MM_REJ_TIMER_T3230_EXP',
    '171' => 'MM_REJ_NO_CELL_AVAILABLE',
    '172' => 'MM_REJ_WRONG_STATE',
    '173' => 'MM_REJ_ACCESS_CLASS_BLOCKED',
    '174' => 'ABORT_MSG_RECEIVED',
    '175' => 'OTHER_CAUSE',
    '176' => 'CNM_REJ_TIMER_T303_EXP',
    '177' => 'CNM_REJ_NO_RESOURCES',
    '178' => 'CNM_MM_REL_PENDING',
    '179' => 'CNM_INVALID_USER_DATA'
    );
     


    end_status.pl
    # коды Call ending cause codes
    # маны по huawei
    #
    # HUAWEI CDMA Datacard Modem AT Command Interface Specification
    # "http://www.letswireless.com.cn/asp_bin/downfile/2009929121443234.pdf"
    #
    # HUAWEI UMTS Datacard Modem AT Command Interface Specification
    # "http://www.net139.com/UploadFile/menu/HUAWEI%20UMTS%20Datacard%20Modem%20AT%20Command%20Interface%20Specification_V2.3.pdf"
    (
    '0' => 'The board is offline.',
    '21' => 'Board is out of service.',
    '22' => 'Call is ended normally.',
    '23' => 'Call is interrupted by BS.',
    '24' => 'BS record is received during a call.',
    '25' => 'BS releases a call.',
    '26' => 'BS rejects the current SO service.',
    '27' => 'There is incoming BS call.',
    '28' => 'received alert stop from BS.',
    '29' => 'Call is ended normally by the client end.',
    '30' => 'received end activation — OTASP call.',
    '31' => 'MC ends call initiation or call.',
    '34' => 'RUIM is not available.',
    '99' => 'NDSS error.',
    '100' => 'rxd a reason from lower layer,look in cc_cause',
    '101' => 'After a MS initiates a call, the network fails to respond.',
    '102' => 'MS rejects an incoming call.',
    '103' => 'A call is rejected during the put-through process.',
    '104' => 'The release is from the For details, check',
    '105' => 'The phone fee is used up.',
    '106' => 'The MS is out of the service'
    );


    В завершение.

    Данная версия скрипта голосового оповещения не претендует на полноту и правильность реализации, а является лишь демонстрацией, и для серьезного использования может быть и должна быть усовершенствована. Необходимо добавить более серьезную обработку состояний CEND, реализовать условия повторного дозвона до абонентов, если с первого раза не удалось оповестить. Также можно сделать web интерфейс включающий в себя планировщик задач, редактор списков абонентов, генерацию отчетов и многое другое.

    Я надеюсь что эта статья окажется востребованной и полезной для Вас, а также постараюсь и впредь выкладывать интересные и полезные статьи.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 19

      +4
      Исправил ошибки парсера
        +3
        Хм, спасибо! Очень хорошая работа по собиранию всей информации в одну программу, в будущем эти наработки, думаю, мне понадобятся =)
          0
          А чтобы не задавать номера портов вручную в скрипте а определять автоматически не экспериментировали?
            0
            Мысли такие посещали, но все бегом бегом, так и оставил выбор портов вручную.
            0
            А сделать изменения например ожидание нажатия кнопки 1 или 0 сложно доработать?
              0
              Вы имеете ввиду голосовое меню?
                0
                Да именно, в целях скажем так перенаправления или принятия решения.
                  0
                  Такие изменения сделать можно. Но нужно учесть что для того чтобы принимать данные о нажатых кнопках на телефоне (по моему эта информация передается в виде USSD, но полной уверенности нет) во время разговора (в голосовой порт пишутся аудиоданные каждые 0.02 секунды) можно поступить 2-я способами:
                  1. чтение данных из командного в параллельном потоке (возможны различные варианты: fork, thread, и по моему был модуль в составе IO умеющий распараллеливать процессы чтения и записи. В случае с fork и thread нужно организовать межпроцессное взаимодействие помоему)
                  2. неблокирующее чтение данных из командного порта (в этом подходе так же есть свои нюансы)
                    0
                    Немного почитав доку — выяснил что во время звонка нажатия кнопок передаются как DTFM, и в этом случае для организации голосового меню достаточно паралельно с записью в голосовой порт произвоодить чтение из него-же, и считанный 320 байтный кусок анализировать на наличие в нем сигнала DTFM. Попробую на неделе сделать/доработать скрипт — умеющий распознавать сигналы тонального набора.
                      0
                      Для более глубокого понимания механизмов организации голосовых меню можно почитать то что выдает google по запросу IVR DTFM
                        0
                        С наскоку победить DTMF не удалось. (в 2-х предыдущих сообщениях ошибочно обозвал DTFM).
                        Нашел несколько косвенных упоминаний о том что e1550 не умеет аппаратно распознавать DTMF сигналы.
                        Программно вроде как распознает perl модуль Modem::Vgetty, более того — этот модуль умеет звонить через модем и организовывать голосовые меню.
                        Также на CPAN-е было найдено еще несколько модулей для создания голосовых меню, и для декодирования DTMF, но эксперименты результата не дали. Возможно я что то не так делал.
                        Есть еще вариант пойти по пути предложенному человеком выложившим код на Си с предложением переделать его в перл.

                        Ну вот как то так. Вынужден пока отступиться от проблемы.
                          0
                          Переписал на Perl эту программу. Потратил на нее уйму времени, но так и не добился распознавания DTMF сигнала. Потерялся в логике функции read_frame, а именно странный расчет количества считываемых байтов:
                             x = read(fd, &buf[i], N-i);
                             ...
                             i += x;
                          

                          А также не доконца понимаю логику функции calc_power, Логика функции зависит от значений в массиве:
                             int k[] = { 11, 13, 14, 19, 21, 23, 26, 27, 28, 33, 36, 39, 40,  /*44,*/ 45, 49, 51, 72, 78, };
                          
                          Но что означают эти цифры — не понимаю.

                          Если кто знает как это работает — поделитесь пожалуйста опытом.
                            0
                            Да уж смотрю сложности с голосовым меню не маленькие…
                            Насколько мне помниться есть алгоритм распознавания сигнала Wiki: «Сигнал DTMF может быть декодирован на цифровой ЭВМ с использованием алгоритма Гёрцеля.» Но я так и не смог его понять.
                              0
                              Да читал, просто хорошо так подзабыл математику. Буду пробовать дальше. Если выйдет каменный цветок — отпишусь.
                            0
                            Все таки победил DTMF. И сумел получить из этого работающий perl код. В архиве 3 файла:
                            1. dtmf_decode.01.pl — работающая версия perl скрипта DTMF декодера.
                            2. test.for.bug.raw — записанный с телефона голос и нажатия кнопок (DTMF сигналы): 01234567890*#
                            2. test.for.bug.1.raw — с генерированный файл с нажатиями кнопок (DTMF сигналы): 112163 112196 11#9632 ##9696

                            Хочу отметить, что на реально записанный файл на части где говорю голосом происходят ложные срабатывания. Вот вывод анализатора на этот файл:
                            DIALTONE BUSY RING DIALTONE RING BUSY RING BUSY RING BUSY DIALTONE 01234567890*#

                            Таким образом делаю вывод что использовать DTMF декодер нужно совместно с Voice activity detector
                        +1
                        Спасибо за статью. Функция прозвона уникальна для этого модема или все модемы имеют эту функцию? Например, huawei e220 имеет подобные возможности? Тема модемов мне очень интересна. Например, часто модемы залочены и умельцы делают программы, которые их разлочивают. Я бы хотел знать, что эти программы делают с модемом и еще лучше посмотреть на аналогичный скрипт. Если кто знает напишите статью.
                          +1
                          Я писал (собирал информацию) именно под e1550, как обстоят дела с другими девайсами к сожалению не подскажу.
                            +1
                            По поводу разлочки модемов — точной информацией не обладаю — но мне кажется что модем может быть «залочен» как на уровне десктопного ПО идущего в комплекте с модемом, так и на уровне прошивки. Подобное мнение у меня сложилось после эксперементов с 3-я модемами huawei e1550 от мегафона. Один был разлочен другом, 2 остальных разлочены небыли. И при попытках выйти в интернет сложилась такая картина:
                            1-й модем: выходит в нет под любой симкой как в Linux так и в MS Windows
                            2-й модем: выходит в нет под любой симкой только в Linux, и под родной симкой в MS Windows
                            3-й модем: выходит в нет и в Linux, и в MS Windows только с родной симкой.
                            Для выхода в нет под MS Windows использовалась ПО из комплекта модема.
                            Для выхода в нет под Linux использовался kNetworkManager.

                            Вроде как для разлочки модема (залоченного на уровне прошивки) нужно ввести AT команду: AT^CARDLOCK=«nck code» (источник1, источник2, источник3)
                            Также где то читал, что данный метод работает не на всех прошивках и не у всех операторов связи.
                            0
                            по многочисленным просьбам залил все имеющиеся наработки по теме статьи на github

                            Only users with full accounts can post comments. Log in, please.