Решил я, что получится весьма полезная возможность собираясь на работу послушать прогноз погоды на громкоговорителе телефона нажав одну запрограммированную кнопку. Как я это сделал смотрите под катом.
Первое что нам потребуется это сам прогноз погоды. Я решил его брать с сайта gismeteo.ru.
Как я это сделал:
Зашел на сайт gismeteo.ru нашел свой город.
Нажал в шапке ссылку «Информеры».
На открывшейся странице нашел текст «Данные в формате XML» и взял ссылку с кнопки «получить код».
Таким образом мы теперь знаем как нам получать свежий прогноз в формате XML.
Теперь нужно его сохранить для последующей обработки:
Велосипед изобретать я не стал и написал скрипт на bash из двух строк
Copy Source | Copy HTML
- #!/bin/bash
- cd /usr/isp/weather
- /usr/bin/wget 'http://informer.gismeteo.ru/xml/34214_1.xml' -O ./34214_1.xml
-
где informer.gismeteo.ru/xml/34214_1.xml и есть ссылка для вашего города. Команда wget скачивает наш XML и записывает его в фаил 34214_1.xml в текущем каталоге, он нам потребуется далее. Что бы прогноз был всегда свежим необходимо этот скрипт записать на выполнение в cron например вот так:
*/60 * * * * /usr/isp/weather/weather_up.sh
Алгоритм озвучивающий прогноз я решил сделать на Perl который взаимодействует с Asterisk через AGI. От нашего алгоритма требуется произвести разбор XML файла и вывести в stdout последовательность строк с командами для Asterisk на воспроизведение звуковых файлов примерно вот так:
Copy Source | Copy HTML
- EXEC Playback weather/Po_dannym_Gismeteo ""
- EXEC Playback weather/tod/dnem ""
- EXEC Playback weather/m/15ogo ""
- EXEC Playback weather/budet ""
- EXEC Playback weather/cloudiness/pasmurno ""
- EXEC Playback weather/precipitation/dozhd' ""<br/>
EXECPlayback weather/temp_vosduha_sostavit ""<br/>EXECPlayback weather/digits/20 ""<br/>EXECPlayback weather/digits/2 ""<br/>EXECPlayback weather/Do ""<br/>EXECPlayback weather/digits/20 ""<br/>EXECPlayback weather/gradusov_C ""<br/>EXECPlayback weather/Skorost'_vetra_sostavit ""
- EXEC Playback weather/digits/5 ""
- EXEC Playback weather/Do ""
- EXEC Playback weather/digits/3 ""
- EXEC Playback weather/metrov_v_sekundu ""
- EXEC Playback weather/Atmosfernoe_davlenie_sostavit ""
- EXEC Playback weather/digits/700 ""
- EXEC Playback weather/digits/40 ""
- EXEC Playback weather/digits/7 ""
- EXEC Playback weather/Do ""
- EXEC Playback weather/digits/700 ""
- EXEC Playback weather/digits/40 ""
- EXEC Playback weather/digits/5 ""
- EXEC Playback weather/milimetrov_rtutnogo_stolba ""
Как вы заметели звуковых записей придётся подготовить очень много, но ведь это раз и навсегда.
В моём варианте в некоторых записанных фразах присутствует слово «от». Например: Atmosfernoe_davlenie_sostavit читать как «Атмосферное давление составит от».
Собственно для разбора XML я использовал модуль XML::Simple он мне показался самым простым в использовании. Для замены XML тэгов на необходимые мне строки для озвучивания применил списки:
Copy Source | Copy HTML
- my $date_name = {
- 1 => ["EXEC Playback weather\/m\/1ogo \"\"\n"],
- 2 => ["EXEC Playback weather\/m\/2ogo \"\"\n"],
- 3 => ["EXEC Playback weather\/m\/3ogo \"\"\n"],
- .....
-
С алгоритмом для преобразования чисел в набор команд для воспроизведения цифр решил тоже не изобретать велосипед и взял готовый алгоритм преобразования денег в пропись (к сожалению уже не вспомню чей), заменил в нужных местах строки такие как "один" на "Playback weather\/digits\/1 \"\"\n" и алгоритм адаптирован (куча времени сэкономлена).
Думаю теперь всё будет понятно, пришло время посмотреть на скрипт:
Copy Source | Copy HTML
- #!/usr/bin/perl
-
- use strict;
- use XML::Simple;
- use Data::Dumper;
-
- my $date_name = {
- 1 => ["EXEC Playback weather\/m\/1ogo \"\"\n"],
- 2 => ["EXEC Playback weather\/m\/2ogo \"\"\n"],
- 3 => ["EXEC Playback weather\/m\/3ogo \"\"\n"],
- 4 => ["EXEC Playback weather\/m\/4ogo \"\"\n"],
- 5 => ["EXEC Playback weather\/m\/5ogo \"\"\n"],
- 6 => ["EXEC Playback weather\/m\/6ogo \"\"\n"],
- 7 => ["EXEC Playback weather\/m\/7ogo \"\"\n"],
- 8 => ["EXEC Playback weather\/m\/8ogo \"\"\n"],
- 9 => ["EXEC Playback weather\/m\/9ogo \"\"\n"],
- 10 => ["EXEC Playback weather\/m\/10ogo \"\"\n"],
- 11 => ["EXEC Playback weather\/m\/11ogo \"\"\n"],
- 12 => ["EXEC Playback weather\/m\/12ogo \"\"\n"],
- 13 => ["EXEC Playback weather\/m\/13ogo \"\"\n"],
- 14 => ["EXEC Playback weather\/m\/14ogo \"\"\n"],
- 15 => ["EXEC Playback weather\/m\/15ogo \"\"\n"],
- 16 => ["EXEC Playback weather\/m\/16ogo \"\"\n"],
- 17 => ["EXEC Playback weather\/m\/17ogo \"\"\n"],
- 18 => ["EXEC Playback weather\/m\/18ogo \"\"\n"],
- 19 => ["EXEC Playback weather\/m\/19ogo \"\"\n"],
- 20 => ["EXEC Playback weather\/m\/10ogo \"\"\n"],
- 21 => ["EXEC Playback weather\/m\/21ogo \"\"\n"],
- 22 => ["EXEC Playback weather\/m\/22ogo \"\"\n"],
- 23 => ["EXEC Playback weather\/m\/23ogo \"\"\n"],
- 24 => ["EXEC Playback weather\/m\/24ogo \"\"\n"],
- 25 => ["EXEC Playback weather\/m\/25ogo \"\"\n"],
- 26 => ["EXEC Playback weather\/m\/26ogo \"\"\n"],
- 27 => ["EXEC Playback weather\/m\/27ogo \"\"\n"],
- 28 => ["EXEC Playback weather\/m\/28ogo \"\"\n"],
- 29 => ["EXEC Playback weather\/m\/29ogo \"\"\n"],
- 30 => ["EXEC Playback weather\/m\/30ogo \"\"\n"],
- 31 => ["EXEC Playback weather\/m\/31ogo \"\"\n"]
- };
-
- my $hour_name = {
- 0 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 1 => ["EXEC Playback weather\/hour\/chas \"\"\n"],
- 2 => ["EXEC Playback weather\/hour\/chasa \"\"\n"],
- 3 => ["EXEC Playback weather\/hour\/chasa \"\"\n"],
- 4 => ["EXEC Playback weather\/hour\/chasa \"\"\n"],
- 5 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 6 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 7 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 8 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 9 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 10 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 11 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 12 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 13 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 14 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 15 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 16 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 17 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 18 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 19 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 20 => ["EXEC Playback weather\/hour\/chasov \"\"\n"],
- 21 => ["EXEC Playback weather\/hour\/chas \"\"\n"],
- 22 => ["EXEC Playback weather\/hour\/chasa \"\"\n"],
- 23 => ["EXEC Playback weather\/hour\/chasa \"\"\n"],
- };
-
- my $m_name = {
- 1 => ["EXEC Playback weather\/m\/yanvarya \"\"\n"],
- 2 => ["EXEC Playback weather\/m\/fevralya \"\"\n"],
- 3 => ["EXEC Playback weather\/m\/marta \"\"\n"],
- 4 => ["EXEC Playback weather\/m\/aprelya \"\"\n"],
- 5 => ["EXEC Playback weather\/m\/maya \"\"\n"],
- 6 => ["EXEC Playback weather\/m\/iunya \"\"\n"],
- 7 => ["EXEC Playback weather\/m\/iylya \"\"\n"],
- 8 => ["EXEC Playback weather\/m\/avgusta \"\"\n"],
- 9 => ["EXEC Playback weather\/m\/sentyabrya \"\"\n"],
- 10 => ["EXEC Playback weather\/m\/octyabya \"\"\n"],
- 11 => ["EXEC Playback weather\/m\/noyabrya \"\"\n"],
- 12 => ["EXEC Playback weather\/m\/decabrya \"\"\n"]
- };
-
- my $tod_name = {
- 0 => ["EXEC Playback weather\/tod\/noch'u \"\"\n"],
- 1 => ["EXEC Playback weather\/tod\/utrom \"\"\n"],
- 2 => ["EXEC Playback weather\/tod\/dnem \"\"\n"],
- 3 => ["EXEC Playback weather\/tod\/vecherom \"\"\n"]
- };
-
- my $cloudiness_name = {
- 0 => ["EXEC Playback weather\/cloudiness\/yasno \"\"\n"],
- 1 => ["EXEC Playback weather\/cloudiness\/malooblachno \"\"\n"],
- 2 => ["EXEC Playback weather\/cloudiness\/oblachno \"\"\n"],
- 3 => ["EXEC Playback weather\/cloudiness\/pasmurno \"\"\n"]
- };
-
- my $precipitation_name = {
- 4 => ["EXEC Playback weather\/precipitation\/dozhd' \"\"\n"],
- 5 => ["EXEC Playback weather\/precipitation\/liven' \"\"\n"],
- 6 => ["EXEC Playback weather\/precipitation\/sneg \"\"\n"],
- 7 => ["EXEC Playback weather\/precipitation\/sneg \"\"\n"],
- 8 => ["EXEC Playback weather\/precipitation\/groza \"\"\n"],
- 9 => [""],
- 10 => ["EXEC Playback weather\/precipitation\/bez_osadkov \"\"\n"]
- };
-
- my $wind_direction_name = {
- 0 => ["EXEC Playback weather\/wind_direction\/severnyi \"\"\n"],
- 1 => ["EXEC Playback weather\/wind_direction\/severo-vostochnyi \"\"\n"],
- 2 => ["EXEC Playback weather\/wind_direction\/vostochno-uzhnyi \"\"\n"],
- 3 => ["EXEC Playback weather\/wind_direction\/ugo-zapadnyi \"\"\n"]
- };
-
-
- my $xmlWeather = new XML::Simple(keeproot => 1,searchpath => ".", forcearray => 1, suppressempty => '');
- my $xmlData1 = $xmlWeather->XMLin('/usr/isp/weather/34214_1.xml');
- my $xmlData = $xmlData1->{MMWEATHER}[ 0]->{REPORT}[ 0]->{TOWN}[ 0]->{FORECAST};
-
- $| = 1;
- while( <STDIN> ) {
- chomp($_);
- last if length($_) == 0;
- }
-
- # "Po dannym Gismeteo"
- print "EXEC Playback weather\/Po_dannym_Gismeteo \"\"\n";
- my $i= 0;
- for ($i = 0; $i < 4; $i++)
- {
- print $tod_name->{$xmlData->[$i]->{tod}}->[ 0];
- print $date_name->{$xmlData->[$i]->{day}}->[ 0];
- print $m_name->{$xmlData->[$i]->{month}}->[ 0];
- #
- print "EXEC Playback weather\/budet \"\"\n";
- print $cloudiness_name->{$xmlData->[$i]->{PHENOMENA}[ 0]->{cloudiness}}->[ 0];
- print $precipitation_name->{$xmlData->[$i]->{PHENOMENA}[ 0]->{precipitation}}->[ 0];
- # Temp
- print "EXEC Playback weather\/temp_vosduha_sostavit \"\"\n";
- print digit_string( $xmlData->[$i]->{TEMPERATURE}[ 0]->{max} );
- print "EXEC Playback weather\/Do \"\"\n";
- print digit_string( $xmlData->[$i]->{TEMPERATURE}[ 0]->{min} );
- print "EXEC Playback weather\/gradusov_C \"\"\n";
- # Veter
- print "EXEC Playback weather\/Skorost'_vetra_sostavit \"\"\n";
- print digit_string( $xmlData->[$i]->{WIND}[ 0]->{max} );
- print "EXEC Playback weather\/Do \"\"\n";
- print digit_string( $xmlData->[$i]->{WIND}[ 0]->{min} );
- print "EXEC Playback weather\/metrov_v_sekundu \"\"\n";
- # Atmosfernoe davlenie
- print "EXEC Playback weather\/Atmosfernoe_davlenie_sostavit \"\"\n";
- print digit_string( $xmlData->[$i]->{PRESSURE}[ 0]->{max} );
- print "EXEC Playback weather\/Do \"\"\n";
- print digit_string( $xmlData->[$i]->{PRESSURE}[ 0]->{min} );
- print "EXEC Playback weather\/milimetrov_rtutnogo_stolba \"\"\n";
-
- }
-
-
- #===================================================================================================
- #===================================================================================================
- #===================================================================================================
- sub digit_string {
- my $digit = shift;
- local $_;
-
- my $sign = 1 if $digit =~ s/^-+//;
-
-
- $digit =~ s#^0+##;
- my ( $b_dig, $s_dig ) = ( split( m/[,.]/, $digit, 2 ) );
- $s_dig ="";
- #
- # Очищаем числа от `лишних' символов ( 100_000,43 )
- #
- if ( defined $b_dig and length $b_dig ) {
- # $b_dig =~ s#[^\d]##sg;
- } else {
- $b_dig = "";
- }
- if ( defined $s_dig and length $s_dig ) {
- # $s_dig =~ s#[^\d]##sg;
- } else {
- $s_dig = "";
- }
- #
- # Округляем копейки в большую сторону, если в результате округления
- # получаем рубль, то приплюсовываем его к b_dig ( рублям )
- #
- # if ( sprintf('%0.2f', "0.$s_dig" ) == 1 ) {
- # $b_dig ++;
- # $s_dig = '00';
- # } else {
- # $s_dig = substr( sprintf('%0.2f', "0.$s_dig" ), 2 );
- # }
- #
- my @array = split( //, ( $b_dig || 0 ) );
-
- #
- # Определяем разрядность числа.
- #
- my $class_id = int( scalar ( @array ) / 3);
- $class_id++ if ( scalar ( @array ) % 3 );
- #
- # Неподдерживаемая разрядность.
- #
- return $digit if $class_id > 5;
-
- my $digits_name = {
- 0 => ["EXEC Playback weather\/digits\/0 \"\"\n","",""],
- 1 => [["EXEC Playback weather\/digits\/1 \"\"\n","EXEC Playback weather\/digits\/1a \"\"\n"],"EXEC Playback weather\/digits\/10 \"\"\n","EXEC Playback weather\/digits\/100 \"\"\n"],
- 2 => [["EXEC Playback weather\/digits\/2 \"\"\n","EXEC Playback weather\/digits\/2e \"\"\n"],"EXEC Playback weather\/digits\/20 \"\"\n","EXEC Playback weather\/digits\/200 \"\"\n"],
- 3 => ["EXEC Playback weather\/digits\/3 \"\"\n","EXEC Playback weather\/digits\/30 \"\"\n","EXEC Playback weather\/digits\/300 \"\"\n"],
- 4 => ["EXEC Playback weather\/digits\/4 \"\"\n","EXEC Playback weather\/digits\/40 \"\"\n","EXEC Playback weather\/digits\/400 \"\"\n"],
- 5 => ["EXEC Playback weather\/digits\/5 \"\"\n","EXEC Playback weather\/digits\/50 \"\"\n","EXEC Playback weather\/digits\/500 \"\"\n"],
- 6 => ["EXEC Playback weather\/digits\/6 \"\"\n","EXEC Playback weather\/digits\/60 \"\"\n","EXEC Playback weather\/digits\/600 \"\"\n"],
- 7 => ["EXEC Playback weather\/digits\/7 \"\"\n","EXEC Playback weather\/digits\/70 \"\"\n","EXEC Playback weather\/digits\/700 \"\"\n"],
- 8 => ["EXEC Playback weather\/digits\/8 \"\"\n","EXEC Playback weather\/digits\/80 \"\"\n","EXEC Playback weather\/digits\/800 \"\"\n"],
- 9 => ["EXEC Playback weather\/digits\/9 \"\"\n","EXEC Playback weather\/digits\/90 \"\"\n","EXEC Playback weather\/digits\/900 \"\"\n"],
- };
-
- my $dec_digits = {
- 11 => "EXEC Playback weather\/digits\/11 \"\"\n",
- 12 => "EXEC Playback weather\/digits\/12 \"\"\n",
- 13 => "EXEC Playback weather\/digits\/13 \"\"\n",
- 14 => "EXEC Playback weather\/digits\/14 \"\"\n",
- 15 => "EXEC Playback weather\/digits\/15 \"\"\n",
- 16 => "EXEC Playback weather\/digits\/16 \"\"\n",
- 17 => "EXEC Playback weather\/digits\/17 \"\"\n",
- 18 => "EXEC Playback weather\/digits\/18 \"\"\n",
- 19 => "EXEC Playback weather\/digits\/19 \"\"\n"
- };
-
- my $digits_class = {
- '-1'=> [ 1,"","",""],
- 0 => [ 0,"","",""],
- 1 => [ 1, "EXEC Playback weather\/digits\/tysiacha \"\"\n","EXEC Playback weather\/digits\/tysiach \"\"\n","EXEC Playback weather\/digits\/tysiachi \"\"\n" ],
- 2 => [ 0, "EXEC Playback weather\/digits\/million \"\"\n","EXEC Playback weather\/digits\/millionov \"\"\n","EXEC Playback weather\/digits\/milliona \"\"\n" ],
- 3 => [ 0, "EXEC Playback weather\/digits\/milliard \"\"\n","EXEC Playback weather\/digits\/milliardov \"\"\n","EXEC Playback weather\/digits\/milliarda \"\"\n" ],
- 4 => [ 0, "EXEC Playback weather\/digits\/trillion \"\"\n","EXEC Playback weather\/digits\/trillionov \"\"\n","EXEC Playback weather\/digits\/trilliona \"\"\n"],
- };
- #
- # Определяем длину левой `тройки'...
- #
- my $id = 0;
- unless ( ( scalar @array ) % 3 ) {
- $id = 2;
- } else {
- $id = ( ( scalar @array ) % 3 ) - 1;
- }
-
- my $str = '';
- my $sub_str = 0;
- my $tvar = 0;
-
- if ( $array[ 0] == 0 ) {
- #
- # Если рублей таки ноль, то так и пишем 'ноль рублей',
- # если не надо то просто закомнтировать следующую строку...
- #
- $str .= $digits_name->{'0'}->[ 0] . ' ' . $digits_class->{'0'}->[2] . ' ';
- } else {
- while ( defined ( $_ = shift @array ) ) {
- if ( $_ > 0 ) {
- if ( $_ == 1 and $id == 1 ) {
- #
- # Считаем сумму для использования в sub num()
- #
- $sub_str += $_ * 10;
- if ( defined ( $tvar = shift @array ) and $tvar > 0 ) {
- $str .= $dec_digits->{ $_ . $tvar };
- $sub_str += $tvar;
- $id--;
- } else {
- unshift @array, $tvar;
- if ( ref $digits_name->{$_}->[$id] eq 'ARRAY' ) {
- # $str .= $digits_name->{$_}->[$id]->[$digits_class->{$class_id-1}->[0]];
- } else {
- $str .= $digits_name->{$_}->[$id];
- }
- }
- } else {
- #
- # Считаем сумму для использования в sub num()
- #
- $sub_str += ( $_ * ( 10 ** $id ) );
- if ( ref $digits_name->{$_}->[$id] eq 'ARRAY' ) {
- $str .= $digits_name->{$_}->[$id]->[$digits_class->{$class_id-1}->[ 0]];
- } else {
- $str .= $digits_name->{$_}->[$id];
- }
- }
- $str .= ' ';
- }
- if ( --$id == -1 ) {
- $id = 2;
- $class_id--;
- if ( $sub_str > 0 ) {
- $str .= num( $sub_str, ( @{ $digits_class->{$class_id} } )[ 1 .. 3 ] ) . ' ';
- } elsif ( $class_id == 0 ) {
- $str .= $digits_class->{$class_id}->[2] . ' ';
- }
- $sub_str = 0;
- }
- }
- }
-
- $str .= $s_dig . ' ' . num( $s_dig, ( @{ $digits_class->{ '-1' } } )[ 1 .. 3 ] );
-
- if ( defined $sign ) {
- $str = "EXEC Playback weather\/digits/minus \"\"\n" . $str;
- }
-
- $s_dig = substr( $str, 0, 1 );
- #
- # В том случае где оно пользовалось оказалось проще
- # так, чем через use locale & ucfirst...
- #
- if ( $s_dig =~ tr/абвгдеёжзийклмнопрстуфхцчшщъыьэюя/АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ/ ) {
- substr( $str, 0, 1, $s_dig );
- }
-
- return $str;
- }
-
- #
- # Используется при переводе денежного числа в строку прописью..
- #
-
- sub num {
- my $d1 = $_[ 0] % 10;
- my $d2 = int( ( $_[ 0] % 100 ) / 10 );
- return $_[2] if ( ( $d2 == 1 ) or ( $d1 =~ /[05-9]/ ) );
- return $_[1] if ( $d1==1 );
- return $_[3];
- }
-
Далее нам необходимо научить Asterisk пользваться этим скриптом. Для этого в файле extensions.conf в нужном контексте прописываем вот так:
Copy Source | Copy HTML
- exten => XXXX,1,Wait(1)
- exten => XXXX,n,Answer()
- exten => XXXX,n,Agi(/usr/isp/weather/weather.pl)
- exten => XXXX,n,Hangup()
-