Русскоговорящий будильник/напоминаловка для Asterisk

 

В Asterisk`е есть штатный будильник - напоминаловка, если вы загляните в директорию /var/lib/asterisk/agi-bin , то увидите что то похожее на wakeup.php. Вещь не плохая, но хочу предложить Вашему вниманию, более продвинутую версию русскоговорящего будильника. В нашем случае, Вы можете отправлять голосовые сообщения как сами себе, так и на любые другие номера. Причем задавать дату можно как в четком виде ГГММДД ЧЧММ, так и в неявном виде, через 10 минут от текущего времени, или через 3-и дня от текущей даты.

 



Концепция будильника следующая, пользователь вводит со своего телефона комбинацию цифр вида *0*X*Y*Z, где

X – дата, когда необходимо озвучить сообщение
Y – время, когда необходимо озвучить сообщение
Z – номер, на который необходимо позвонить для проигрывания сообщения

 

Возможные варианты значения X (дата, когда проигрывать напоминание)

0

Звонить сегодня

От 1 до 99

Позвонить через 1-99 дней

ГГГГММДД

Позвонить в конкретную дату

 

Возможные варианты значения Y (время, когда проигрывать напоминание)

0

Звонить в это время (не работает с текущим днем)

От 1 до 999

Звонить через 1-999 минут

ЧЧММ

Звонить в конкретное время

 

Возможные варианты значения Z (номер куда будем отправлять напоминание)

0

Номер с которого звонят

zzz

Локальный номер (у меня 3-х значные номера)

zzzzzzzzzzzzzzzzz

Внешний номер, может быть любым!

 



Примеры:

*0*0*120*0  - позвонить через 2-а часа  на свой телефон, и озвучить надиктованное сообщение (не забыть выключить суп!).

*0*20101231*2300*777 – позвонить 31 декабря 2010 года в 23.00 на номер 777 и озвучить поздравление с новым годом!

*0*30*1500*78123090607 – позвонить через 30 дней  в 15.00 на номер 78123090607 и озвучить сообщение (пора оплачивать счета)!

*0*20110308*0*1234567 – позвонить 8 марта 2011 года в текущее время на номер 1234567 и озвучить поздравление с праздником!

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


В файле extensions.conf мы должны описать наш экстеншен!

exten => _*0*X.,1,Answer
exten => _*0*X.,n,AGI(reminder.agi)
exten => _*0*X.,n,Hangup

Таким образом, все набранные номера, которые начинаются с *0* попадают в наш контекст и включают скрипт reminder.agi

Далее опишем контекст go – он запускается call файлом в момент когда происходит дозвон до абонента с целью озвучить надиктованное сообщение.

exten => go,1,NoOp("Будильничег")
exten => go,n,AGI(reminder_listen.agi)
exten => go,n,HagnUp


ну и теперь самое интересное, это наши скрипты

/var/lib/asterisk/agi-bin/reminder.agi

#!/usr/bin/perl
use Asterisk::AGI;
use POSIX;
use File::Copy;
use Time::Local;
use Date::Calc qw (Add_Delta_Days);

$AGI = new Asterisk::AGI;
my %input = $AGI->ReadParse();

$trunk="megatrank";
# начало отсчета с 1900 года, значение year - смещение от 1900 года
$datesec=time;
#chomp($datesec);
# получаем значение exten и заносим в переменную $digit – это то что набрал пользователь на своем телефоне, далее мы разберем по частям эти цифры с целью понять что хотел сказать пользователь (когда и куда звонить)
$digit = $AGI->get_variable('EXTEN');
# на всякий случай, выдергиваем номер звонящего
$src = $AGI->get_variable('CALLERID(num)');
#регулярным выражением выдергиваем значения даты времени и номера звонящего
$digit =~ /^\*0\*(\d+)\*(\d+)\*(\d+)/;
$wdate=$1;
$wtime=$2;
$wnum=$3;

# вводим маленькую функцию для озвучки ошибки набора, что б несколько раз не писать один и тот же код!
# обратите внимание, я взял звуковые файлы с http://ivrvoice.ru/downloader и перекинул их  в Asterisk в папку /var/lib/asterisk/sounds/ru11 !!!!
sub digit_error {
$AGI->exec('Wait',"1");
$AGI->exec('Playback',"ru11/an-error-has-occured");
$AGI->exec('Playback',"ru11/check-number-dial-again");
exit 0;
}
# звуковые файлы в директории digits мною были заменены с английского на русский аналог, все с того же сайта. На самом деле это не совсем верно, по идее Астериск должен цеплять русские файлы после переопределения глобальной переменной LANG, однако сходу у меня не получилось, потому исправил ситуацию простым копированием файлов.
# Данная функция, корректно для нашего чисто русского слуха, озвучивает день!
sub say_day {
$sayday=$_[0];
if ("$sayday" < 20) {
$AGI->exec('Playback',"digits/h-$sayday\\n");
} elsif ( 20 < "$sayday" and  "$sayday" < 30 ) {
$sayday=~/\d(\d)/;
$AGI->exec('Playback',"digits/20");
$AGI->exec('Playback',"digits/h-$1\\n");
} elsif ( "$sayday" == 20 ) {
$AGI->exec('Playback',"digits/h-20n");
} elsif ( "$sayday" == 30 ) {
$AGI->exec('Playback',"digits/h-30n");
} else {
$AGI->exec('Playback',"digits/30");
$AGI->exec('Playback',"digits/h-1\\n");
}
}

# берем локальное время и разбиваем на переменные
($sec2,$min2,$hour2,$mday2,$mon2,$year2,$wday2,$yday2,$isdst2)=localtime($datesec);
#####

не забываем к году прибавить 1900 а к месяцу еденичку
$tmpyear2=$year2+1900;
$tmpmon2=$mon2+1;
# для проверки работы скрипта выводим на экран информацию, когда у нас задание стартовало.
$AGI->exec('NoOp',"Старт_задания_$tmpyear2.$tmpmon2.$mday2\_в_$hour2:$min2");
#####
# если пользователь ввел в поле дата 8 цифр, значит он имел ввиду четкую дату!
if (length($wdate) eq "8") {
$wdate =~ /(\d{4})(\d{2})(\d{2})/;
$timestamp1 = timelocal($sec2,$min2,$hour2,$3,$2-1,$1-1900);
# если ввел 0 – значит имел ввиду что запустить будильник сегодня
} elsif ($wdate eq "0") {
$timestamp1=$datesec;
# если ввел 2-х значное число, значит имел ввиду что пускть задание через сколько то дней.
} elsif (length($wdate) ge "1" and length($wdate) le "2" and $wdate ne "0") {
$timestamp1=$datesec + $wdate*86400;
# ну а если ерунду ввел, значит говорим ему об этом в мягкой форме и октлючаемся
} else {
&digit_error;
}
# обратите внимание, я не делал проверку на правильность ввода даты, т.е. система проверяет, кол-во дней в месяце, месяцев в году, что не есть хорошо, но на мой взгляд это не критично!
####
# Та же операция с временем
if (length($wtime) eq "4") {
$wtime =~ /(\d{2})(\d{2})/;
$timestamp=$timestamp1-(localtime($datesec))[2]*3600-(localtime($datesec))[1]*60+($1*3600)+($2*60);
} elsif ($wtime eq "0") {
$timestamp=$timestamp1+60;
} elsif (length($wtime) ge "1" and length($wtime) le "3") {
$timestamp=$timestamp1+$wtime*60;
} else {
$AGI->exec('NoOp',"Не_верно_задано_время");
&digit_error;
}
####

($sec1,$min1,$hour1,$mday1,$mon1,$year1,$wday1,$yday1,$isdst1)=localtime($timestamp);
$mon_sound="mon-$mon1";
$mon1++;
$year1=1900+$year1;

# В результате хитрых и не очень операций, получаем время, когда необходимо позвонить нашему абоненту!
$AGI->exec('NoOp',"Выполнение_задачи_$year1.$mon1.$mday1\_в_$hour1:$min1");

####
# если же дата выполнения задачи ранее текущей даты, значит пользователь ошибся при вводе даты и времени
if ($timestamp le $datesec) {
$AGI->exec('NoOp',"Дата_задания_меньше_текущего_времени!");
&digit_error;
}
####
# проделываем похожую операцию с номером куда будем звонить, при 0 – звоним сами себе, если длина введенных цыфр от 3-х до 4-х, значит это локальный звонок,  если ни то не другое, пользователь имел ввиду что звоним на внешний номер!
if ( $wnum eq "0" ) {
$ch="SIP/$src";
$dst="$src";
} elsif ( length($wnum) eq "4" or length($wnum) eq "3") {
$ch="SIP/$wnum";
$dst="$wnum";
} else {
$ch="SIP/$wnum\@$trunk";
$dst="$wnum";
}

#формируем имя файла для звонка
$records="$year1$mon1$mday1\-$hour1$min1-$dst";
$filename="/var/lib/asterisk/sounds/records/$records.sln";

CICLE3:
# Приятный женский голос говорит –оставьте сообщение после сигнала, затем нажмите решетку или повесьте трубку. Вешать трубку не следует, т.к. нам необходимо будет подтвердить запись, потому необходимо после надиктованного сообщения нажать решетку
$AGI->exec('Playback',"ru11/vm-intro");
$AGI->exec('Record',"records/$records.sln||10");
CICLE2:
# Тут мы озвучиваем само сообщение и куда и когда оно будет отправлено
$AGI->exec('Playback',"ru11/vm-soobshenie");
$AGI->exec('Playback',"ru11/na-nomer");
$AGI->exec('SayDigits',"$dst");
$AGI->exec('Playback',"digits/at");
$AGI->exec('Playback',"digits/day-$wday1");
&say_day($mday1);
$AGI->exec('Playback',"digits/$mon_sound");
$AGI->exec('Playback',"digits/at");
$AGI->exec('SayNumber',"$hour1");
$AGI->exec('Playback',"ru11/hours");
$AGI->exec('SayNumber',"$min1");
$AGI->exec('Playback',"ru11/minutes");
$AGI->exec('Playback',"records/$records");

# ввели счетчик возварата к прослушки или записи, что бы пользователь не заигрывался.
$count++;
if ($count eq "5") {  $AGI->exec('Playback',"ru11/goodbye"); unlink($filename); exit 0; }

# подтверждаем запись (нажмите 1-н что бы принять сообщение 2-а что бы прослушать, 3-и что бы записать его заново)
$AGI->exec('Read',"rep|ru11/vm-review|1||1|5");
$rep = $AGI->get_variable("rep");


if ( $rep eq "2" ){
goto CICLE2;
} elsif ( $rep eq "3" ) {
unlink($filename);
goto CICLE3;
# если пользователь вводит 1-н, тем самым подтверждая запись, ему говорить что сообщение записано и формируется call файл в контексте wakeup экстеншен go, максимальное количество попыток дозвона 3, время ожидания на проводе 60 сек. Так же говорим что CallID у нас подменяется на NOTE (напоминание), это нужно для локальных телефонов с дисплеем, что бы можно было понять от кого звонок и передаем переменную в астериск date, говорящую когда была сделана запись, а так же переменную с именем файла, который будем слушать.
} elsif ( $rep eq "1" ) {
$AGI->exec('Playback',"ru11/vm-msgsaved");
open (CALL,  "> /tmp/$records");
print CALL  "Channel:$ch\nContext:wakeup\nExtension:go\nPriority:1\nMaxRetries:3\nRetryTime:60\nWaitTime:60\nCallerID:NOTE<$src>\nSet:date=$datesec\nSet:src=$src\nSet:records=$records\n";
close (CALL);

#меняем атрибуты файла, что бы астериск при чтении директории outgoing не запускал сразу после перемещения нашего call файла процесс дозвона.
utime($timestamp,$timestamp,"/tmp/$records");
move("/tmp/$records","/var/spool/asterisk/outgoing/$records");
$AGI->exec('Playback',"ru11/goodbye");
exit 0;
}
# попрощались и удалили временный файл.
$AGI->exec('Playback',"ru11/demo-moreinfo");
unlink($filename);
exit 0;


Скрипт рабочий, у Вас могут возникнуть сложности только с модулями, в таком случае идем на http://search.cpan.org/  и качаем необходимые модуля.

Обратите внимание все записанные файлы хранятся в директории /var/lib/asterisk/sounds/records в формате sln – при создании директории поменяйте правильно права, иначе работать не будет!

Вроде записать сообщение, записали, теперь нужно бы его прослушать.




Итак скрипт ./reminder_listen.agi

#!/usr/bin/perl
use Asterisk::AGI;
use POSIX;
use File::Copy;
use Time::Local;

$AGI = new Asterisk::AGI;
my %input = $AGI->ReadParse();
$count=0;
sub say_day {
$sayday=$_[0];
if ("$sayday" < 20) {
$AGI->exec('Playback',"digits/h-$sayday\\n");
} elsif ( 20 < "$sayday" and  "$sayday" < 30 ) {
$sayday=~/\d(\d)/;
$AGI->exec('Playback',"digits/20");
$AGI->exec('Playback',"digits/h-$1\\n");
} elsif ( "$sayday" == 20 ) {
$AGI->exec('Playback',"digits/h-20n");
} elsif ( "$sayday" == 30 ) {
$AGI->exec('Playback',"digits/h-30n");
} else {
$AGI->exec('Playback',"digits/30");
$AGI->exec('Playback',"digits/h-1\\n");
}
}

#Получаем от Asterisk данные о том, откуда поступило напоминание, имя звукового файла и дата когда была сделана запись.

$src = $AGI->get_variable('src');
$records = $AGI->get_variable('records');
$filename="/var/lib/asterisk/sounds/records/$records";
$date = $AGI->get_variable('date');

($sec1,$min1,$hour1,$mday1,$mon1,$year1,$wday1,$yday1,$isdst1)=localtime($date);
$mon_sound="mon-$mon1";
CICLE1:
# Начинаем грузить нашего абонента информацией откуда был сделан звонок, когда и что собственно от него хотели!
$AGI->exec('Playback',"ru11/vm-from-phonenumber");
$AGI->exec('SayDigits',"$src");

$AGI->exec('Playback',"digits/at");
$AGI->exec('Playback',"digits/day-$wday1");
&say_day($mday1);
$AGI->exec('Playback',"digits/$mon_sound");
$AGI->exec('Playback',"digits/at");
$AGI->exec('SayNumber',"$hour1");
$AGI->exec('Playback',"ru11/hours");
$AGI->exec('SayNumber',"$min1");
$AGI->exec('Playback',"ru11/minutes");

$AGI->exec('Playback',"records/$records");
# В конце концов ошарашенного таким напором абонента мы переспрашиваем, хочет ли он еще раз прослушать наше сообщение, если да то у него еще в запасе 4-ре раза прослушки, далее цикл прервется и система ему скажет goodbye, удалит файл и повесит трубку.
$AGI->exec('Read',"rep|ru11/vm-repeat|1||1|5");
$rep = $AGI->get_variable("rep");
if ( $rep eq "5" ){
$count++;
$AGI->exec('NoOp',"$count");
if ($count eq "3") {  $AGI->exec('Playback',"ru11/goodbye"); unlink($filename); exit 0; }
goto CICLE1;
}
unlink($filename);

exit 0;

Вот собственно и все. Как мог подробно изложил информацию. Конечно во многих местах кода можно более изящно написать, но данная система работает довольно стабильно, если спецом не вгонять ее в раковое положение!

 

вторая часть статьи, описывает xml сервис для телефонов cisco, позволяющий быстро и удобно для пользователя вводить значения даты, времени и номера.


При копировании данного материала, ссылка на сайт www.3090607.ru  – обязательна!




Обновлено 18.10.2010 23:00
 

Комментарии  

 
+1 #5 Администратор 03.04.2011 02:04
Я надеюсь, что тот, кто забрел на эту статью, и осилился ее дочитать, понимает в perl и подправит ошибки допущенные при публикации.
Цитировать
 
 
+1 #4 alex0127 02.04.2011 20:27
$AGI->exec('Read',"rep|ru11/vm-repeat|1||1|5"); теперь надо
$AGI->exec('Read',"rep,ru11/vm-repeat,1,1,5");

exten => go,n,HagnUp надо конечно Hangup

и еще где то в коментах в скрипте пропущен знак # перед запуском надо сделать проверку синтаксиса perl -w имя_скрипта
Цитировать
 
 
+5 #3 Администратор 15.10.2010 09:58
А если нет в папке указанных файлов, значит идем на http://3090607.ru/note/18-golos-menu и читаем что за файлы нам может предложить http://ivrvoice.ru/downloader
Не забудет, если вы указываете переменную окружения ru, значит вы должны создать папку ru в digits, sound etc... и туда скопировать наши голосовые файлики.
Цитировать
 
 
-2 #2 komil 15.10.2010 07:58
Цитирую bmfreeze:
а если нет в папке указанных файлов?

Мозги для чего?
Цитировать
 
 
+1 #1 bmfreeze 15.10.2010 07:43
а если нет в папке указанных файлов?
Цитировать
 

Добавить комментарий


IP Phone © 2010
Design by Coryphaeus
Тех поддержка: Aura ART Studio