Tratamento de Entrada de Dados
Início  Anterior  Próximo



O evento OnPlayStop é chamado sempre quando uma mensagem termina, portanto este evento será chamado diversas vezes durante o atendimento. Ele junto com o evento OnDigitsReceived são os mais complexos do exemplo e merecem maior atenção.

Nesta primeira parte do tratamento do OnPlayStop, contemplaremos apenas a execução em seqüência da mensagem de boas vindas.

O evento OnPlayStop passa o status como parâmetro através da variável StopStatus. No nosso atendimento é necessário tratar duas situações.: quando a mensagem termina normalmente ou quando ela é interrompida por causa do recebimento de um dígito. No primeiro caso a variável StopStatus recebe o valor ssNormal. Nesta situação o sistema ficará em silêncio por 5 segundos esperando a digitação do ramal ou opção de menu pelo usuário.
 
procedure TForm1.voice1PlayStop(Sender: TObject; Port,  
                                        StopStatus: Smallint);  
Begin  
  case StopStatus of  
     ssNormal:  //MENSAGEM FOI ATE O FIM  
     begin  
        case Estado[Port] of  
           BOASVINDAS:  
           begin  
               //aqui espera 1 digito que pode ser a opção    
               //de menu ou o primeiro digito do ramal  
               InsereDado(Port,'Esperando Digitação');  
               Estado[Port] := ESPERA_DIGITOS_MENU;  
               voice1.GetDigits(Port,1,'',5000,5000);  
               //habilita espera de ocupado  
               voice1.EnableCallProgress(port);  
               //Habilita detecção de pulso  
               voice1.EnablePulseDetection(Port);  
           end;  
     end;     
  end;  
end;  

Este if acima trata o caso de as mensagens de boas vindas terem sido reproduzidas sem a interrupção do usuário. O fluxo de atendimento pede que após a mensagem o sistema fique 5 segundos em silêncio esperando a digitação do usuário. Isto é feito apenas com a chamada a função GetDigits.

No código apresentado o GetDigits indica que a VoicerLib esperará por 1 digito que poderá ser a opção de menu ou o primeiro dígito do ramal. O tratamento desta função GetDigits será feito no OnDigitsReceived, explicado mais adiante.

Repare que o estado é alterado para ESPERA_DIGITOS_MENU para que o evento OnDigitsReceived saiba qual a situação a tratar.

Ainda é preciso prever a situação do usuário teclar algo sobre a mensagem. Até a versão 2.0 isto deveria ser feito no evento OnPlayStop.

A partir da versão 2.1, o evento OnDigitsReceived também é gerado quando um dígito é detectado durante a reprodução de mensagem sendo que a variável Status recebe o valor edDigitOverMessage para indicar a situação. Isto faz com que tanto o tratamento de dígito sobre a mensagem como o gerado a partir do método GetDigits sejam tratados em um único lugar, no caso, o evento OnDigitReceived.

Agora apresentaremos o caso de dígito sobre mensagem. Se for o primeiro dígito da numeração de ramais (dois no nosso exemplo) o sistema deverá ficar esperando os proximos 2 dígitos. Se for outro, deve ser tratado como opção de menu.

procedure TForm1.voice1DigitsReceived(Sender: TObject; Port,  
  Status: Smallint);  
var  
  ret: integer;  
begin  
  case Status of  
     EdMaxDigits,  
     edDigitOverMessage:  //msg interrompida por digito  
     begin  
       case Estado[Port] of  
          ESPERA_DIGITOS_MENU,  
          BOASVINDAS:              
          begin                                                  
              InsereDado(Port,   
                       'Tratamento de Digito após mensagem');  
              TrataPrimeiroDigito(Port);  {<- IMPORTANTE}  
          end;        
        end;  
     end;  
  end;  
 
end;  

O case mais externo (Status) testa se o valor é EdMaxDigits ou edDigitOverMessage. O primeiro indicará que a mensagem de boas vindas foi executada até o fim e o método GetDigits foi acionado, conforme mostrado no código da página anterior. Já o segundo indica que a mensagem foi interrompida por dígito. Como o tratamento é o mesmo para os dois casos, o fato de separarmos as constantes por virgula indica um OU outro.

A função TrataPrimeiroDigito é uma subrotina que verificará se é ramal ou opção de menu. Este código foi colocado em uma subrotina para deixar o programa mais organizado. Eis o código da rotina TrataPrimeiroDigito:
 
procedure TForm1.TrataPrimeiroDigito(Port: smallint);  
begin  
   if Copy(voice1.ReadDigits(Port),1,1) = '2' then  
begin  
   InsereDado(Port,'Esperando resto do ramal');  
   Estado[Port] := ESPERA_DIGITOS_RAMAL;  
   voice1.GetDigits(1,3,'',5000,5000);  
