рдпреВрдПрд╕рдмреА рдореЙрдбреЗрдо Huawei e1550 рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдкрд░реНрд▓ рдкрд░ рд╡реЙрдпрд╕ рдореЗрдиреВ рдХрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди

рд╣рд╛рд▓ рд╣реА рдореЗрдВ, рдореИрдВрдиреЗ рдПрдХ рдкреЛрд╕реНрдЯ рд▓рд┐рдЦреА рдЬрд┐рд╕рдореЗрдВ рдореИрдВрдиреЗ рдереЛрдбрд╝рд╛ рд╕рд┐рджреНрдзрд╛рдВрдд рджрд┐рдпрд╛, рдФрд░ рдПрдХ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЗ рд╡реНрдпрд╛рд╡рд╣рд╛рд░рд┐рдХ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХрд╛ рд╡рд░реНрдгрди рдХрд┐рдпрд╛ рдЬреЛ рд╣реБрдВрдбрдИ e1550 рдпреВрдПрд╕рдмреА рдореЙрдбреЗрдо рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рд╕реВрдЪреА рдореЗрдВ рдЧреНрд░рд╛рд╣рдХреЛрдВ рдХреА рд╡реЙрдпрд╕ рдХреЙрд▓ (рдЕрд▓рд░реНрдЯ) рдХрд░рддрд╛ рд╣реИред рдЯрд┐рдкреНрдкрдгрд┐рдпреЛрдВ рдореЗрдВ рд╕реЗ рдПрдХ рдореЗрдВ, рдПрдХ рд╕рд╡рд╛рд▓ рдпрд╣ рдкреВрдЫрд╛ рдЧрдпрд╛ рдерд╛ рдХрд┐ рдПрдХ рд╡реЙрдпрд╕ рдХрдиреЗрдХреНрд╢рди рдХреЗ рджреМрд░рд╛рди рд╕рдмреНрд╕рдХреНрд░рд╛рдЗрдмрд░ рдХреЗ рдлреЛрди рдкрд░ рдмрдЯрди рджрдмрд╛рдиреЗ рдкрд░ рдбреЗрдЯрд╛ рдХреИрд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВред рдЗрд╕ рдореБрджреНрджреЗ рдХреЗ рд╡рд┐рд╕реНрддреГрдд рдЕрдзреНрдпрдпрди рдХреЗ рдХрд╛рд░рдг рдЗрд╕ рдкрдж рдХрд╛ рдирд┐рд░реНрдорд╛рдг рд╣реБрдЖред

рдпрд╣ рд▓реЗрдЦ рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рд╕рд╛рде рд╡реЙрдпрд╕ рдореЗрдиреВ рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЛ рдкреНрд░рд╕реНрддреБрдд рдХрд░реЗрдЧрд╛:
- рд╡реЙрдпрд╕ рдореИрд╕реЗрдЬ рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ
- рд╕рд┐рд╕реНрдЯрдо рдХрдорд╛рдВрдб рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдирд╛
рдпрд╣ рд╕рдм Goertzel рдХреЗ рдПрд▓реНрдЧреЛрд░рд┐рдереНрдо рдкрд░ рдЖрдзрд╛рд░рд┐рдд рд╕рдВрдХреЗрддреЛрдВ рдХреЗ DTMF рдбрд┐рдХреЛрдбрд░ рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд▓рд┐рдП рд╕рдВрднрд╡ рд╣реЛ рдЧрдпрд╛ред
рдПрдХ рдмреЛрдирд╕ рдХреЗ рд░реВрдк рдореЗрдВ - рдЖрд╡рд╛рдЬ рдореЗрдиреВ рд╕реНрд░реЛрддреЛрдВ рдХрд╛ рдПрдХ рд╕рдВрдЧреНрд░рд╣ред


рд╡рд┐рдХрд╛рд╕ рдХрд╛ рд╡рд╛рддрд╛рд╡рд░рдг


