Pull to refresh

Голосовой автоинформатор даты и времени, приятным женским голосом, русским языком, на базе asterisk? Легко

Reading time7 min
Views38K
В преддверии выходных не чем себя занять, так как по регламенту не позволены грандиозные настройки? На старом, заброшенном сервере запылился asterisk? Абоненту нечем тестировать телефонную линию? Для тех, кому не с кем поговорить и для тех, кто потерялся во времени.



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

или

Текущее время один час, тридцать пять минут, десять секунд. Сегодня четверг, шестнадцатое октября.

Для простоты и прозрачности внедрения мы не будем пользоваться AGI и попросим железную леди сообщать нам дату и время, по большому счету, поработав лишь с dialplan`ом и say.conf`ом. И если ваш asterisk до сих пор не говорит по-русски — не беда, этому мы его научим. Кому стало интересно, добро пожаловать под хабракат.


Который час?


Думаю, не стоит даже упоминать, что ваш сервер должен знать точное время, опираясь, например, на NTP.

В двух словах о настройке NTP
Время на серверах имеет свойство рассинхронизироваться, если его не подводить. Пакет ntp потребуется в любом случае. Для себя я выбрал первый вариант с ntpd, так как другие мои сервера подводят время внутри сети.
#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/null\n" >> /etc/crontab



Обучим леди русскому языку


Далее предположим, что asterisk установлен, но если это не так, то есть многоматериалов на эту тему. Так же будем считать, что первоначальная настройка хотя бы одного SIP-телефона или софтфона уже произведена.

Пакет русских звуков
Для Asterisk 1.4, если добавить languageprefix=yes в asterisk.conf, структура звуковых каталогов будет как в более новых версиях по умолчанию.
Стандартный каталог: /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


#cat /etc/asterisk/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


#cat /etc/asterisk/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.

Про работу с переменными asterisk можно ознакомиться под спойлером
Полный синтаксис переменной ${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`ом.

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()



Бонус: прослушать готовый результат в живую можно по телефону:
Tags:
Hubs:
+15
Comments12

Articles

Change theme settings