end  
else  
begin  
   nOpMenu[Port] :=   
              StrToInt(Copy(voice1.ReadDigits(Port),1,1));  
 
   if rcMenu[nOpMenu[Port]].sRamalMenu <> '' then  
   begin  
      //disca direto  
      //adiciona o ramal  
      lstRamal.Add(rcMenu[nOpMenu[Port]].sRamalMenu);  
      //adiciona fuga  
      lstRamal.Add(RAMAL_FUGA1);  
      lstRamal.Add(RAMAL_FUGA2);  
      Estado[Port] := TRANSFERENCIA;  
      nEstadoRamal[Port] := LIVRE;  
      //aguarde um instante...  
      voice1.PlayFile(Port,'sisaguar.sig','');  
   end  
   else  
      if rcMenu[nOpMenu[Port]].sFrase <> '' then  
      begin  
         voice1.ClearDigits(Port);  
         Estado[Port] := SUBMENU;  
         //fala submenu  
         voice1.PlayFile(Port,   
                     rcMenu[nOpMenu[Port]].sFrase,                   
                     rcMenu[nOpMenu[Port]].sOpcoesSubMenu);  
 
      end  
      else  
      begin  
         //estrutura errada, sem ramal nem submenu  
         //então vai parafuga  
     (.....)  
      end;  
    end;  
end;  

Aqui é importante entender um detalhe de implementação do aplicativo exemplo. Como ele prevê a possibilidade de menus e sub-menus era necessário criar uma estrutura de dados que simplificasse a codificação desta necessidade.

No exemplo, se o usuario escolher a opção de menu 3, por exemplo, ele irá para um sub-menu com duas opções. Cada uma destas duas opções transfere para um ramal diferente. Já a opção 6, transfere para um ramal diretamente, sem sub-menu. O esquema abaixo ilustra a estrutra de menu e sub-menu do nosso exemplo:
 
clip0007  
No programa, foi criado uma estrutura do tipo record da seguinte maneira:
 
  TrcMenu = record  
     sRamalMenu: string;   //se for vazio é pq tem submenu  
     sFrase: string;      //preenhida no caso de submenu  
     sOpcoesSubMenu: string;  
  end;  
 
Em seguida foram criadas duas variáveis globais, uma do tipo acima que contemplará o menu e outra do tipo string que contemplará os ramais de sub-menu.
 
 Public  
    (......)  
    rcMenu: array[0..9] of TrcMenu;  
    sRamalSubMenu:array[0..9,0..9] of string;  
    (......)  
 
A variável rcMenu é um vetor de 0 à 9 pois utilizaremo o índice do vetor como a opção digitada pelo usuário. Por exemplo, se o usuário digitar a opção 3, pegaremos a variável rcMenu índice 3 (rcMenu[3]) para saber se deve tem sub-menu ou discagem para ramal direto.

Já o array sRamalSubMenu é um vetor bidimencional, onde o primeiro índice é a opção de menu, e o segundo a opção de sub-menu. Desta maneira, quando o usuário escolher o menu e em seguida o sub-menu, o programa já saberá para qual ramal discar:

sRamalSubMenu[op_menu,op_submenu].  

No evento OnShow do form, inicializaremos essas duas variáveis com a configuração do nosso atendendor. Aqui tudo está hardcoded mas em uma aplicação real é interessante ter estes dados lidos de arquivos de configuração.
 
procedure TForm1.FormShow(Sender: TObject);  
(.........)  
  //estrutura de menus e submenus  
  rcMenu[3].sRamalMenu := '';           
  rcMenu[3].sFrase := 'sub_coml.sig';  
  rcMenu[3].sOpcoesSubMenu := '12';  
  rcMenu[4].sRamalMenu := '';  
  rcMenu[4].sFrase := 'sub_sup.sig';  
  rcMenu[4].sOpcoesSubMenu := '12';  
  rcMenu[5].sRamalMenu := '';  
  rcMenu[5].sFrase := 'sub_fax.sig';  
  rcMenu[5].sOpcoesSubMenu := '12';  
  rcMenu[6].sRamalMenu := '241';  
  rcMenu[6].sFrase := '';  
  //submenu  
  sRamalSubMenu[3][1] := '231';    //cons final  
  sRamalSubMenu[3][2] := '220';    //revendas  
  sRamalSubMenu[4][1] := '212';    //sup soft  
  sRamalSubMenu[4][2] := '211';    //tecnica  
  sRamalSubMenu[5][1] := '202';    //fax coml  
  sRamalSubMenu[5][2] := '235';    //fax tec  
(.........)  
end;  

A estrutura rcMenu funciona da seguinte forma: se o campo sRamalMenu tiver algum valor, significa que não há sub-menu e deve discar direto para o ramal (caso do índice 6). Caso contrário, o campo sFrase deverá estar preenchido com a mensagem a ser reproduzida com o sub-menu e o campo sOpcoesSubMenu contém os dígitos a serem aceitos como opções de cada um dos sub-menus.