рдСрдкрд░реЗрдЯрд┐рдВрдЧ рд╕рд┐рд╕реНрдЯрдо: рд▓рд┐рдирдХреНрд╕
рд╡рд┐рддрд░рдг: OpenSUSE 12.3
рдХрд░реНрдиреЗрд▓: рей.рей.резреж-рез.резрем-рдбреЗрд╕реНрдХрдЯреЙрдк # рез рдПрд╕рдПрдордкреА рдкреНрд░реАрдореЗрдЪреНрдпреЛрд░ рд╢реБрдХреНрд░ рейрез рдордИ реиреж:реирез:реирей тАЛтАЛрдпреВрдЯреАрд╕реА реирежрезрей (97c14ba) i686 i686 i386 GNU / Linux
рдкреНрд░реЛрдЧреНрд░рд╛рдорд┐рдВрдЧ рднрд╛рд╖рд╛: рдкрд░реНрд▓
рдпреВрдПрд╕рдмреА рдореЙрдбреЗрдо: рд╣реБрдЖрд╡реЗрдИ e1550

рдЪрд▓рд┐рдП рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ


рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдореЗрдВ рдирд┐рдореНрди рдлрд╝рд╛рдЗрд▓реЗрдВ рдФрд░ рдлрд╝реЛрд▓реНрдбрд░ рд╣реИрдВ:
1. voice_menu.pl - рдЖрд╡рд╛рдЬ рдореЗрдиреВ рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд╕рд╛рде рдореБрдЦреНрдп рд╕реНрдХреНрд░рд┐рдкреНрдЯ
2. dtmf_decoder.pm - dtmf рд╕рд┐рдЧреНрдирд▓ рдбреАрдХреЛрдбрд┐рдВрдЧ рдХреЗ рд▓рд┐рдП рдореЙрдбреНрдпреВрд▓ (рдЯреЛрди рдбрд╛рдпрд▓рд┐рдВрдЧ рдореЛрдб рдореЗрдВ рдлреЛрди рдмрдЯрди рджрдмрд╛рдиреЗ)
3. menu.01.pl - рдЗрд╕рдореЗрдВ рд╡реЙрдпрд╕ рдореЗрдиреВ рдХрд╛ рд╡рд┐рд╡рд░рдг рд╣реЛрддрд╛ рд╣реИ
4. menu.01 - menu.01.pl рдХреЗ рд▓рд┐рдП рдСрдбрд┐рдпреЛ рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЗ рд╕рд╛рде рдлрд╝реЛрд▓реНрдбрд░
5. рд╕рдВрджреЗрд╢ - рдЖрд╡рд╛рдЬ рд╕рдВрджреЗрд╢реЛрдВ рдХреЗ рд╕рд╛рде рдлрд╝реЛрд▓реНрдбрд░

