Вариация на тему: прогноз погоды по телефону

    Решил поделиться ещё одним способом организации сервиса прогноза погоды по телефону. Здесь, по сравнению с этим постом, больше интеллекта перенесено в Asterisk.
    Weather


    XML с погодой


    XML с текущей погодой и прогнозом на два следующих дня беру с BBC.

    Так как от Cron'а нам не избавиться, то добавляем:
    0 2 * * * /home/alexandr/xml/weather/almaty/bbcweather.sh
    0 14 * * * /home/alexandr/xml/weather/almaty/bbcweather.sh
    30 16 * * * /home/alexandr/xml/weather/almaty/bbcweather.sh

    Опытным путем было установлено, что XML обновляется три раза в сутки (по крайней мере для города Алматы).

    Скрипт не только скачивает, но также обрабатывает XML-ку и раскладывает необходимые значения по файликам. Правда мой скрипт очень примитивный и скорее всего многих введёт в уныние своей неэффективностью, поэтому не буду заострять на нём внимание.

    Текстовые файлы


    Имена файлов для одного и того же параметра (например, минимальная температура) для разных дней будут отличаться цифрой-префиксом: 1 – сегодня, 2 и 3 – завтра и послезавтра соответственно. Например, минимальная температура для сегодняшнего дня (первый блок данных в XML) будет храниться в файле с именем «1Tmin», для завтрашнего (второй блок данных) – «2Tmin».

    Теперь по содержимому файлов (которые, кстати, не должны содержать символа конца строки):
    • День недели (Wday) содержит «day-0» для воскресенья,… «day-6» для субботы;
    • Направление ветра (Wdir) содержит «N» для «северный»,… «NW» для «северо-западный»;
    • Числовые значения (влажность, температура, скорость ветра) записываются числом;
    • Текстовые – текстом :-) (облачность, видимость).

    Для тестирования (а также для написания скрипта) пользовался параметром "-n" команды «echo» для того, чтобы не записывать в файл символ конца строки:
    echo -n "day-5" > 1Wday

    Итак, имеем список файликов с погодными данными. Теперь переходим к конфигурации extensions.conf.

    extensions.conf


    [weather]
    exten => 071,1,Set(wpath="/home/your-directory/")
    exten => 071,n,Goto(say-weather,s,1)
    
    [say-weather]
    exten => s,1,Answer()
    exten => s,n,Set(E=1)
    exten => s,n(play),Playback(digits/${SHELL(cat ${wpath}${E}Wday)})
    exten => s,n,Playback(custom/temperatura)
    exten => s,n,SayNumber(${SHELL(cat ${wpath}${E}Tmin)})
    exten => s,n,SayNumber(${SHELL(cat ${wpath}${E}Tmax)})
    exten => s,n,Playback(custom/wind)
    exten => s,n,Playback(custom/wind/${SHELL(cat ${wpath}${E}Wdir)})
    exten => s,n,SayNumber(${SHELL(cat ${wpath}${E}Wspd)})
    exten => s,n,Playback(custom/metrov-v-sekundu)
    exten => s,n,Playback(custom/vlazhnost)
    exten => s,n,SayNumber(${SHELL(cat ${wpath}${E}Humd)})
    exten => s,n,Playback(custom/procentov)
    
    exten => s,n,WaitExten()
    exten => s,n,Hangup()
    exten => _[1-3],1,Set(E=${EXTEN})
    exten => _[1-3],n,Goto(say-weather,s,play)
    


    Разбор полётов

    Публикуем extension, по которому будем дозваниваться до погоды и, заодно, выставляем переменную «wpath», где указываем каталог ранее созданных файликов:
    exten => 071,1,Set(wpath="/home/your-directory/")

    Переходим в контекст say-weather:
    exten => 071,n,Goto(say-weather,s,1)
    

    После инициализации значения «E» (по умолчанию выставляем сегодняшний день), воспроизводим название дня недели:
    exten => s,n,Set(E=1)
    exten => s,n(play),Playback(digits/${SHELL(cat ${wpath}${E}Wday)})
    

    Команду Playback можно и не представлять, а вот дальше на всякий случай расскажу. Если указывается относительный путь, то по умолчанию (во всяком случаем на моём debian) подставляется "/usr/share/asterisk/sounds/en/" (или «ru/», если в sip.conf указан параметр «language=ru»). Значения «wpath» и «E» мы уже определили, поэтому получается следующее:
    Playback(/usr/share/asterisk/sounds/ru/digits/${SHELL(cat /home/your-directory/1Wday)})

    Результатом выполнения «cat /home/your-directory/1Wday» пусть будет, например, строка «day-0» (допустим, что сегодня – воскресенье), тогда:
    Playback(/usr/share/asterisk/sounds/ru/digits/day-0)

    Т.е. воспроизводится файл, содержащий название нужного нам дня недели, т.к. файл digits/day-0 как раз и содержит звуковую запись «Воскресенье».

    По такому же принципу отрабатывает и эта строчка:
    SayNumber(${SHELL(cat ${wpath}${E}Tmin)})
    

    Проговаривается минимальная температура сегодняшнего дня, значение которой берётся из файла "/home/your-directory/1Tmin".

    Увы, пришлось записывать часть сообщений самостоятельно, их я поместил в каталог «custom».
    exten => s,n,Playback(custom/temperatura)

    После окончания всех Playback и SayNumber, ждем ввода дополнительных цифр:
    exten => s,n,WaitExten()
    

    Дополнительные цифры включают в себя 1, 2 и 3, что видно по паттерну _[1-3]:
    exten => _[1-3],1,Set(E=${EXTEN})
    exten => _[1-3],n,Goto(say-weather,s,play)
    

    При нажатии на любую из них, первая строчка присваивает введённое значение переменной «E», а вторая строчка осуществляет переход к метке «play» (третья строка этого контекста). Таким образом Asterisk снова сообщит звонящему погоду, но уже на завтра (если была введена цифра 2) или послезавтра (3).

    Ничто не мешает нам написать ещё один extension рядом с 071 и воспользоваться тем же самым контекстом say-weather для воспроизведения погоды для другого города:
    [weather]
    exten => 071,1,Set(wpath="/home/user/weather/city1/files")
    exten => 071,n,Goto(say-weather,s,1)
    exten => 072,1,Set(wpath="/home/user/weather/city2/files")
    exten => 072,n,Goto(say-weather,s,1)
    


    Откуда качал архив с русскими сообщениями уже не вспомню, но он должен легко нагуглиться.
    Подробные описания функций Asterisk на www.voip-info.org.

    Бонус


    В качестве бонуса выкладываю часть extensions.conf, с помощью которой я записывал свои звуковые сообщения:
    [test-context]
    exten => 051,1,Goto(rec-file,s,1)
    [rec-file]
    exten => s,1,Verbose(1,Recording application)
    exten => s,n,Answer()
    exten => s,n,Playback(record-enter-num)
    exten => s,n(filename),Read(filename)
    exten => s,n(rec-mes),Record(/tmp/recs/${filename}:gsm)
    exten => s,n,Playback(beep)
    exten => s,n,Wait(1)
    exten => s,n(rec-play),Playback(/tmp/recs/${filename})
    exten => s,n(rec-review),Background(vm-review)
    exten => s,n,WaitExten(10)
    exten => s,n(rec-del),System(rm /tmp/recs/${filename}.gsm)
    exten => s,n,Playback(vm-deleted)
    exten => s,n(read-hang),Read(rep,,1,,,2);Wait for 1 digit for 2 seconds to receive 1 which means that another file is to be recorded
    exten => s,n,Gotoif($["${rep}" = "1"]?filename)
    exten => s,n,Hangup()
    
    exten => 1,1,Playback(vm-msgsaved)
    exten => 1,n,SayDigits(${filename})
    exten => 1,n,Goto(rec-file,s,read-hang)
    
    exten => 2,1,Goto(rec-file,s,rec-play)
    
    exten => 3,1,Wait(1)
    exten => 3,n,Goto(rec-file,s,rec-mes)
    
    exten => i,1,Goto(rec-file,s,rec-review)
    exten => t,1,Goto(rec-file,s,rec-del)
    exten => 0,1,Goto(rec-file,s,rec-del)
    

    Готовим табличку со списком имен файлов (в виде цифр) и текстом, который нужно будет проговорить, звоним с более-менее хорошего телефона/гарнитуры на 051, «прогоняем» каждую строчку своей таблички и в конце забираем готовые файлики из /tmp/recs/ (не забывая менять их названия на что-то более понятное).

    И подсказка для того, чтобы легче было пользоваться:

    Recording Menu


    UPD: Ссылка на bbcweather.sh: pastebin.com/DJYy6XRM и sed.bbc: pastebin.com/Xm4EeDst. Если кто будет делать с помощью нормального XML-парсера – не забудьте поделиться кодом :-).

    UPD2: Выкладываю переписанный скрипт (на этот раз на Perl) и выдержку из extensions.conf:

    weather.sh


    #!/usr/bin/perl
      use XML::LibXML;
    my $d = "/home/alexandr/xml/perl/bbc/moscow/";          # Working directory
    my $f = "files/";                                       # Directory with weather files
    my $wget = "wget -qO - http://newsrss.bbc.co.uk/weather/forecast/58/Next3DaysRSS.xml > ";
    my $filename = "bbc.xml";                               # XML-file to process
    my $i = 3;                                              # Counter: try to download XML-file $i time(s)
    my $wait = 2;                                           # Wait for $wait second(s) between downloads
    my $log = "/var/log/bbc_weather_moscow.log";            # Log-file :-) (used for logging failed download attempts)
    my %days = ( "Sunday" => "day-0", "Monday" => "day-1", "Tuesday" => "day-2", "Wednesday" => "day-3", "Thursday" => "day-4", "Friday" => "day-5", "Saturday" => "day-6");
    
    sub t2f{                                                # Subroutine for writing text to file
      my($text, $file, $app) = @_;                          # Read parameters $text, $file (add non-zero $app to append, but not rewrite file)
      if ($app) { open (OUT_FILE, '>>', $file); }
      else      { open (OUT_FILE, '>',  $file); }
      print OUT_FILE "$text";
      close (OUT_FILE);
    }
    
    sub noun{                                               # Choose noun for number
      my($number, $form1, $form2, $form5) = @_;             # Usage: &noun(1, градус, градуса, градусов);
      my $n10  = abs($number) % 10;
      my $n100 = abs($number) % 100;
      if ( $n100 > 4 && $n100 < 21 || $n10 == 0 || $n10 > 4 ) { return $form5 };
      if ( $n10  > 1 && $n10  < 5  ) { return $form2 };
      if ( $n10 == 1 ) { return $form1 };
      return $form5;
    }
    
    chdir $d;                                               # Go to working directory
    DOWNLOAD:                                               # Label for 'Goto' (will be used in case XML has not been updated)
    $i -= 1;
    #print("Downloading...\n");
    system($wget.$filename);
    my $parser = XML::LibXML->new();
    my $doc    = $parser->parse_file($filename);            # Parse this file ($filename)
    
    #check if XML has been updated
    open(FILE, 'date.txt');
    my $date = <FILE>;
    close(FILE);
    my @xmldate = $doc->findnodes('//pubDate');
    if ( $date eq @xmldate[0]->to_literal ) {
      my $t = localtime;
      if ($i) { &t2f("$t: XML has not been updated, I will try $i more ".&noun($i,"time","times","times")."\n",$log,1); }
      else    { &t2f("$t: XML has not been updated, I will not try any more\n",$log,1);
        exit;                                               # No more tries left -> Finish myself!
      }
      sleep $wait;
      goto DOWNLOAD;                                        # Tried to do with 'While' or "Do, while', but variables should be initialised outside loop, decided to use 'Goto'
    }
    else {
      &t2f(@xmldate[0]->to_literal,"date.txt");
    }
    
    #searching for //item/title and //item/description values
    $i = 0;
    foreach my $item ($doc->findnodes('//item')) {          # for each //item
      $i += 1;  # BTW, we should be counting from 1 to 3 (today, tomorrow and day after tomorrow)
      my $title = $item->findnodes('./title');      $title = $title->to_literal;    #take ./title and convert to string
      my $desc  = $item->findnodes('./description'); $desc = $desc->to_literal;     #take ./description and convert to string
    
      chdir $d.$f;                                                          # Go to directory with weather files
      if ( $title =~ m/^(\w+)/ ) {                                          # Match first word (weekday)
           &t2f($days{"$1"},$i."Wday"); }                                   # Export weekday in format day-0, day-1, etc
      if ( $title =~ m/: (\w+(\s\w+){0,}),/ ) {                             # Match one or more words, delimited with spaces (matched pattern should be followed by comma)
           &t2f("$1",$i."Prec"); }
    
      if ( $desc =~ m/Max Temp: (-{0,1}\d+)/ ) {
           &t2f("$1",$i."Tmax");                                            # Export Tmax (Max Temperature)
           &t2f(&noun("$1","gradus","gradusa","gradusov"),$i."DMax"); }     # Export Dmax (right form of "градус" for Tmax)
    
      if ( $desc =~ m/Min Temp: (-{0,1}\d+)/ ) {
           &t2f("$1",$i."Tmin");                                            # Export Tmin (Min Temperature)
           &t2f(&noun("$1","gradus","gradusa","gradusov"),$i."DMin"); }
    
      if ( $desc =~ m/Wind Direction: (\w+)/ ) {                            # Export Wdir (Wind Direction)
           &t2f("$1",$i."Wdir"); }
    
      if ( $desc =~ m/Wind Speed: (\d+)/ ) {
          &t2f(int("$1"*0.44704+0.5),$i."Wspd");                            # Export Wspd (mph*0.44704=kmh), int(kmh+0.5) - rounding to nearest integer
          &t2f(&noun("$1"*0.44704+0.5,"m_s","ma_s","mov_s"),$i."Wsms"); }           # Метр(а,ов) в секунду
    
      if ( $desc =~ m/Visibility: (\w+)/ ) {
          &t2f("$1",$i."Visb"); }                                           # Export Visb (Visibility)
    
      if ( $desc =~ m/Pressure: (\d+)/ ) {
          &t2f(int("$1"*0.75),$i."Pres");                                   # Export Pres (Pressure) (mb*0.75=mmHg)
          &t2f(&noun("$1","mm_hg","mma_hg","mmov_hg"),$i."Prmm"); }         # Миллиметр(а,ов) ртутного столба
    
      if ( $desc =~ m/Humidity: (\d+)/ ) {
          &t2f(int("$1"),$i."Humd");                                        # Export Humd (Humidity)
          &t2f(&noun("$1","procent","procenta","procentov"),$i."Hper"); }
    }
    


    extensions.conf


    exten => 072,1,Set(wpath="/home/alexandr/xml/perl/bbc/moscow/files/")
    exten => 072,n,System(echo "${STRFTIME(${EPOCH},,%Y.%m.%d %H:%M:%S)}\t${CALLERID(name)}\t${CALLERID(num)}" >> /var/log/calls_to_moscow_weather.log)
    exten => 072,n,Goto(say-weather-perl,s,1)

    [say-weather-perl]
    exten => s,1,Answer()
    exten => s,n,Set(E=1)
    exten => s,n,Set(CHANNEL(language)=alex_01) ; By default use directory: /usr/share/asterisk/sounds/alex_01/
    exten => s,n(play),Background(digits/${SHELL(cat ${wpath}${E}Wday)}) ; Weekday
    exten => s,n,Background(weather/${SHELL(cat ${wpath}${E}Prec)}) ; Precipitation
    exten => s,n,Background(weather/temperatura) ; Precipitation
    exten => s,n,SayNumber(${SHELL(cat ${wpath}${E}Tmin)}) ; Minimum temperature
    exten => s,n,SayNumber(${SHELL(cat ${wpath}${E}Tmax)}) ; Maximum temperature
    exten => s,n,Background(weather/${SHELL(cat ${wpath}${E}DMax)}) ; Maximum degree(s)
    exten => s,n,Background(weather/veter) ; Wind
    exten => s,n,Background(weather/${SHELL(cat ${wpath}${E}Wdir)}) ; Wind Direction
    exten => s,n,SayNumber(${SHELL(cat ${wpath}${E}Wspd)}) ; Wind speed
    exten => s,n,Background(weather/${SHELL(cat ${wpath}${E}Wsms)}) ; Meter(s) per seconD
    exten => s,n,Background(weather/vlazhnost) ; Humidity
    exten => s,n,SayNumber(${SHELL(cat ${wpath}${E}Humd)}) ; Humidity percentage
    exten => s,n,Background(weather/${SHELL(cat ${wpath}${E}Hper)}) ; Percent(s)

    exten => s,n,WaitExten()
    exten => s,n,Hangup()
    exten => _[1-3],1,Set(E=${EXTEN})
    exten => _[1-3],n,Goto(say-weather-perl,s,play)

    UPD3: Предоставляю вниманию хабрасообщества бесплатный сервис прогноза погоды по телефону для города Москва. Прогноз погоды доступен по телефону +7 (499) 753-00-09. Данный номер любезно предоставлен компанией Док.Телеком, которой выражается благодарность за оказание помощи в предоставлении данной услуги всем желающим. Спасибо :-)!
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 15

      0
      Спасибо, поэкспериментирую на досуге ))
      ЗЫ. Можно всё-таки увидеть содержимое файла bbcweather.sh?
        +1
        Выложил и bbcweather.sh и sed.bbc
        Каталоги в статье я немного изменял, а сейчас выложил без изменений, но думаю всё должно быть понятно.
        0
        Нет ли у кого такого готового сервиса для Москвы, чтобы позвонить «снаружи» по SIP? Своего астериска нет :(
          0
          Вот здесь пишут, что 100 – прогноз погоды в Москве, но у меня инвайты остаются без ответа… Хотя регистрация на Sipnet проходит нормально…
            0
            sip:100@sipnet.ru не устроит?
              0
              Прикольно :-), раньше 100 не набиралось, теперь рассказывает о курсе валют.
              Вот здесь пишут, что набрав 0606051 из sipnet.ru, можно узнать погоду в Москве, а набрав 0606192, можно узнать курс валют. А на самом деле – всё с точностью до наоборот :).
            +2
            А почему не сделать
            30 2,14,16 * * * /home/alexandr/xml/weather/almaty/bbcweather.sh
            ?
              +1
              Не знал :-)… Спасибо!
              0
              Здорово, что все чаще и чаще появляются проекты интеграции IP-телефонии с различными сторонними сервисами. На мой взгляд, очень перспективная вещь с точки зрения оптимизации времени на обработку информации. Мы делали подобную интеграцию для Интернет-магазина с базой данных товаров, когда по вводу артикула через ext можно было узнать наличие товара и сделать заказ без ожидания в очереди оператора call-центра.

              А Ваш астериск наружу смотрит или только для своих внутренних целей используете?
                0
                С точки зрения задумки/реализации – очень здОрово :-) (про артикул и наличие товара), но с точки зрения юзабилити… Есть статистика по использованию именно дозвона с донабором артикула и пр.? Артикул ведь просто так не вспомнишь, а если смотреть в Интернет-магазине, то по-моему гораздо удобнее там же и сделать заказ…

                Asterisk использую для тестов и для личных целей. Поставил шлюз с FXS-портом родителям в другом городе, говорим по SIP, а FXO к городской линии подключил, чтобы в тот город по SIP звонить. Для доступа снаружи сделал проброс портов и DynDNS (т.к. динамичейский IP), и надо бы ещё fail2ban поставить :-).
                  0
                  Дело в том, что не все интернет-магазины делают онлайн-выгрузку о наличии на сайт. Да, покупателю требуется зайти на сайт и посмотреть артикул там — это факт. Мы делали такую реализацию для интернет-магазина шин. В «горячий» сезон (осенью и весной) сильно снижает нагрузку на офис.

                  Про наружу я спросил с целью, что готов Вам выделить бесплатно прямой московский номер для входящих звонков, чтобы протестировать Вашу систему и дать возможность сообществу получать оперативную сводку погоды для Московского региона с любого стационарного или мобильного номера :)
                    0
                    Надо же :-). Без проблем устрою. Правда нужно будет решить вопрос с озвучкой (у меня часть произносится «встроенной» девушкой, а часть моим не самым приятным голосом :-) ) и склонением (градус(а/ов) – уже есть задумка).
                    В профайле есть и ICQ, и Skype (не всегда онлайн).
                      0
                      Написал в личные сообщения. Давайте попробуем подключить.
                  0
                  UPD2: Выкладываю переписанный скрипт (на этот раз на Perl) и выдержку из extensions.conf

                  Кстати, еще натолкнулся на такой вариант: с помощью sox «склеиваем» все отдельный короткие файлы в один большой и проигрываем его с помощью Background (сейчас есть проблема в том, что при проигрывании чисел с помощью SayNumber нельзя перейти к другому дню, т.к. донабор при этом не используется).
                  Но не стал использовать, т.к. пришлось бы самому релизовывать прогрывание чисел :).
                    0
                    UPD3: Теперь Вы можете узнать прогноз погоды для Москвы по телефону: +7 (499) 753-00-09.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое