; MXM-UD10.ASM - MEX1.10 Smart modem overlay for UDS 212 A/D modem
;
;       TO USE: Edit the UDSNEW parameter for your particular modem.
;               Assemble with ASM.COM or equivalent assembler.  Then
;               use MLOAD21.COM (or later) to combine this overlay
;               with the original MEXxx.COM file, and your computer
;               specific overlay file.( MXO-xxxx in the example below).
;
;               A>MLOAD21 MEX.COM=MEXxx.COM,MXM-UD10,MXO-xxxx
;
;       >>>>  Report bugs to fortfone rcp/m 414-563-9932 <<<<<<<<<<<<
;
; =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =
;
YES     EQU     0FFH
NO      EQU     000H
;
;       UDSNEW - If yes, this is a revised UDS 212 A/D.  The new
;       models can be disconnected by sending the string 'XXXT' followed
;       by carriage return.  Older models can only be disconnected by
;       dropping DTR low for at least 60 msec.  Define UDSNEW to be yes
;       if you want to use the 'XXXT' disconnect string,  Define UDSNEW
;       to be NO to use the DTR disconnect method. (The computer overlay
;       must intercept the DISCV vector, drop DTR, then JMP to the original
;       DISCV vector address.)
;
UDSNEW  EQU     YES
;
;
;  *NOTE* As an owner of a UDS 212-A/D modem you are probably aware
;       of the consequences regarding training the modem at one baud rate
;       and then changing baud rates without untraining the modem first.
;       The poor thing will go crazy.  Using this overlay *requires* the
;       user to always disconnect a call before changing baud rates.
;       The Ctl-J + N command will disconnect the modem and then untrain
;       the ACU.  Do *NOT* make a completed call, exit terminal mode
;       with Ctl-J + E, and then try to change baud rates. (Incomplete or
;       aborted calls don't count, the modem is automatically untrained
;       if the call isn't completed.)  If you're in doubt, enter terminal
;       mode and give the disconnect command, Ctl-J + N, this will insure
;       the modem is disconnected and 'untrained'.
;
;---------------
;
; Misc equates
;
BDOS            EQU     005H
CR:             EQU     0DH             ;carriage return
LF:             EQU     0AH             ;linefeed
;
;---------------
;
; MEX service processor equates
;
MEX     EQU     0D00H           ; MEX SERVICE PROCESSOR ENTRY POINT
INMDM   EQU     255             ;get char from port to A, CY=no more in 100 ms
TIMER   EQU     254             ;delay 100ms * reg B
TMDINP  EQU     253             ;B=# secs to wait for char, cy=no char
CHEKCC  EQU     252             ;check for ^C from KBD, Z=present
SNDRDY  EQU     251             ;test for modem-send ready
RCVRDY  EQU     250             ;test for modem-receive ready
SNDCHR  EQU     249             ;send a chara (B) to the modem (after sndrdy)
RCVCHR  EQU     248             ;recv a char from modem (after rcvrdy)
LOOKUP  EQU     247             ;table search: see CMDTBL comments for info
PARSFN  EQU     246             ;parse filename from input stream
BDPARS  EQU     245             ;parse baud-rate from input stream
SBLANK  EQU     244             ;scan input stream to next non-blank
EVALA   EQU     243             ;evaluate numeric from input stream
LKAHED  EQU     242             ;get nxt char w/o removing from input
GNC     EQU     241             ;get char from input, cy=1 if none
ILP     EQU     240             ;inline print
DECOUT  EQU     239             ;decimal output
PRBAUD  EQU     238             ;print baud rate
PRNTBL  EQU     237             ;print command table in columnar format
PRID    EQU     236             ;print MEX ID on console
;
CONOUT  EQU     2               ; simulated BDOS functions, for MEX
PRINT   EQU     9
INBUF   EQU     10
;
;---------------
;
;       PDIAL completion codes to be returned to MEX
;
PCARR   EQU     0       ; CARRIER DETECT, CONNECTION MADE
PBSY    EQU     1       ; PHONE IS BUSY
PNOANS  EQU     2       ; NO ANSWER
PABRT   EQU     3       ; KEYBD ABORT
PERR    EQU     4       ; MODEM ERROR
PNRING  EQU     5       ; NO RING
PNDIAL  EQU     6       ; NO DIAL TONE
;
;----------------
;
;       MEX Baud rate codes
;
BD110   EQU     0
BD300   EQU     1
BD450   EQU     2
BD600   EQU     3
BD710   EQU     4
BD1200  EQU     5
BD2400  EQU     6
BD4800  EQU     7
BD9600  EQU     8
BD1920  EQU     9
;
;-----------------
;
;       Entry points in the computer overlay area
;
       ORG 0162H               ; MEX main overlay
       JMP     PDIAL           ;jump to modem dialing routine  162H
       JMP     MDMDSC          ;jump to modem disconnect routine    165H
MSPEED: EQU     0107H           ;MEX speed code byte
SET8BT: EQU     0171H           ;Set serial port for 8-bit data, no parity
SETNRM: EQU     0174H           ;Set serial port for normal operation.
;
;----------------
;
;       New MEX 1.10 Smartmodem overlay patch points.
;
       ORG 0D55H       ; Fixed at address 0D55H
       DW      DUMMY   ; SMINIT - not used.
       DW      SSET    ; SSET command processor
       DW      DUMMY   ; SMEXIT - not used.
;
;---------------
;
;       Smartmodem code begins at 0900H
;
       ORG 0900H
;
;---------------
;
;       PDIAL - Actual dial service routine.
;
;       Entry point to dialing routine. This routine saves all the digits in
;       a buffer, dialing the number only after all digits/commands have been
;       received.  It then monitors the ACU call progress responses and returns
;       the proper success/failure codes to MEX.
;
;       This routine is called by MEX with a dialing digit (ASCII) in the
;       A register.  The routine can use all registers.  The routine is called
;       with the special value  254 (decimal) to indicate the beginning of a
;       dialing sequence.  The special value 255 indicates the end of the
;       dialing sequence, and requires the PDIAL routine to return one of the
;       MEX dialing completion codes in the A register.
;
PDIAL:  CPI     254
       JZ      STDIAL  ; start of dialing sequence, reset pointers
;
       CPI     255
       JNZ     DDIGIT  ; store digit, will dial the whole number later
;
;       all digits recieved, time to dial the number.
;
       XRA     A       ; Null terminate the phone number
       CALL    DDIGIT
;
;       check for 300/1200 baud rate. Modem ACU will not work at other rates.
;
       LDA     MSPEED
       CPI     BD300
       JZ      PDIAL0
       CPI     BD1200
       JZ      PDIAL0
;
       CALL    ILPRT
       DB      CR,LF,'Baud rate must be 300/1200 for Auto Dial - ',0
       JMP     DLERR   ; return the modem error code
;
;       Return error code if an invalid dialing character was received.
;
PDIAL0: LDA     ERRFLG
       ORA     A
       JZ      PDIAL1
;
       CALL    ILPRT
       DB      CR,LF,'Invalid Digit/Chara in dial string - ',0
       JMP     DLERR   ; return the modem error code
;
PDIAL1: CALL    FLUSH   ; Flush the modem input
       CALL    SET8BT  ; Set the serial port for 8-bit operation.
       CALL    FLUSH   ; Flush input again.
;
;       First step is to train the modem to the current baud rate.
;
       CALL    MDMTRN  ; Send the modem training command 'EN'
       JC      DLERR   ; Exit upon training error.
;
       LXI     H,DOPTS ; Send the options commands
       CALL    SNDSTR
       CALL    FLUSH   ; Ignore modem responses
;
;       Now dial the number
;
       MVI     A,'D'   ; Dialing command = 'D'
       CALL    SEND1
       LXI     H,DPREFX ; Send the dialing prefix
       CALL    SNDSTR
       LXI     H,DNUMBR ; Send the phone number
       CALL    SNDSTR
       MVI     A,CR    ; Send a Return to start the dialing procedure
       CALL    SEND1
;
;       Begin call progress monitoring
;
       CALL    FLUSH   ; Flush the "DIALING - " message
; *** Note: cannot abort with Control-C while waiting on first dial tone ***
       MVI     B,30    ; get a chara, time out after 30 seconds
       MVI     C,TMDINP
       CALL    MEX
       JC      DLERR   ; Timeout = Modem error
       CPI     'D'
       JNZ     PDLER   ; No Dial tone error - exit
       CALL    FLUSH   ; Flush the "D.T. - " message
;
;       Got past first Dial Tone, now look for the following completion msgs.
;       If there is no response in 25.5 seconds, report a modem error to MEX.
;
;       COMPLETE
;       NO ABT - ABORT
;       BUSY - ABORT
;       NO D.T. - ABORT
;       NO ANSWER - ABORT
;
PDLP0:  MVI     B,0     ; no response timeout counter 0=25.5 sec
PDLP1:  DCR     B
       JZ      DLERR   ; Exit upon timeout error
       PUSH    B
       MVI     C,CHEKCC ; Check for Ctl-C from Keyboard
       CALL    MEX
       JZ      PDABRT
       MVI     C,INMDM
       CALL    MEX
       POP     B
       JC      PDLP1   ; try again, if no chara
;
PDLP2:  CPI     'C'     ; Check for the letter 'C' as in 'COMPLETE'
       JNZ     PDLP3
;
PDL20:  MVI     C,INMDM ; Flush charas until LF or a timeout is rcv'd.
       CALL    MEX
       JC      PDL21
       CPI     LF
       JNZ     PDL20
PDL21:  MVI     A,PCARR ; Return carrier detect (success) code to MEX
       CALL    SETNRM  ; reset serial port for normal operation.
       RET
;
PDLP3:  CPI     'B'     ; Check for the letter 'B' as in 'BUSY'
       JNZ     PDLP4
       MVI     A,PBSY ; Return the busy error code
       JMP     MDMRST
;
PDLP4:  CPI     '.'     ; Check for '.' as in 'NO D.T.'
       JNZ     PDPLP5
PDLER:  MVI     A,PNDIAL ; Return the No dial tone error code
       JMP     MDMRST
;
PDPLP5: CPI     'A'     ; Check for 'A' as in 'NO ANSWER/NO ABT'
       JNZ     PDLP0   ; Get/check another chara from modem
       MVI     A,PNOANS ; Return the no answer error code
       JMP     MDMRST
;
DLERR:  MVI     A,PERR  ; Return the modem error code
       JMP     MDMRST
;
PDABRT: POP     B
       MVI     A,PABRT ; Keyboard abort code
       JMP     MDMRST
;
;---------------
;
;       Start the dialing process, reset the dial pointer, error flag
;
STDIAL: LXI     H,DNUMBR        ; Reset the dial string pointer
       SHLD    DPTR
       XRA     A               ; Clear the error flag
       STA     ERRFLG
       RET
;
;---------------
;
;       Add another digit to the phone number string
;
DDIGIT: CPI     ','     ; map ',' into 'D' (delay)
       JNZ     DDGT1
       MVI     A,'D'
;
; Check digit/chara for validity
;
DDGT1:  CALL    CHKDGT
       JC      DGTERR
;
; Load valid digit into phone num string
; Dashes and spaces are ignored...
;
       CPI     '-'
       RZ
       CPI     ' '
       RZ
       LHLD    DPTR
       MOV     M,A
       INX     H
       SHLD    DPTR
       RET
;
; Invalid digit, set flag and exit.
;
DGTERR: MVI     A,PERR  ; Must use the modem error code
       STA     ERRFLG
       RET
;
;---------------
;
;       CHKDGT - Check for valid dialing digit/char
;       Returns CY=1 if invalid chara
;
CHKDGT: MOV     B,A     ; Save orig char in B
       LXI     H,CHRSTR
       MOV     A,M
;
DGTLP:  CMP     B
       RZ              ; Character is ok, return CY=0
       INX     H       ; Chk for end of table
       MOV     A,M
       ORA     A
       JNZ     DGTLP
;
       STC             ; CY=1 indicates error
       RET
;
CHRSTR: DB      0       ; 0 is a valid digit, must be first digit of table.
       DB      '0123456789' ; Valid dialing digits
       DB      'PDWT'  ; Special ACU commands, Pulse,Delay,Wait,Tone
       DB      'pdwt'  ; Lower case versions okay.
       DB      '- '    ; Allow dash and space for clarity
       DB      0       ; end of table
;
TRNSTR: DB      'EN',0
;
CLRSTR: DB      'OG0   ',0
;
CCODE:  DB      0       ; Completion code byte
;
DNUMBR: DS      50      ; storage for phone number
;
DPTR:   DW      DNUMBR  ; Pointer into DNUMBR for next digit
;
ERRFLG: DB      0       ; Digit error flag
;
;---------------
;
; Support routines
;
;       Send null terminated string to modem
;
SNDSTR: MOV     A,M
       ORA     A
       RZ              ; Null terminated string
       INX     H
       PUSH    H
       CALL    SEND1
       POP     H
       JMP     SNDSTR
;
;       Send One chara to modem
;
SEND1:  PUSH    PSW
SND1LP: MVI     C,SNDRDY
       CALL    MEX
       JNZ     SND1LP
       POP     PSW
       MOV     B,A
       MVI     C,SNDCHR
       JMP     MEX
;
;       Table Search, - for SSET command parsing
;
TSRCH:    MVI   C,LOOKUP
         JMP   MEX
;
;       In-Line Print, null-terminated string
;
ILPRT:  MVI     C,ILP
       JMP     MEX
;
;       CRLF - Send CR,LF to console
;
CRLF:   CALL    ILPRT
       DB      CR,LF,0
       RET
;
;---------------
;
;       'Train' the modem ACU
;
MDMTRN: LXI     H,TRNSTR        ; Send the training string
       CALL    SNDSTR
       CALL    GCOLON  ; Check for a ':' from the modem
       RC              ; CY=1 indicates training error
       XRA     A       ; CY=0
       RET
;
;---------------
;
;       MDMRST - Reset the ACU to an untrained state.  Aborts any
;       dialing operation in progress. Preserves register A
;
;       Send 'Q' to abort dial operation
;       Send 'OG0' to untrain the ACU. (if ACU is active)
;
MDMRST: EI
       PUSH    PSW     ; Save 'A', trashes all other reg's
       CALL    SET8BT  ; Set port for 8 bit data
       CALL    FLUSH
       MVI     A,'Q'   ; Abort dialing in progress
       CALL    SEND1   ;
       CALL    GCOLON  ; Check if ACU is alive
       JC      MDMRS1  ;
       LXI     H,CLRSTR ; Send the 'OG0' command string
       CALL    SNDSTR  ;
MDMRS1: CALL    FLUSH
       CALL    SETNRM  ;reset the serial port for normal oper.
       CALL    FLUSH
       POP     PSW     ; restore 'A'
       RET
;
;---------------
;
;       Get a colon response from the modem, if there is a character
;       timeout before receiving colon, return CY=1 to indicate error
;       All other characters are accepted, up to a maximum of 30
;
GCOLON: MVI     C,INMDM
       MVI     B,30
GCOLP:  PUSH    B
       CALL    MEX
       POP     B
       RC              ; return CY=1 upon modem timeout..
       CPI ':'
       RZ              ; return CY=0 upon receving ':'
       DCR     B
       STC
       RZ              ; return CY=1 upon too many characters
       JMP     GCOLP
;
;---------------
;
;       Flush charas from modem, returns upon timeout.
;
FLUSH:  MVI     C,INMDM ; Read until there are no more charas in 100msec
       CALL    MEX
       JNC     FLUSH
       RET
;
;---------------
;
;       Disconnect modem from phone line and untrain the ACU.
;
;       Uses the Disconnect string if the modem has the new EPROM.
;       Otherwise the computer overlay must drop DTR for at least
;       60 msec to disconnect the modem.
;
MDMDSC: PUSH    PSW
;
       IF UDSNEW
         CALL  SET8BT
         CALL  FLUSH
         LXI   H,DSCSTR
         CALL  SNDSTR
       ENDIF   ; UDSNEW
;
       CALL    MDMRST  ; untrain the modem ACU
       CALL    FLUSH   ; extra delay for modem to reset itself.
       POP     PSW
       RET
;
       IF UDSNEW
DSCSTR:   DB    'XXXT',CR,0
       ENDIF   ; UDSNEW
;
;--------------------------------
;
; SSET command processing routine
;
SSET:   MVI     C,SBLANK
       CALL    MEX
       JC      SETSHO
       LXI     D,CMDTBL
       CALL    TSRCH
       PUSH    H
       RNC
       POP     H
SETERR: CALL    ILPRT
       DB      CR,LF,'SSet command error',CR,LF,0
       RET
;
;
CMDTBL: DB      '?'+80H
       DW      SETHLP
       DB      'PREFI','X'+80H ; dialing prefix string
       DW      SETPRE
       DB      'OPTION','S'+80H ; Options command string
       DW      SETOPT
       ; INSERT ADDITIONAL SSET COMMANDS HERE
       DB      0               ;End of table
;
;---------------
;
; SSET <no-args> display status of all 'sset' items
;
SETSHO: CALL    CRLF
       LXI     H,SHOTBL
SETSLP: MOV     E,M
       INX     H
       MOV     D,M
       INX     H
       MOV     A,D
       ORA     E
       JZ      CRLF    ;Exit thru CRLF
       PUSH    H
       XCHG
       CALL    GOHL
       CALL    CRLF
       POP     H
       JMP     SETSLP
;
GOHL:   PCHL
;
; table of Show routine addresses
;
SHOTBL:   DW    OPTSHO  ; Option commands
         DW    PRESHO  ; show the dialing prefix string
       ; Add other status routines here
         DW    0       ; End of table
;
;-----------------------
;
; SSET ? print available SSET commands
;
SETHLP:   CALL  ILPRT
         DB    CR,LF,'Available SSET Commands: (UDS-212 A/D)',0
;
         LXI   H,CMDTBL
         MVI   C,PRNTBL
         CALL  MEX
         CALL  CRLF
         JMP   CRLF    ; Exit thru CRLF
;
;--------------
;
; SSET PREFIX {string} Change the dialing prefix string, default is 'P'
;       other useful prefixes are T, P9W, etc.
;
; SSET PREFIX                   Print current string.
; SSET PREFIX ""                Set prefix to null string
; SSET PREFIX string            Set prefix to 'string'
; SSET PREFIX "string"          (Quotes are optional, same as above)
;
SETPRE:   MVI   C,SBLANK        ; Isolate the space terminated string
         CALL  MEX
         JC    PRESHO          ; Show current string
;
         MVI   B,(ENDPRE-DPREFX)-2     ; B=Max length of string
         LXI   H,DPREFX
         MVI   C,GNC           ; MEX service code for get next chara
SETPLP:   PUSH  H
         PUSH  B
         CALL  MEX             ; Fetch chara, CY=1 means end of string
         JC    SETPEX
; Verify chara as a valid dialing digit.
         CPI   '"'
         CNZ   CHKDGT
         JNC   SETP1
; Invalid digit, report error, terminate PREFIX string.
         CALL  ILPRT
         DB    CR,LF,'Invalid character in PREFIX string'
         DB    CR,LF,0
         JMP   SETPEX
; Store good digit, except quote marks
SETP1:    POP   B
         POP   H
         CPI   '"'
         JZ    SETPLP
         MOV   M,A             ; Store new chara
         INX   H
         DCR   B               ; Check for string too long
         JNZ   SETPLP          ; get next chara
         JMP   SETPX1          ; Exit, if no more room

SETPEX:   POP   B
         POP   H
SETPX1:   MVI   M,0             ; Finished. Insert 0, insert 'RET'
         INX   H
         MVI   M,0C9H          ; Insert RET instruction
       ; Fall thru to PRESHO
;
;---------------
;
; PRESHO  Show current dial prefix string.
;
PRESHO:   CALL  ILPRT
         DB    'Dialing prefix: ',0
         CALL  ILPRT
DPREFX:   DB    'P'     ; dialing prefix string, default = 'P'
         DB    0,0,0,0,0,0,0,0,0,0,0 ; Max of 12 charas
         DB    0       ; String must be null-terminated, *plus* a RET inst.
         RET
ENDPRE:
;
;--------------
;
; SSET OPTIONS {string} Change the dialing options string, default is 'OF1'
;       other useful options are OBx
;
; SSET OPTIONS Print current string.
; SSET OPTIONS ""               Set options to null string
; SSET OPTIONS string           Set options to 'string'
; SSET OPTIONS "string"         (Quotes are optional, same as above)
;
SETOPT:   MVI   C,SBLANK        ; Isolate the space terminated string
         CALL  MEX
         JC    OPTSHO          ; Show current string
;
         MVI   B,(ENDOPT-DOPTS)-2      ; B=Max length of string
         LXI   H,DOPTS
         MVI   C,GNC           ; MEX service code for get next chara
SETOLP:   PUSH  H
         PUSH  B
         CALL  MEX             ; Fetch chara, CY=1 means end of string
         POP   B
         POP   H
         JC    SETOP1
         CPI   '"'             ; ignore quote charas
         JZ    SETOLP
         MOV   M,A             ; Store new chara
         INX   H
         DCR   B               ; Check for string too long
         JNZ   SETOLP          ; get next chara
;
SETOP1:   MVI   M,0             ; Finished. Insert 0, insert 'RET'
         INX   H
         MVI   M,0C9H          ; Insert RET instruction
       ; Fall thru to PRESHO
;
;--------------
;
; OPTSHO
;
OPTSHO: CALL ILPRT
       DB      'Modem Options : ',0
       CALL ILPRT
DOPTS:  DB 'OB1',0,0,0,0,0,0    ; 18 charas max, default=OB1
       DB 0,0,0,0,0,0,0,0,0
       DB 0
       RET
ENDOPT:
;
;----------------
;
DUMMY:  RET     ; Do nothing routine.
;
;
;
       END