BlackTDN Search

terça-feira, 15 de novembro de 2011

Protheus :: Advpl :: VT100 Emulador de microterminal via TELNET

Dias atrás alguém me pediu para desvendar o SIGAACD. Mas o que (…) é o SIGAACD? Descobri que nada mais é do que um módulo do Protheus para o gerenciamento de microterminais através do protocolo TELNET usando VT100.
Bem, desafio aceito.
Em busca de material…. humpf… não encontrei…. vou usar a metodologia иαldσ dj (fuça..fuça.. fuça.. fuça… _VTOUT() humm… isso é interessante… fuça… fuça… fuça.. _VTREADBYTES( NR , NR , NR ) hum isso não entendi…. fuça.. fuça.. fuça.. fuça.. _VTGETBYTE(), opa, isso eu entendi…).
Bem, já tenho o fragmento do material mínimo necessário para programar um emulador. Vamos entender o que é TELNET e VT100 e, depois, ao nosso exemplo:
“Telnet é um protocolo cliente-servidor usado para permitir a comunicação entre computadores ligados numa rede (exemplos: rede local / LAN, Internet), baseado em TCP.” (fonte: Wikipédia)
VT100 nada mais é do que uma convenção de um padrão para emular um microterminal (fonte: Microsoft/TechNet :: Convenções VT-UTF8, VT100+ e VT100). Além da VT100 existem diversas outras “convenções”.
O Protheus, emula um terminal no padrão VT100.
Para habilitar o protocolo TELNET no Protheus é bem simples. Basta incluir as chaves abaixo no arquivo ini.
[TELNET]
Enable=1
Environment=<Environment a ser utilizado>
Main=<Funcao a ser executada>
NPARAMS=<Numeros de Paramentros da Funcao. Habilita Param1, Param2, ParamN…>
port=<Numero da porta de escuta. Padrao 23>
Feito isso, basta reiniciar o server Protheus que o TELNET Server será habilitado. Observe:
image
As funções passíveis a MAIN são: SIGAACDM, SIGAACDT e SIGAACD
Para SIGAACDM teremos a seguinte saída:
[TELNET]
Enable=1
Environment=ndj_01
Main=SIGAACDM
NPARAMS=0
;port=24
usando o TELNET da Microsoft teremos:
image
image
Para SIGAACDT teremos:
[TELNET]
Enable=1
Environment=ndj_01
Main=SIGAACDT
NPARAMS=0
image
image
e, para SIGAACD
[TELNET]
Enable=1
Environment=ndj_01
Main=SIGAACD
NPARAMS=0
image
image
image
image
image
Legal. Mas como é que o Protheus faz isso?
Simples, quando recebe uma conexão via TELNET ele “desperta” 3 funções:
  1. _VTOUT() :: Envia as mensagens para o terminal;
  2. _VTREADBYTES( NR , NR , NR ) : Faz a leitura Buferizada das informações digitadas no Terminal; e
  3. _VTGETBYTE() : Faz a leitura byte a byte do que foi digitado no Terminal