Agora, voltemos a comentar o código da rotina TrataPrimeiroDigito da página 71.

O primeiro if (abaixo) verifica se o primeiro dígito recebido é 2, que no nosso caso significa o início de um ramal, pois todos os ramais começam com 2.

(....)  
   if Copy(voice1.ReadDigits(Port),1,1) = '2' then  
begin  
   InsereDado(Port,'Esperando resto do ramal');  
   Estado[Port] := ESPERA_DIGITOS_RAMAL;  
   voice1.GetDigits(Port,3,'',5000,5000);  
end  
else  
(....)  
 
O programa deverá então esperar os outros dois dígitos do ramal. Repare que a função GetDigits passa o 3 como segundo parâmetro, indicando que deve esperar 3 dígitos. Isto está correto pois, como a propriedade AutoClearDigits está false, o buffer de dígitos do canal já entra com aquele número 2 digitado anteriormente, restando apenas mais 2 números a serem digitados para completar os 3 que a função GetDigits está esperando. Em resumo, o segundo parâmetro da função GetDigits indica, na realidade, quantos dígitos devem existir no buffer interno de dígitos do canal e não quantos dígitos devem ser digitados a partir de sua chamada (Se a propriedade AutoClearDigits estiver true, as duas situações se equivalem).

No else do if acima está o tratamento da situação de ter sido digitado um dígito diferente do plano de numeração de ramais. Então deverá ser verificado se o dígito é uma opção de menu válida para sub-menu ou para transferência direta.

Observe a porção de código abaixo:

(....)  
else  
begin  
   nOpMenu[Port] :=   
              StrToInt(Copy(voice1.ReadDigits(Port),1,1));  
 
   if rcMenu[nOpMenu[Port]].sRamalMenu <> '' then  
   begin  
      //disca direto  
      //adiciona o ramal  
      lstRamal.Add(rcMenu[nOpMenu[Port]].sRamalMenu);  
      //adiciona fuga  
      lstRamal.Add(RAMAL_FUGA1);  
      lstRamal.Add(RAMAL_FUGA2);  
      Estado[Port] := TRANSFERENCIA;  
      nEstadoRamal[Port] := LIVRE;  
      //aguarde um instante...  
      voice1.PlayFile(Port,'sisaguar.sig','');  
   end  
   else  
(....)  

Para facilitar, atribuímos à variável nOpMenu[Port] o número da opção digitada pelo usuário. Ela deve ser declarada como global. É um array de 20 posições para prever até 20 canais:

(....)  
 public  
    nOpMenu: array[1..20] of integer;       //menu  
    nOpSubMenu: array[1..20] of integer;    //submenu  
(....)  
 
O if rcMenu[nOpMenu[Port]].sRamalMenu <> '' testa se o campo sRamalMenu tem algum conteúdo. Se tiver, entra no bloco deste if e trata a discagem direta ao ramal. Aqui pressupomos que o conteúdo de sRamalMenu será um ramal válido.

Quando o sistema disca para o ramal, existe a possibilidade de o ramal estar ocupado ou não atender. No nosso aplicativo, caso isso aconteça o sistema tentará transferir para os ramais de fuga que são dois. Utilizamos a mesma técnica das mensagens em seqüência, criando uma lista para os ramais. O tratamento de transferência será explicado em detalhes no item "Transferência e Supervisão".

Ainda na rotina TrataPrimeiroDigito temos a necessidade de tratar o caso de sub-menu:
 
procedure TForm1.TrataPrimeiroDigito(Port: smallint);  
begin  
   if Copy(voice1.ReadDigits(Port),1,1) = '2' then  
begin  
   (.......)  
end  
else  
begin  
   nOpMenu[Port] :=   
              StrToInt(Copy(voice1.ReadDigits(Port),1,1));  
 
   if rcMenu[nOpMenu[Port]].sRamalMenu <> '' then  
   begin  
   (.......)  
   end  
   else  
      if rcMenu[nOpMenu[Port]].sFrase <> '' then  
      begin  
         voice1.ClearDigits(Port);  
         Estado[Port] := SUBMENU;  
         //fala submenu  
         voice1.PlayFile(Port,   
                     rcMenu[nOpMenu[Port]].sFrase,                   
                     rcMenu[nOpMenu[Port]].sOpcoesSubMenu);  
      end  
      else  
(.......)  
 
No bloco que trata o sub-menu, o sistema terá que falar uma nova mensagem e em seguida esparar a digitação de uma das opções propostas. Então é necessário apagar o buffer interno de dígitos através do método ClearDigits(port) , mudar o estado da aplicação para SUBMENU e iniciar a reprodução da mensagem de sub-menu (PlayFile).

Toda esta entrada de dados pode ser simplificada com os métodos Menuxxx e Promptxxx.