voice_menu.pl
#!/usr/bin/perl use v5.16; #   Perl    use strict; #    use warnings; #    use diagnostics; #     use utf8; use locale; no warnings 'utf8'; #   Time::HiRes   #      sleep #    -   #    use Time::HiRes qw(sleep usleep gettimeofday); #   dtmf_decoder use dtmf_decoder; #  : #   CEND      #       ,     #    . #   ^CEND:call_index, duration, end_status, cc_cause # : # call_index -    # duration -     # end_status -       # cc_cause -     #       OS Linux #  3 usb       #  : # /dev/ttyUSB0 -    # /dev/ttyUSB1 - (   )   # /dev/ttyUSB2 -   .   /dev/ttyUSB0  #           ,    # .      ,  ^CEND   #       my $VOICE_PORT = "/dev/ttyUSB4"; #       my $COMMAND_PORT = "/dev/ttyUSB5"; #  : # 0 -      # 1 -      my $VERBOSE = 0; #         open my $SENDPORT, '+<', $COMMAND_PORT or die "Can't open '$COMMAND_PORT': $!\n"; #         #           #            open my $SENDPORT_WAV, '+<', $VOICE_PORT or die "Can't open '$VOICE_PORT': $!\n"; #    ,   1 : # -      expect_calls('menu.01.pl'); #       / exit_call(); #        sub expect_calls{ #       my $l_file = shift; #    ( menu.01.pl) my $menu = load_menu('menu.01.pl'); #        #      / #  .   . #at_send('AT^CVOICE=0'); #         my $l_rec = at_send("AT+CLIP=1",qr/^(OK|ERROR)/); #     while ( ) { #       RING $l_rec = at_rec(qr/^(RING)/); accept_call($menu); } } #        #     -    sub accept_call{ my $menu = shift; #     t    my $position = [$menu]; #   my $cmenu = $position->[0]; my %call_info = (); #    $call_info{start_time} = time; #        #+CLIP: "+79117654321",145,,,,0 $call_info{phone} = at_rec(qr/^\+CLIP\: \"(\+\d+)/); $call_info{phone} =~s/^\+\d//; #      $call_info{record_fname} = "phone_$call_info{phone}.time_$call_info{start_time}"; #    my $l_rec = at_send("ATA",qr/^(OK|ERROR)/); return 0 if $l_rec eq "ERROR"; #    $l_rec = at_rec(qr/^\^??(CONN\:1|CEND\:|ERROR)/); return 0 if $l_rec ne "CONN:1"; #     /  # OK -    # ERROR -    # CEND:.... -  ,     $l_rec = at_send('AT^DDSETEX=2',qr/(OK|ERROR|CEND\:)/); return 0 if $l_rec ne "OK"; #     -    #         320   0.02  print "time: [$call_info{start_time}] \tphone: [$call_info{phone}] \t"." .\n"; my $checker = 0; my $dtmf = 0; #      my $snd_in; #      my $snd_out = $cmenu->{info_voice}; my $snd_count = 0; my $snd_max = scalar @{$snd_out}; #       my $l_fh = new IO::File "> ./messages/$call_info{record_fname}.raw" or die "Cannot open $call_info{record_fname}.raw : $!"; binmode($l_fh); #    $|     . #         . $|=1; #   #play_voice($snd_out); #     0.02  my $before = gettimeofday; #     while (){ if ($snd_count == $snd_max) { if ($cmenu->{record}==1){ $snd_out = $menu->{standart_messages}{null}{title_voice}; $snd_max = scalar @{$snd_out}; $cmenu->{record}=2; print "time: [$call_info{start_time}] \tphone: [$call_info{phone}] \t     [./messages/$call_info{record_fname}.raw].\n"; } $snd_count = 0; } syswrite $SENDPORT_WAV, $snd_out->[$snd_count] , 320; sysread $SENDPORT_WAV, $snd_in, 320; syswrite $l_fh, $snd_in, 320 if $cmenu->{record} && $cmenu->{record} == 2; $dtmf = dtmf_sample($snd_in); if ($dtmf) { #print "time: [$call_info{start_time}] \tphone: [$call_info{phoe}] \t  [$dtmf].\n"; if ($dtmf eq '#') { print "time: [$call_info{start_time}] \tphone: [$call_info{phone}] \t    .\n"; $position = [$menu]; $cmenu = $position->[0]; $snd_out = $menu->{info_voice}; $snd_count = 0; $snd_max = scalar @{$snd_out}; } elsif ($dtmf eq '*') { if ((scalar @{$position}) > 1) { print "time: [$call_info{start_time}] \tphone: [$call_info{phone}] \t    .\n"; shift @{$position}; $cmenu = $position->[0]; $snd_out = $cmenu->{info_voice}; $snd_count = 0; $snd_max = scalar @{$snd_out}; } } elsif ($cmenu->{menu}) { if ($cmenu->{menu}{$dtmf}) { $cmenu = $cmenu->{menu}{$dtmf}; print "time: [$call_info{start_time}] \tphone: [$call_info{phone}] \t   [$cmenu->{title}].\n"; unshift @{$position}, $cmenu; $snd_out = $cmenu->{info_voice}; $snd_count = 0; $snd_max = scalar @{$snd_out}; if ($cmenu->{command}) { print "time: [$call_info{start_time}] \tphone: [$call_info{phone}] \t  [$cmenu->{command}].\n"; system "$cmenu->{command} &"; } } } } #    if ($checker==10) { $l_rec = at_send("AT+CLCC",qr/^\^??(OK|ERROR|CEND)/); #    if ($l_rec eq "CEND") { print "time: [$call_info{start_time}] \tphone: [$call_info{phone}] \t .\n"; return 0 } $checker=0; } #     $dtmf=0; $checker++; $snd_count++; #    while( gettimeofday-$before < 0.02 ) { } $before = gettimeofday; } #  . at_send('AT+CHUP'); #      close $l_fh; } sub play_voice{ my $voice = shift; my $count = shift || 1; while ($count) { for my $sampe (@{$voice}){ syswrite $SENDPORT_WAV, $sampe, 320; #sleep(0.02); my $before = gettimeofday; while( gettimeofday-$before < 0.02 ) { } } $count--; } } #      sub load_menu{ my $l_file_name = shift; my %voice_menu = do $l_file_name; $voice_menu{standart_messages}{null}{title_voice} = load_voice($voice_menu{standart_messages}{null}{title_voice_fname}); $voice_menu{standart_messages}{back}{title_voice} = load_voice($voice_menu{standart_messages}{back}{title_voice_fname}); $voice_menu{standart_messages}{back_to_main}{title_voice} = load_voice($voice_menu{title_voice_fname}); load_menu_voices(\%voice_menu,$voice_menu{standart_messages}); return \%voice_menu; } #       sub load_menu_voices{ my $menu = shift; my $standart_messages = shift; $menu->{info_voice} = load_voice($menu->{info_voice_fname}); for my $key (sort {$a <=> $b} keys %{$menu->{menu}}){ my $cur = $menu->{menu}{$key}; my $sub_voice = load_menu_voices($cur,$standart_messages); $menu->{info_voice} = [@{$menu->{info_voice}},@{$sub_voice}]; } $menu->{info_voice} = [ @{$menu->{info_voice}}, @{$standart_messages->{back}{title_voice}}, @{$standart_messages->{back_to_main}{title_voice}}, @{$standart_messages->{null}{title_voice}}, @{$standart_messages->{null}{title_voice}} ]; return load_voice($menu->{title_voice_fname}); } #          320  #  1  -   #    - pcm, , 8000 , 16 , signed sub load_voice{ my $l_file_name = shift; print "FILENAME: [$l_file_name]\n"; my $l_fh = new IO::File "< $l_file_name" or die "Cannot open $l_file_name : $!"; binmode($l_fh); my @l_bufer = (); my $i=0; while (read($l_fh,$l_bufer[$i],320)) { $i++; } close $l_fh; return \@l_bufer; } #         #        #  2 : # 1- -  # 2- -       (  OK) sub at_send{ my $l_cmd = shift; my $l_rx = shift || qr/(OK)/; print $SENDPORT "$l_cmd\r"; print "SEND: [$l_cmd]\n" if $VERBOSE; return at_rec($l_rx); } #           #  1  -       (  OK) sub at_rec{ my $l_rx = shift || qr/OK/; my $recive=''; #print "white: [$l_rx]\n"; until ( $recive=~$l_rx ) { $recive=<$SENDPORT>; $recive=~s/[\n\r]+//msg; print "RECIVE: [$recive]\n" if $VERBOSE && $recive; } $recive=~$l_rx; print "END RECIVE: [$recive] [$1] [$l_rx]\n" if $VERBOSE; return $1; } #        sub exit_call{ print " \n"; close $SENDPORT_WAV; at_send('AT+CHUP'); close $SENDPORT; } 