No exemplo de código que vou apresentar utilizarei a Primeira e a última, uma vez que ainda não compreendi totalmente os parâmetros da _VTREADBYTES. Ela possui 3 parâmetros formais do tipo numérico: n1, n2, n3 (mas o que fazer com eles ainda me é uma incógnita);
Para entender o que enviar para o Terminal TELNET pesquisei:
E, para o exemplo de programa, peguei emprestado o código do jogo “Guess” implementado no Harbour por Eddie Runia (Harbour Project: $Id: guess.prg 14676 2010-06-03 16:23:36Z vszakats $).
Para o nosso Emulador TELNET do jogo Guess, teremos as seguintes configurações:
[TELNET]
Enable=1
Environment=ndj_01
Main=U_MAINGUESS
NPARAMS=0
;port=24
e, para joga-lo:
image
image
image
image
image
image
image
… depois de algum tempo
image
Usando PuTTY: A Free Telnet/SSH Client:
image
image
image
image
… depois de algumas tentativas…:
image
O Código? ei-lo:
#INCLUDE "INKEY.CH"
#INCLUDE "PROTHEUS.CH"
#DEFINE OPC_GUESS        1
Static __cESC        :=    Chr(K_ESC)    //27
Static __lForceExit    := .F.
/*(
    Procedure:    U_MAINGUESS
    Autor:        Marinaldo de Jesus
    Data:        15/11/2011
    Descricao:    Exemplo de emulador TELNET
    Sintaxe:    U_MAINGUES
    [TELNET]
    Enable=1
    Environment=ndj_01
    Main=U_MAINGUES
    NPARAMS=0
    ;port=24 ;//DEFAULT 23
)*/
Procedure U_MAINGUESS()
    Local nOpc        := Val( Read( 0 , 0 , "Iniciar o Jogo? 0-Nao; 1-Sim <enter>: " ) )
    DO CASE
    CASE ( nOpc == OPC_GUESS )
        Guess()
    OTHERWISE
        IKillApp(.T.)
    ENDCASE
    /*(
       Guess a number
       Date       : 1999/04/22
       My first application (big word) written in Harbour
       Written by Eddie Runia <eddie@runia.com>
       www - http://harbour-project.org
       Placed in the public domain
    )*/
    Static Function Guess()
        Local cPick        := ""
        Local cAttempts    := ""
        Local nSeed        := Randomize( 1 , 256 )
        Local nPick        := 0
        Local nAttempts    := 0
        Local nflGuessed
        Local lContinue := .T.
        Clear()
        Say( 0 , 0 , "Welcome to guess a number...."  )
        Say( 1 , 0 , "You have to guess a number between 0 and 255 [ press <esc> <enter> to exit ]"  )
        While !( IKillApp() )
            While ( lContinue )
                nSeed        := Randomize( 1 , 256 )
                nflGuessed    := 0
                nAttempts    := 0
                While ( nflGuessed == 0 )
                    Clear(3,0,3)
                    nPick        := Val( Read( 3 , 0 , "Value <enter>: " ) )
                    IKillApp()
                    cPick        := AllTrim( Str( nPick , 3 , 0 ) )
                    cAttempts    := AllTrim( Str( ++nAttempts , 4 , 0 ) )
                    DO CASE
                    CASE ( nPick > 255 )
                        Clear(5,0)
                        Say(5,0,cPick + " More than 255" )
                    CASE ( nPick < 0 )
                        Clear(5,0)
                        Say(5,0,cPick + " Less than 0")
                    CASE ( nPick > nSeed )
                        Clear(5,0)
                        Say(5,0,"Try lower: " + cPick )
                    CASE ( nPick < nSeed )
                        Clear(5,0)
                        Say(5,0,"Try higher: " + cPick )
                    OTHERWISE
                        Say(5,0,"Congratulations, you've, AFTER " + cAttempts + " ATTEMPTS, guessed the number " + cPick )
                        Sleep(300)
                        nflGuessed := 1
                    ENDCASE
                End While
                Clear(7,0)
                lContinue := ( Upper( Read( 7 , 0 ,  "Continue Y/N <enter> : " ) ) == "Y" )
                Clear(5,0)
                IF !( lContinue )
                    IKillApp(.T.)
                EndIF   
           End While
        End While
    Return( .T. )
    /*(
        Function:    Say
        Autor:        Marinaldo de Jesus
        Data:        15/11/2011
        Descricao:    Direciona Saida para o Terminal TELNET
        Sintaxe:    Say(nRow,nCol,cOut)
    )*/
    Static Function Say(nRow,nCol,cOut)
        DEFAULT cOut := ""
        SetPos(nRow,nCol)
    Return(_QQOut(@cOut))
    /*(
        Function:    Clear
        Autor:        Marinaldo de Jesus
        Data:        15/11/2011
        Descricao:    Limpa o Console do Terminal TELNET
        Sintaxe:    Clear(nTop,nLeft,nBottom,nRight)
    )*/
    Static Function Clear(nTop,nLeft,nBottom,nRight)
        DEFAULT nTop     := 0
        DEFAULT nLeft    := 0
        DEFAULT nBottom    := 300
        DEFAULT nRight    := 80
    Return(Scrool(nTop,nLeft,nBottom,nRight))
    /*(
        Function:    Scrool
        Autor:        Marinaldo de Jesus
        Data:        15/11/2011
        Descricao:    Limpa o Console do Terminal TELNET
        Sintaxe:    Scrool(nTop,nLeft,nBottom,nRight)
    )*/
    Static Function Scrool(nTop,nLeft,nBottom,nRight)
        Local nT
        Local cSPC    := Space(nRight-nLeft)
        For nT := nTop To nBottom
            Say(nT,nLeft,cSPC)
        Next nT
    Return(SetPos(nTop,nLeft))
    /*(
        Function:    _QQOut
        Autor:        Marinaldo de Jesus
        Data:        15/11/2011
        Descricao:    Direciona Saida para o Terminal TELNET
        Sintaxe:    _QQOut(cOut)
    )*/
    Static Function _QQOut(cOut)
        Local cVTFun    := "_VTOUT"
    Return(&cVTFun.(cOut,))
    /*(
        Function:    SetPos
        Autor:        Marinaldo de Jesus
        Data:        15/11/2011
        Descricao:    Define o Posicionamento da Linha/Coluna para a Mensagem
        Sintaxe:    SetPos(nRow,nCol)
    )*/
    Static Function SetPos(nRow,nCol)
        Local cR
        Local cC
        Local cSetPos
        DEFAULT nRow    := 0
        DEFAULT nCol    := 0
        cR        := Alltrim( Str( nRow , 4 , 0 ) )
        cC         := Alltrim( Str( nCol , 4 , 0 ) )
        cSetPos := __cESC
        cSetPos += "["
        cSetPos += cR
        cSetPos += ";"
        cSetPos += cC
        cSetPos += "H"
    Return(_QQOut(cSetPos))
    /*(
        Function:    Read
        Autor:        Marinaldo de Jesus
        Data:        15/11/2011
        Descricao:    Le Byte a Byte o Conteudo Digitado no Terminal
        Sintaxe:    Read( nRow , nCol , cOut )
    )*/
    Static Function Read( nRow , nCol , cOut )
        Local cVTFun    := "_VTGETBYTE"
        Local cByte        := ""
        Local cBuffer    := ""
        Local lExit        := .F.
        Say( nRow , nCol , cOut + cBuffer )
        While !( lExit )
            cByte    := &cVTFun.(,)
            lExit    := ( Asc(cByte) == K_ENTER )
            IF ( cByte == __cESC )
                __lForceExit    := .T.
            EndIF   
            IF !( lExit )
                IF ChkAsc( cByte , .F. )
                    cBuffer    += cByte
                EndIF   
            EndIF
        End While
    Return( cBuffer )
    /*(
        Function:    IKillApp
        Autor:        Marinaldo de Jesus
        Data:        15/11/2011
        Descricao:    Verifica se deve finalizar a aplicacao
        Sintaxe:    IKillApp(lKillApp)
    )*/
    Static Function IKillApp(lKillApp)
        Local cMsg            := "bye bye. Exiting..."
        Local lKilled        := .F.
        DEFAULT lKillApp    := .F.
        IF (;
                ( lKillApp );
                .or.;
                ( __lForceExit );
            )   
            lKilled            := .T.
            lKillApp        := .T.
            Clear(0,0)
            IF ( __lForceExit )
                cMsg        := "<esc> " + cMsg
            EndIF
            Say( 5 , 0 , cMsg )           
            Sleep(800)
            KillApp(lKillApp)
        EndIF
    Return( lKilled )
