; FORMULA
;
; Programma per Proteus
;
; (C) 2004 Simone Zanella Productions
;
; Riceve dati da un terminale connesso via RS-232 che comunica con il protocollo Datalogic Sysnet (terminali Formula);
; i dati ricevuti (se la trasmissione ha avuto successo) sono successivamente introdotti in emulazione di
; tastiera - ogni dato è terminato da Enter (Invio).
; Questo programma può essere installato come script associato al servizio Proteus.
;
; I parametri di comunicazione si trovano all'inizio del programma e sono:
; - COMPORT = porta di comunicazione ("COM1", "COM2", ecc.)
; - COMSPEED = velocità di comunicazione (1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200)
; - COMPARITY = "N" (nessuna), "E" (pari), "O" (dispari), "M" (marca), "S" (spazio)
; - COMDATA = 7 o 8 (bit di dati)
; - COMSTOP = 1 o 2 (bit di stop)
; - COMFLOW = "R" (RTS/CTS detto anche hardware), "X" (XON/XOFF detto anche software), "E" (entrambi), "N" (nessuno)
;
; E' poi possibile specificare:
; - TERMADDR: indirizzo del terminale interrogato (di default, 1);
; - POLLINTERVAL: periodicità (in secondi) con la quale il programma verifica la presenza di un terminale;
; - TIMEOUT: timeout (in millisecondi) per la ricezione di un pacchetto dal terminale.
;
; Il codice commentato più sotto permette anche in alternativa di:
; - selezionare una specifica finestra (applicativo Ultra-Edit) ed inviare il dato verbatim, seguito da Invio;
; - lanciare il blocco note (se non aperto), introdurre il dato verbatim (seguito da Invio) e tornare alla finestra
;   iniziale.

#!proteus -z -j

!include "win32.prt"

; Parametri di configurazione
; ---------------------------

; Porta seriale
COMPORT = "COM1"
; Baud rate
COMSPEED = 9600
; Parità (None, Odd, Even, Mark, Space: solo iniziale)
COMPARITY = "M"
; Bit di dati
COMDATA = 7
; Bit di stop
COMSTOP = 1
; Controllo di flusso (Rts/cts, Xon/xoff, Entrambi, Nessuno: solo iniziale)
COMFLOW = "N"

; Indirizzo del terminale
TERMADDR = 1

; Ritardo introdotto dopo l'invio di ciascuna riga in emulaz. di tastiera (valore in secondi)
PACE = 0.02

; Intervallo di polling
POLLINTERVAL = 1

; Timeout (in millisecondi) per la ricezione di un pacchetto dal terminale
TIMEOUT = 1500

; ---------------------------

HCOM = OpenCom(COMPORT, COMSPEED, COMPARITY, COMDATA, COMSTOP, COMFLOW)

ASCII_STX = 2
ASCII_ETX = 3

WHILE 1
  ; Attende l'intervallo di poll
  SLEEP(POLLINTERVAL)
  
  Q = ControllaTerminale(TERMADDR)
  IF NEQ(Q, -1)
    WHILE QUEUELEN(Q)
      L = DEQUEUE(Q)
      ; Se si vuole selezionare una particola finestra, si può utilizzare 
      ; un'espressione simile alla seguente:
      ; W32SETFOCUS(W32FINDWINDOW("*ULTRAEDIT-32*"))
      W32SENDKEYS(KTrans(L) "{ENTER}")
      SLEEP(PACE)
    LOOP
    QUEUEFREE(Q)
  FI

; Variante con salvataggio/ripristino finestra corrente e selezione blocco notes
; IF NEQ(Q, -1)
;   ; Preleva la finestra corrente
;   HOLD = W32GETFOCUS()
;     
;   ; Cerca il blocco notes
;   H = W32FINDWINDOW("*Blocco note*")
;   IF EQ(H, 0)
;     ; Non trovato - lo lancia
;     W32SHELL("NOTEPAD.EXE")
;     ; Attende un secondo che si renda disponibile
;     SLEEP(1)
;     ; Cerca la sua finestra nuovamente
;     H = W32FINDWINDOW("*Blocco note*")
;   FI
;   ; Se trovato, seleziona la sua finestra ed invia i dati
;   IF NEQ(H, 0)
;     W32SETFOCUS(H)
;     WHILE QUEUELEN(Q)
;       L = DEQUEUE(Q)
;       W32SENDKEYS(KTrans(L) "{ENTER}")
;       SLEEP(PACE)
;     LOOP
;   ELSE
;     ; Impossibile aprire il blocco notes - dati persi!
;   FI
;   W32SETFOCUS(HOLD)
;   QUEUEFREE(Q)
; FI