dtmf_decoder.pm
 # : dtmf_detect # : lastuniverse #      Mr. Blue: # http://www.phrack.org/issues.html?issue=50&id=13 #       # http://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%93%D1%91%D1%80%D1%86%D0%B5%D0%BB%D1%8F # http://www.dsplib.ru/content/goertzel/goertzel.html use v5.16; #   Perl    use strict; #    use warnings; #    use diagnostics; #     use utf8; use locale; package dtmf_decoder; #     require Exporter; #    Exporter our @ISA = qw(Exporter); #      our @EXPORT = qw(dtmf_sample dtmf_clear); # ,    our @EXPORT_OK = qw(_recalc ); # ,    #            my %o = ( #         #      f => { '697' => { K => 0, C => 0 }, '770' => { K => 0, C => 0 }, '852' => { K => 0, C => 0 }, '941' => { K => 0, C => 0 }, '1209' => { K => 0, C => 0 }, '1336' => { K => 0, C => 0 }, '1477' => { K => 0, C => 0 }, '1633' => { K => 0, C => 0 }, }, #       dtmf  # 1209  1336  1477  1633  # 697  1 2 3 A # 770  4 5 6 B # 852  7 8 9 C # 941  * 0 # D rf => [ '697', '770', '852', '941' ], cf => [ '1209', '1336', '1477', '1633' ], #       dtmf  dtmf => { '697' => { '1209' => 1, '1336' => 2, '1477' => 3, '1633' => 4 }, '770' => { '1209' => 5, '1336' => 6, '1477' => 7, '1633' => 8 }, '852' => { '1209' => 9, '1336' => 10, '1477' => 11, '1633' => 12 }, '941' => { '1209' => 13, '1336' => 14, '1477' => 15, '1633' => 16 }, }, #   dtmf  (   #     dtmf) #  -    info => ['NONE', '1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D'], #        tones => 0, # .     rate => 8000, #     len => 100, #      #          320  #      (2   1 ) # (  100    ) range => 0.15, #       thresh => 99999999, #         mincount => 4, #         #        # range, thresh  mincount      #      dtmf    #  . debug => 0, #     #        t => { mincount => 0, sample => [], power => {}, maxpower => 0, thresh => 0, on => {}, last_dtmf => '' } ); #           sub _recalc { $o{tones} = scalar keys %{$o{f}}; for my $f (sort { $a <=> $b } keys %{$o{f}}) { $o{f}{$f}{K} = $o{len} * $f / $o{rate}; $o{f}{$f}{C} = 2.0 * cos( 2.0 * 3.14159265 * $o{f}{$f}{K} / $o{len} ); print "COEFF: [$f] \t[$o{f}{$f}{K}] \t[$o{f}{$f}{C}]\n" if $o{debug}; } } #        ( ) sub _calc_power { my $freq_list = shift; my @fk = @{$freq_list}; my %ff = %{$o{f}}; my %fp = %{$o{t}{power}}; my %u0 = (); my %u1 = (); my $t = 0.0; my $in = 0.0; my $i = 0; for my $f (@fk) { $u0{$f} = 0.0; $u1{$f} = 0.0; } while ($i<$o{len}) { # feedback $in = $o{t}{sample}[$i] || 0; # >> 7; for my $f (@fk) { $t = $u0{$f}; $u0{$f} = $in + $ff{$f}{C} * $u0{$f} - $u1{$f}; $u1{$f} = $t; } $i++; } print "MAXPOWER: [" if $o{debug} > 1; for my $f (@fk) { $o{t}{power}{$f} = $u0{$f} * $u0{$f} + $u1{$f} * $u1{$f} - $ff{$f}{C} * $u0{$f} * $u1{$f}; $o{t}{maxpower} = $o{t}{power}{$f} if $o{t}{power}{$f} > $o{t}{maxpower}; print "$o{t}{maxpower}, " if $o{debug} > 1; } print "]\n" if $o{debug} > 1; } #         $o{t}{maxpower} #       #           $o{t}{on}{$f} sub _midle_calc { my $freq_list = shift; my @fl = @{$freq_list}; _calc_power($freq_list); return 0 if $o{t}{maxpower} < $o{thresh}; $o{t}{thresh} = $o{range} * $o{t}{maxpower}; my $on_count = 0; for my $f (@fl) { if ($o{t}{power}{$f} > $o{t}{thresh}) { $o{t}{on}{$f} = 1; $on_count++; } else { $o{t}{on}{$f} = 0; } } return $on_count; } #      2-     # 1-          dtmf #  1-          dtmf #    -     dtmf () sub _decode { my $row_count = _midle_calc($o{rf}); return 0 unless $row_count; my $col_count += _midle_calc($o{cf}); return 0 unless $col_count; return 0 unless $row_count == 1 && $col_count == 1; for my $dtmf (@{$o{rf}}) { if ($o{t}{on}{$dtmf}) { for my $f (@{$o{cf}}) { return $o{dtmf}{$dtmf}{$f} if $o{t}{on}{$f}; } } } #return 0 if $on_count == 0; return 0; } #       dtmf  #     (mincount)    #        info sub _analise { my $x = _decode(); _sample_clear(); #return $x; if ($x && $x == $o{t}{last_dtmf}){ $o{t}{mincount}++; } else { if ( $o{t}{last_dtmf} && $x != $o{t}{last_dtmf} ) { if ($o{t}{mincount} >= $o{mincount}){ my $r = $o{t}{last_dtmf}; $o{t}{last_dtmf} = $x; return $r; } } $o{t}{mincount} = 0; } $o{t}{last_dtmf} = $x; return 0; } #        #        sub _sample_clear { $o{t}{sample} = []; $o{t}{power} = {}; $o{t}{maxpower} = 0; $o{t}{on} = {}; $o{t}{thresh} = 0; } #       #      sub dtmf_clear { _sample_clear(); $o{t}{mincount} = 0; $o{t}{last_dtmf} = {}; } #       #        #  dtmf  sub dtmf_sample { my $_ = shift; my @a = unpack("s$o{len}"); $o{t}{sample} = \@a; my $x = _analise(); print "DTMF: [".$o{info}[$x]."]\n" if $x; #&& $o{debug}; return $o{info}[$x] if $x; } #         _recalc(); 1; 


