В 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;
Вот собственно и все. Как мог подробно изложил информацию. Конечно во многих местах код можно более изящно написать, но данная система работает довольно стабильно.
URL: http://3090607.ru/note/28-create-wakeup
Обсуждается: http://www.opennet.dev/tips/info/2472.shtml