Return
Quer o original? Siga o link: totvs-advpl-naldodj
[]s
иαldσ dj
P.S.:
1 ) Obviamente que o exemplo é bem simples. Para ter um microterminal completo terá que programar um bucado. As funções básicas estão descritas acima. Basta, agora, pesquisar, estudar, praticar, estudar mais um pouco, praticar, praticar e praticar. Uma dica, tente converter o código manklala.prg ($Id: mankala.prg 14676 2010-06-03 16:23:36Z vszakats $)  também de Eddie Runia para o Harbour-Projetct, para ser jogado via TELNET.
2 ) Anônimo deixou o Link: “Conteúdo Colaborativo”(tdn: http://tdn.totvs.com/kbm#24886). Ele contém as funções em “ADVPL” para troca de mensagens com o microterminal. Preferi montar o exemplo na raça usando as funções da API (senão não seria desafio)

14 comentários:

  1. Muito chato (pra nao falar outra coisa), programar para esse VT100. Prefiro fazer 50 telas modelo3, 15 relatórios em TReport e 5 WebServices, do que pegar outro desenvolvimento desse.

    Ficadica!
    ...mas parabéns!

    ResponderExcluir
  2. [Funcao VTMODELO()]
    Acesse o link http://tdn.totvs.com/kbm#24886
    e veja o conteudo colaborativo..

    ResponderExcluir
  3. Valeu pela dica. Mas preferi não usar as funções em ADVPL para o modelo e sim funções da API (que não estou documentadas no TDN ou, se estão, são de acesso restrito). Temos que conhecer o "cerne" para dominar.
    []s

    иαldσ dj

    ResponderExcluir
  4. Hehehee.. testei a parada mestrao Lee!!!! E "funfou"... hehehe! Grande abraço e parabens Mestrao!!!!

    ResponderExcluir
  5. ...enfim, resumindo a cena... VT100 ou caso queriam SIGAACD, é um terreno onde você irá fazer muita, aliás, MUUUUUUUUITA.. gambiarra para chegar ao resultado esperado.
    Ainda é uma tecnologia bem falha, incompleta que deve ter sido abandonada pela Totvs. O desenvolvimento é indicado para casos simples, com uma complexidade muito baixa... se seu 'amigo' consultor prometer uma FERRARI ao cliente no SIGAACD.. prepare-se por que você tera de construi-la com um chiclete, um grampo e uma linha de costura.

    ResponderExcluir
  6. Para ajudar ainda tem a função VTDEBUG que monta a tela do cliente vt100 no formato advpl como no putty,teraterm ou telnet, mas para facilitar o debug dos programas linha a linha.

    O vt100 é mais utilizado com telas pequenas como de coletores de dados e microterminais, com interface simples e tráfego de dados minimo.

    ResponderExcluir
  7. Eu fiz um programa usando as funções VT100, como faço para emular ele em tela de ADVPL?

    ResponderExcluir
    Respostas
    1. Tem que emular via TelNet. conforme exemplo.

      Excluir
    2. Naldo,
      quando tento emular via Telnet não consigo utilizar as setas direcionais do teclado, fica aparecendo uns códigos (^[[A, ^[[B...). Não sei como resolver isso.
      poderia me ajudar?

      Obrigado!

      Excluir
  8. Teste o VTDEBUG , este é o simulador do ACD

    ResponderExcluir
  9. Srs. o Haldo me conhece, já fizemos curso de C# juntos e também trabalhamos na TOTVS. Porém a diferença é que eu trabalhei diretamente no desenvolvimento padrão ACD. Pois bem, o VTDEBUG não era uma solução oficial para permitir debugar programas padrão vt100, porém como a tecnologia da TOTVS na época não criou uma soluções, nós da equipe resolvemos criar o VTDEBUG temporariamente, que pelo que percebi permanece até hoje. Eu sei que existem diversas deficiências, mas peço que todos entendam que isso era somente uma ferramenta interna para permitir a equipe debugar e infelizmente isso ficou como padrão. Eu tenho versões POWER do VTDEBUG, mas é claro não é oficial. É importante informar que o Alex Sandro Valário também esta trabalhando comigo na iMind, caso tenha dúvida acesse: http://imind.com.br.

    ResponderExcluir
  10. Olá Erike Yuri,
    Estou tentando usar um VT100 (putty, terminator e outros) no Linux, porém as setas direcionais do teclado não funcionam, fica aparecendo uns códigos (^[[A, ^[[B...). Não sei como resolver isso.
    poderia me ajudar?

    Obrigado!

    ResponderExcluir
  11. Olá Naldo, boa tarde! Tudo bem? Então, desculpe-me a ousadia, mas estou quebrando a cuca com um bug no uso dos terminais VT100, que temos espalhados na área de produção. Pois bem, existe a rotina padrão de apontamento da produção, T_ACDA020, executando normalmente. Resolvi customizar uma rotina, tendo como base o T_ACDA020.PRG, criei uma cópia do mesmo, cuja finalidade é aceitar a digitação/captura do peso da balança, para um produto que possui duas unidades de medida em que a 1ª está em metro e a 2ª está em KG. Acontece que a peça é grande, em forma de rolo, o usuário não tem como medir a peça, nesse caso resolvemos capturar o peso que etá relacionado à 2ª unidade, convertendo para a 1ª unidade em metros (MT). Realizamos testes via VTDEBUG e a rotina executou perfeitamente e acompanhamos as movimentações geradas, tudo ok e validado com o usuário. No entanto, na hora de colocar em produção, o programa não executa no microterminal VT100. Caso tenhas alguma solução aplicada para algum caso semelhante, poderias compartilhar?

    ResponderExcluir