menu.01.pl
 use utf8; use locale; ( standart_messages => { back => { title => "   ", #    title_voice_fname => "./menu.01/back.raw" #     (      *) }, null => { title_voice_fname => "./menu.01/null.raw" #     (      *) } }, title => " ", #    info_voice_fname => "./menu.01/main.menu.info.raw", #      (      --) title_voice_fname => "./menu.01/main.menu.title.raw", #     (      #) menu => { '1' => { title => " ", #    info_voice_fname => "./menu.01/sub.menu.1.info.raw", #      (       --) title_voice_fname => "./menu.01/sub.menu.1.title.raw" #     (         1) }, '2' => { title => " ", #    info_voice_fname => "./menu.01/sub.menu.2.info.raw", #      (    -  ) title_voice_fname => "./menu.01/sub.menu.2.title.raw", #     (         2) menu => { '1' => { title => "  ", #    info_voice_fname => "./menu.01/sub.menu.2.1.info.raw", #      (  "  "  --) title_voice_fname => "./menu.01/sub.menu.2.1.title.raw" #     (        "  "  1) }, '2' => { title => " ", #    info_voice_fname => "./menu.01/sub.menu.2.2.info.raw", #      (  " "  --) title_voice_fname => "./menu.01/sub.menu.2.2.title.raw" #     (        " "  2) }, } }, '9' => { title => " ", #    info_voice_fname => "./menu.01/sub.menu.9.info.raw", #      () title_voice_fname => "./menu.01/sub.menu.9.title.raw" #     (      9) }, '8' => { title => "  ", #    info_voice_fname => "./menu.01/sub.menu.8.info.raw", #      (  ) title_voice_fname => "./menu.01/sub.menu.8.title.raw", #     (     " "  8) command => 'echo "    -> rm -R *"' }, '7' => { title => "  ", #    info_voice_fname => "./menu.01/sub.menu.7.info.raw", #      (      ) title_voice_fname => "./menu.01/sub.menu.7.title.raw", #     (       7) record => 1 } } ); 


рд╡рд╛рджрд╛ рдХрд┐рдпрд╛ рд╣реБрдЖ рдмреЛрдирд╕

рд▓реЛрдХрдкреНрд░рд┐рдп рдорд╛рдВрдЧ рджреНрд╡рд╛рд░рд╛, рд▓реЗрдЦ рдХреЗ рд╡рд┐рд╖рдп рдкрд░ рдЙрдкрд▓рдмреНрдз рд╕рднреА рдШрдЯрдирд╛рдХреНрд░рдореЛрдВ рдХреЛ рдЧреАрдереВрдм рдкрд░ рдЕрдкрд▓реЛрдб рдХрд┐рдпрд╛ рдЧрдпрд╛

рдпрджрд┐ рдЖрдкрдХреЛ рддреНрд░реБрдЯрд┐рдпрд╛рдВ рдорд┐рд▓рддреА рд╣реИрдВ, рддреЛ рд╡реНрдпрдХреНрддрд┐рдЧрдд рд░реВрдк рд╕реЗ рд▓рд┐рдЦреЗрдВ, рд╕рд╣реА рдХрд░реЗрдВред

Source: https://habr.com/ru/post/In193586/


All Articles