В преддверии выходных не чем себя занять, так как по регламенту не позволены грандиозные настройки? На старом, заброшенном сервере запылился asterisk? Абоненту нечем тестировать телефонную линию? Для тех, кому не с кем поговорить и для тех, кто потерялся во времени.
В этой публикации мы будем делать акцент на падежи порядковых числительных, так как произносимый текст должен быть связан и не резать слух. Попытаемся добиться следующего произношения:
Текущее время пятнадцать часов, двадцать одна минута, двадцать секунд. Сегодня среда, пятнадцатое октября.
или
Текущее время один час, тридцать пять минут, десять секунд. Сегодня четверг, шестнадцатое октября.
Для простоты и прозрачности внедрения мы не будем пользоваться AGI и попросим железную леди сообщать нам дату и время, по большому счету, поработав лишь с dialplan`ом и say.conf`ом. И если ваш asterisk до сих пор не говорит по-русски — не беда, этому мы его научим. Кому стало интересно, добро пожаловать под хабракат.
Который час?
Думаю, не стоит даже упоминать, что ваш сервер должен знать точное время, опираясь, например, на NTP.
#yum -y install ntp
Вариант №1. Добавляем сервера по вкусу, в зависимости от региона, в котором находится asterisk. Чем быстрее отклик от сервера времени, тем точнее оно выставится, при прочих равных. Я выберу российский пул.
#grep "^server" /etc/ntp.conf server 0.ru.pool.ntp.org server 1.ru.pool.ntp.org server 2.ru.pool.ntp.org server 3.ru.pool.ntp.org #chkconfig ntpd on #service ntpd start
Проверяем, что демон слушает нужные порты:
#netstat -putln | grep ntpd
Если есть желание раздавать время своим серверам, не забудьте проверить файервол, порт UDP/123. Правило должно встать до завершающего REJECT`а. Не забудьте сохранить.
#iptables -nL --line-numbers #iptables -I INPUT 4 -s 10.0.0.0/255.0.0.0 -p udp --dport 123 -j ACCEPT #service iptables save
Вариант №2. Для разовой корректировки подойдет утилита ntpdate, входящая в тот же пакет ntp. Такой вариант годен, если раздача времени с сервера не планируется.
#ntpdate ru.pool.ntp.org 9 Oct 17:08:41 ntpdate[32744]: adjust time server 85.21.78.8 offset -0.183259 sec
Можно добавить в cron:
#echo -e "n47 */1 * * * root /usr/sbin/ntpdate pool.ntp.org > /dev/nulln" >> /etc/crontab
Обучим леди русскому языку
Далее предположим, что asterisk установлен, но если это не так, то есть многоматериалов на эту тему. Так же будем считать, что первоначальная настройка хотя бы одного SIP-телефона или софтфона уже произведена.
Стандартный каталог: /var/lib/asterisk/ в котором подпапки, зависящие от двух «xx» букв ISO кода страны (ru, nl, fr, de, it, pt, es ...)
sounds/xx sounds/xx/digits sounds/xx/letters sounds/xx/phonetic
Создадим каталог, если его нет
#mkdir /var/lib/asterisk/sounds/ru/
Загружаем русские звуки:
#wget -O asterisk-sounds-additional-master.zip https://github.com/pbxware/asterisk-sounds-additional/archive/master.zip #wget -O asterisk-sounds-master.zip https://github.com/pbxware/asterisk-sounds/archive/master.zip
Распаковываем:
#unzip asterisk-sounds-additional-master.zip #unzip asterisk-sounds-master.zip
Копируем на свое место:
#cp -R ./asterisk-sounds-additional-master/* /var/lib/asterisk/sounds/ru/ #cp -R ./asterisk-sounds-master/* /var/lib/asterisk/sounds/ru/
Посмотреть, какие фразы записаны можно в следующих файлах:
#less ./asterisk-sounds-additional-master/additional-sounds-ru.txt #less ./asterisk-sounds-master/core-sounds-ru.txt
sip.conf
Укажем asterisk`у, использовать русский язык для SIP, добавив language=ru в [general]:
#cat /etc/asterisk/sip.conf [general] language=ru
Применяем настройки:
#asterisk -rx "sip reload"
say.conf
[ru-base](!)
_[n]um:0X => num:${SAY:1}
_[n]um:X => digits/${SAY}
_[n]um:[1-2]f => digits/${SAY:0:1}f
_[n]um:[3-9]f => digits/${SAY:0:1}
; Tens
_[n]um:1X => digits/${SAY:0:2}
_[n]um:1Xf => digits/${SAY:0:2}
_[n]um:[2-9]0 => digits/${SAY:0:2}
_[n]um:[2-9]0f => digits/${SAY:0:2}
_[n]um:[2-9][1-2] => digits/${SAY:0:1}0, num:${SAY:1}
_[n]um:[2-9][1-2]f => digits/${SAY:0:1}0, num:${SAY:1}
_[n]um:[2-9][3-9] => digits/${SAY:0:1}0, num:${SAY:1}
_[n]um:[2-9][3-9]f => digits/${SAY:0:1}0, num:${SAY:1}
; Hundreds
_[n]um:0XX => num:${SAY:1}
_[n]um:0XXf => num:${SAY:1}
_[n]um:[1-9]00 => digits/${SAY:0:1}00
_[n]um:[1-9]00f => digits/${SAY:0:1}00
_[n]um:XXX => num:${SAY:0:1}00, num:${SAY:1}
_[n]um:XXXf => num:${SAY:0:1}00, num:${SAY:1}
; enumeration
_e[n]um:X => digits/h-${SAY}
_e[n]um:X[n] => digits/h-${SAY}
_e[n]um:0X => enum:${SAY:1}
_e[n]um:0X[n] => enum:${SAY:1}
_e[n]um:1X => digits/h-${SAY}
_e[n]um:1X[n] => digits/h-${SAY}
_e[n]um:[2-9]0 => digits/h-${SAY}
_e[n]um:[2-9]0[n] => digits/h-${SAY}
_e[n]um:[2-9][1-9] => num:${SAY:0:1}0, digits/h-${SAY:1}
_e[n]um:[2-9][1-9][n] => num:${SAY:0:1}0, digits/h-${SAY:1}
_e[n]um:[1-9]00 => digits/h-${SAY}
_e[n]um:[1-9]00[n] => digits/h-${SAY}
_e[n]um:[1-9]XX => num:${SAY:0:1}00, enum:${SAY:1}
_e[n]um:[1-9]XX[n] => num:${SAY:0:1}00, enum:${SAY:1}
[ru](ru-base)
_chas:0 => num:${SAY}, digits/hours
_chas:1 => digits/${SAY}, digits/hour
_chas:[2-4] => num:${SAY}, digits/hours-a
_chas:[5-9] => num:${SAY}, digits/hours
_chas:0X => chas:${SAY:1}
_chas:1X => num:${SAY}, digits/hours
_chas:20 => num:${SAY}, digits/hours
_chas:2[1-4] => num:${SAY:0:1}0, chas:${SAY:1}
_mi[n]uta:0 => num:${SAY}, digits/minutes
_mi[n]uta:1 => digits/1f, digits/minute
_mi[n]uta:2 => digits/2f, digits/minutes-i
_mi[n]uta:[3-4] => num:${SAY}, digits/minutes-i
_mi[n]uta:[5-9] => num:${SAY}, digits/minutes
_mi[n]uta:0X => minuta:${SAY:1}
_mi[n]uta:1X => num:${SAY}, digits/minutes
_mi[n]uta:[2-5]0 => num:${SAY}, digits/minutes
_mi[n]uta:[2-5][1-9] => num:${SAY:0:1}0, minuta:${SAY:1}
_seku[n]da:0 => num:${SAY}, seconds
_seku[n]da:[5-9] => num:${SAY}, seconds
_seku[n]da:0X => sekunda:${SAY:1}
_seku[n]da:1X => num:${SAY}, seconds
_seku[n]da:[2-5]0 => num:${SAY}, seconds
_dayofweek:[0-6] => digits/day-${SAY}
_dayofmo[n]th:X => enum:${SAY}n
_dayofmo[n]th:XX => enum:${SAY}n
_mo[n]th:X => digits/mon-$[${SAY} - 1]
_mo[n]th:XX => digits/mon-$[${SAY} - 1]
Применяем настройки:
#asterisk -rx "module reload app_playback.so"
Контекст [ru-base] в say.conf имеет завершающий (!)
восклицательный знак в скобках означает, что это шаблон, который мы в дальнейшем включаем в [ru]
Попробуем разобрать одно правило. Первое, на что стоит обратить внимание, это символы X Z N. Они интерпретируются asterisk`ом как специальные и если эти литеры фигурируют в названии правила чтения, их следует взять в квадратные скобки, например mo[n]th.
_mo[n]th:XX => digits/mon-$[${SAY} - 1]
Синтаксис достаточно прост и правило совпадает, если входные данные XX — две любые цифры. Проигрываем файл digits/mon-(XX-1), где (XX-1) это арифметическая операция. При X=02 (да, «переваривает» даже такие цифры, что нам очень поможет), 02-1=1, digits/mon-1: «Февраля».
Отдельно стоит упомянуть секунды. Во-первых, в записанных фразах есть только единственная запись: «секунд». Это значит, что на вход этой функции должны приходить округленные данные, например 0, 10, 20, и так далее. А во-вторых, по мнению автора, это облегчит восприятие полученной информации.
extensions.conf
[my_regular_context] ; Там где живет ваш SIP абонент/телефон.
exten => 100,1,Goto(informer_100,s,1)
[informer_100]
exten => s,1,Set(FreezeEPOCH=$[${EPOCH} + 15]) ; Добавляем 15 секунд к unixtime.
same => n,Set(TimeNow=${STRFTIME(${FreezeEPOCH},,%Y%m%d%H%M.%S-%w-%j)})
same => n,Playback(silence/1&at-tone-time-exactly) ;Тишина 1 секунда + Текущее время
same => n,Playback(chas:${TimeNow:8:2},say) ; десять + часов
same => n,Playback(minuta:${TimeNow:10:2},say) ; сорок + одна + минута
same => n,Playback(sekunda:${TimeNow:13:1}0,say) ; двадцать + секунд
same => n,Playback(silence/1&digits/today) ; тишина 1 секунда + сегодня
same => n,Playback(dayofweek:${TimeNow:16:1},say) ; четверг
same => n,Playback(dayofmonth:${TimeNow:6:2},say) ; шестнадцатое
same => n,Playback(month:${TimeNow:4:2},say) ; октября
same => n,Playback(silence/1&beep) ; тишина 1 секунда + короткий гудок
same => n,Hangup()
Применяем настройки:
#asterisk -rx "dialplan reload"
same => n,Set(FreezeEPOCH=$[${EPOCH} + 15])
В контексте [informer_100]
стоит объяснить строчку, где мы в переменной FreezeEPOCH
добавляем 15 секунд к unixtime. Сделано это для компенсации времени, потраченного на проигрывание файлов, предшествующих секундам.
Далее мы формируем необходимый нам формат даты в переменной TimeNow
. Она содержит данные в виде: 201410160043.34-4-289
. При чтении мы выдергиваем из «массива» необходимые числа. Они всегда на своих местах и извлечение не составит труда. Более подробно о форматах можно посмотреть в #man strftime
.
${AnyVariable:x:y}
, где x — начальное положение, а y — количество цифр, которое должно быть возвращено. Пусть задана строка:201410160043.34-4-289
Используя конструкцию ${AnyVariable:x:y}
, можно извлечь следующие данные:
${AnyVariable:0:4}
— будет возвращена строка 2014. Пропустить ноль символов слева и взять четыре символа.
${AnyVariable:4:8}
— будет возвращена строка 10160043.
${AnyVariable:-3:3}
— строка будет начинаться с третьего символа, считая с конца и включает три символа, что даст 289.
${AnyVariable:2}
— если количество цифр, которое должно быть возвращено, не задано, будет возвращена вся оставшаяся строка, получим 1410160043.34-4-289.
Исходя из say.conf, секунды у нас должны округляться. Из двухзначного формата секунд мы выбираем первую цифру и добавляем к ней ноль: ${TimeNow:13:1}0
Возможно, кто-то из читателей захочет самостоятельно сформировать правила, например, для прочтения рублей. Объема готовых примеров должно быть достаточно, чтобы справиться с этой задачей. А для проверки произношения можно воспользоваться нижеприведенным dialplan`ом.
exten => 101,1,Set(Number=0) ; от нуля
same=>n(start),playback(enum:0${Number}n,say) ; читать enum с предшествующим нулем
same=>n,Set(Number=$[ ${Number} + 1 ]) ; шаг в единицу
same=>n,GotoIf($[${Number} <= 9 ]?start) ; до девяти
same=>n,Hangup()
Прослушать готовый результат в живую можно по телефону:
Автор: xBrowser