LOOP
W32CLOSEHANDLE(HCOM)
ABORT 0   


FUNCTION KTrans(s)

l = STRLEN(s)
r = ""
FOR x = 1 TO l
  c = SUBSTR(s, x, 1)
  ; Mappatura dei caratteri speciali: caratteri che non si trovano
  ; sulla tastiera potrebbero richiedere l'introduzione della sequenza
  ; con ALT + numero
  SWITCH c STREQ
  ON "~"
    r = r "{ALT DOWN}{NUMPAD1}{NUMPAD2}{NUMPAD6}{ALT UP}"
  ON "{"
    r = r "{ALT DOWN}{NUMPAD1}{NUMPAD2}{NUMPAD3}{ALT UP}"    
  ON "}"
    r = r "{ALT DOWN}{NUMPAD1}{NUMPAD2}{NUMPAD5}{ALT UP}"
  ON "+", "^", "%", "(", ")", "[", "]"
    r = r "{" c "}"
  OTHER
    r = r c
  OFF
NEXT
RETURN r


FUNCTION OpenCom(comport, comspeed, comparity, comdata, comstop, comflow)

; Apertura seriale con parametri richiesti
hcom = W32CREATEFILE(comport, NOR(_W32_GENERIC_WRITE, _W32_GENERIC_READ), 0, \
                     _W32_OPEN_EXISTING, 0)

IF EQ(hcom, -1)
  CONSOLELN "Impossibile aprire " comport "."
  ABORT 2
FI

compar = VECNEW(13)
v = W32GETCOMSTATE(hcom, compar)

VECSET(compar, 2, comspeed)
v = NOR(_W32_COM_BINARY, _W32_COM_PARITY_ON)
SWITCH LEFT(comflow, 1) STRIEQ
ON "R"
  NOR(@v, _W32_COM_RTS_HANDSHAKE, _W32_COM_CTSFLOW_ON)
ON "X"
  NOR(@v, _W32_COM_XONXOFF_OUT, _W32_COM_XONXOFF_IN, _W32_COM_XOFF_CONTINUE)
ON "E"
  NOR(@v, _W32_COM_RTS_HANDSHAKE, _W32_COM_CTSFLOW_ON, \
          _W32_COM_XONXOFF_OUT, _W32_COM_XONXOFF_IN, _W32_COM_XOFF_CONTINUE)
ON "N"
OFF

VECSET(compar, 3, v)
VECSET(compar, 7, comdata)

SWITCH LEFT(comparity, 1) STRIEQ
ON "N"
  v = _W32_COM_PARITY_NONE
ON "E"
  v = _W32_COM_PARITY_EVEN
ON "O"
  v = _W32_COM_PARITY_ODD
ON "M"
  v = _W32_COM_PARITY_MARK
ON "S"
  v = _W32_COM_PARITY_SPACE
OFF

VECSET(compar, 8, v)
SWITCH comstop
ON 1
  VECSET(compar, 9, 0)
ON 2
  VECSET(compar, 9, 2)
OFF

v = W32SETCOMSTATE(hcom, compar)
VECFREE(compar)

IF v
  CONSOLELN "Errore nell'impostazione della porta (" W32GETLASTERROR() ")."
  W32CLOSEHANDLE(hcom)  
  ABORT 3
FI

tout = VECNEW(5)
VECSET(tout, 1, 0)
VECSET(tout, 2, 0)
VECSET(tout, 3, 0)
VECSET(tout, 4, 0)
VECSET(tout, 5, 0)
W32SETCOMTIMEOUTS(hcom, tout)
VECFREE(tout)

RETURN hcom


FUNCTION ControllaTerminale(staddr)

