| Russian-language alarm clock/reminders for Asterisk | [edit] |
В 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; Вот собственно и все. Как мог подробно изложил информацию. Конечно во многих местах код можно более изящно написать, но данная система работает довольно стабильно. |
|
ASTERISK Alarm Clock
Russian-speaking alarm clock/reminder for Asterisk [ fix ]

Note: This post may be AI-assisted and then human-edited. Editorial responsibility: Dmytro Lomaka, Kaltenkirchen.