; Verifica se il terminale è disponibile
q = QUEUENEW()
W32READCOM(_HCOM, 0)
s = MKPacket(CHR(27) "0*" CHR(27), staddr)
W32WRITEFILE(_HCOM, s)
status = 0
RxPack(s, status, 1)

IF EQ(status, 0)
  ; Pacchetto ricevuto
  rec = INT(RIGHT(s, 3))
  bigrec = EQ(rec, 999)
      
  ; Cicla finché non abbiamo esaurito i tentativi
  s = ""
  WHILE STRNEQ(s, "<EOT>")
    SLEEP(0.06)
    RxPack(s, status, 0)
    IF EQ(status, -1)
      BREAK
    ELSE
      IF AND(STRNEQ(s, "<EOF>"), STRNEQ(s, "<EOT>"))
        ENQUEUE(q, s)
        DEC(@rec)
      ELSE
        IF STREQ(s, "<EOT>")
          BREAK
        FI
      FI
    FI
  LOOP
  
  IF EQ(status, 0)
    ; Devo ricevere <EOF> e <EOT> finali
    ; Invio la richiesta di cancellazione dati
    SLEEP(1)
    s = MKPacket(CHR(27) "1*" CHR(27), staddr)
    W32WRITEFILE(_HCOM, s)
    RxPack(s, status, 0)
    IF STREQ(s, "<DEL>")
      status = 0
    ELSE
      status = 1
    FI
  FI
FI

IF EQ(status, 0)
  RETURN q
FI
QUEUEFREE(q)
RETURN -1


FUNCTION RxPack(@s, @status, firstpoll)

; Riceve un pacchetto, con timeout.
WHILE 1
  IF firstpoll
    ; Attende in modo efficiente solo se siamo in fase di polling
    IF NOT(W32WAITRXCHAR(_HCOM, _TIMEOUT))
      s = ""
      status = -1
      RETURN
    FI  
  FI
  r = ""
  t1 = ADD(W32GETTICKCOUNT(), _TIMEOUT)
  WHILE 1
    r2 = W32READCOM(_HCOM, 0)
    IF STRLEN(r2)      
      r = r r2
      IF STREQ(RIGHT(r, 1), Chr(13))
        BREAK
      ELSE
        t1 = ADD(W32GETTICKCOUNT(), _TIMEOUT)
      FI
    ELSE
      IF GT(W32GETTICKCOUNT(), t1)
        ; "Timeout"
        s = ""
        status = -1
        RETURN
      FI
    FI
  LOOP
  ; Sincronizza l'inizio del pacchetto
  n = STRSTR(r, CHR(2))
  IF NEQ(n, 0)
    RESTFROM(@r, n)
  FI
  IF ChkPacket(r)
    ; "ACK"
    W32WRITEFILE(_HCOM, Chr(6))
    s = SUBSTR(r, 3, SUB(STRLEN(r), 6))
    status = 0
    RETURN
  ELSE
    ; "NAK"
    W32READCOM(_HCOM, 0)
    W32WRITEFILE(_HCOM, Chr(21))
  FI
LOOP
RETURN


FUNCTION ChkPacket(packet)

; Ritorna 1 o 0 in base alla correttezza del pacchetto
; (verifica del checksum)
RETURN STREQ(IdChecksum(LEFT(packet, SUB(STRLEN(packet), 3))), LEFT(RIGHT(packet, 3), 2))


FUNCTION IdChecksum(data)

; Calcola il checksum su un pacchetto Sysnet
r = 0
dl = STRLEN(data)
FOR x = 1 TO dl
  ADD(@r, ASC(SUBSTR(data, x, 1)))
NEXT
MOD(@r, 256)
RETURN PFORMAT("02X", r)


FUNCTION MKPacket(txline, staddr)

; Crea un pacchetto per l'indirizzo
chk = 0
tlen = STRLEN(txline)
for i = 1 to tlen
  ADD(@chk, ASC(SUBSTR(txline, i, 1)))
next
ADD(@chk, _ASCII_STX, staddr, _ASCII_ETX)
MOD(@chk, 256)
RETURN CHR(_ASCII_STX) CHR(staddr) txline CHR(_ASCII_ETX) PFORMAT("02X", chk) "\r"