
PROGRAM modem;
      {Written by Jack M. Wierda  Chicago Illinois
      This program is in the public domain.

      LANGUAGE: UCSD Pascal
      FILES:    MODEM3.PAS -- main program
                MDM3-Z80IO.Z80 -- serial line interface for Z80
                MDM3-8080IO.Z80 -- serial line interface for Intel 8080

      This program is basically a re-write in PASCAL of Ward Christensen's
Modem Program which was distributed in CP/M User's Group Volume 25. Identical
and compatible options are provided to allow this program to work directly
with Ward's program running under CP/M. One difference is that when sending
files the PASCAL or CP/M transfer mode must be selected. The PASCAL mode
transfers files between two systems running PASCAL, while the CP/M mode is
used when the receiving system is running CP/M. Basically the CP/M mode
provides the linefeeds required to make a PASCAL file compatible with CP/M.
When CP/M files are received they contain linefeeds, these can be deleted
using the editor to make the file compatible with PASCAL. CP/M files may also
contain tabs which the PASCAL editor does not expand.
      External assembly language routines are used to read the status, and read
or write the keyboard and modem ports. These routines are available as
separate files for the 8080 and Z80 processors. The port and flag definitions,
and the timing constant for the one second delay should be changed as required
for your particular hardware.
      The program has been tested with text files only, and may not work
correctly for code or other types of files.
      The PDP-10 mode transfers PASCAL files to a DEC SYSTEM-10 computer.}

CONST 
      nul = 0;
      soh = 1;
      ctrlc = 3;
      eot = 4;
      errormax = 5;
      retrymax = 5;
      ctrle = 5;
      ack = 6;
      tab = 9;
      lf = 10;
      cr = 13;
      ctrlq = 17;
      ctrls = 19;
      nak = 21;
      ctrlz = 26;
      space = 32;
      delete = 127;
      lastbyte = 127;
      timeout = 256;
      loopspersec = 1800       {1800 LOOPS PER SECOND AT 4MHZ};
      kbsp = 0           {KEYBOARD STATUS PORT};
      kbdrf = 128        {KEYBOARD DATA READY FLAG};
      kbdp = 1           {KEYBOARD DATA PORT};
      kbmask = 127       {KEYBOARD DATA MASK};
      dchdp = 128        {D. C. HAYES DATA PORT};
      dchmask = 255      {D. C. HAYES DATA MASK};
      dchsp = 129        {D. C. HAYES STATUS PORT};
      {STATUS PORT BIT ASSIGNMENTS}
      rrf     =    1   {RECEIVE REGISTER FULL};
      tre     =    2   {TRANSMIT REGISTER EMPTY};
      perr    =    4   {PARITY ERROR};
      ferr    =    8   {FRAMING ERROR};
      oerr    =    16  {OVERFLOW ERROR};
      cd      =    64  {CARRIER DETECT};
      nri     =    128 {NO RINGING INDICATOR};
      dchcp1 = 129       {D. C. HAYES CONTROL PORT 1};
      {CONTROL PORT 1 BIT ASSIGNMENTS}
      epe     =    1   {EVEN PARITY ENABLE};
      ls1     =    2   {LENGTH SELECT 1};
      ls2     =    4   {LENGTH SELECT 2};
      sbs     =    8   {STOP BIT SELECT};
      pi      =    16  {PARITY INHIBIT};
      dchcp2 = 130       {D. C. HAYES CONTROL PORT 2};
      {CONTROL PORT 2 BIT ASSIGNMENTS}
      brs     =    1   {BIT RATE SELECT};
      txe     =    2   {TRANSMIT ENABLE};
      ms      =    4   {MODE SELECT};
      es      =    8   {ECHO SUPPRESS};
      st      =    16  {SELF TEST};
      rid     =    32  {RING INDICATOR DISABLE};
      oh      =    128 {OFF HOOK};

VAR file1 : text;
    option, hangup, return, mode, baudrate, display, filemode : char;
    sector : ARRAY[0..lastbyte] OF integer;
    dchcw2 : integer;
    ovrn1, ovrn2, showrecv, showtrans : boolean;

FUNCTION stat(port,exr,mask:integer): boolean;
external;

FUNCTION input(port,mask:integer): integer;
external;

PROCEDURE output(port,data:integer);
external;

PROCEDURE sendline(sldata:integer);
BEGIN
  REPEAT
  UNTIL stat(dchsp,tre,tre);
  output(dchdp,sldata);
  IF showtrans
  THEN
    IF (sldata = cr) OR ((sldata >= space) AND (sldata <= delete))
    THEN
      write(chr(sldata))
END;

FUNCTION readline(seconds:integer): integer;

VAR j : integer;
BEGIN
  j := loopspersec * seconds;
  REPEAT
    j := j-1
  UNTIL (stat(dchsp,rrf,rrf)) OR (j = 0);
  IF j = 0
  THEN
    readline := timeout
  ELSE
    BEGIN
      j := input(dchdp,dchmask);
      IF showrecv
      THEN
	IF (j = cr) OR ((j >= space) AND (j <= delete))
	THEN
	  write(chr(j));
      readline := j
    END
END;

PROCEDURE sendstr(str:string);

VAR j: integer;
BEGIN
  FOR j := 1 TO length(str) DO
    sendline(ord(str[j]))
END;

FUNCTION uppercase(ch : char) : char;
BEGIN
  IF ch IN ['a'..'z']
  THEN
    uppercase := chr(ord(ch)-space)
  ELSE
    uppercase := ch
END;

PROCEDURE purgeline;

VAR j : integer;
BEGIN
  REPEAT
    j := input(dchdp,dchmask)      {PURGE THE RECEIVE REGISTER};
  UNTIL NOT stat(dchsp,rrf,rrf)
END;

PROCEDURE dchinitialize;
BEGIN
  writeln('Waiting for carrier');
  REPEAT
    BEGIN
      IF option IN ['R','S']
      THEN
	BEGIN
	  output(dchcp1,pi+ls2+ls1);
	  output(dchcp2,oh+rid+txe+dchcw2)
	END;
      IF option IN ['C','P','T']
      THEN
	BEGIN
	  output(dchcp1,ls2+epe);
	  output(dchcp2,oh+rid+txe+dchcw2)
	END
    END
  UNTIL (stat(dchsp,cd,cd)) OR (input(kbdp,kbmask) = ctrle);
  purgeline;
  writeln('Carrier detected')
END;

PROCEDURE makesector;

VAR j : integer;
    ch : char;
BEGIN
  j := 0;
  IF ovrn1
  THEN
    BEGIN
      sector[j] := cr;
      j := j+1
    END;
  IF ovrn2
  THEN
    BEGIN
      sector[j] := lf;
      j := j+1
    END;
  ovrn1 := false;
  ovrn2 := false;
  WHILE (NOT eof(file1)) AND (j <= lastbyte) DO
    BEGIN
      WHILE (NOT eoln(file1)) AND (j <= lastbyte) DO
	BEGIN
	  read(file1,ch);
	  IF ord(ch) <> lf
	  THEN
	    BEGIN
	      sector[j] := ord(ch);
	      j := j+1
	    END
	END;
      IF eoln(file1)
      THEN
	BEGIN
	  readln(file1);
	  IF filemode IN ['P']
	  THEN
	    IF j <= lastbyte
	    THEN
	      BEGIN
		sector[j] := cr;
		j := j+1
	      END
	    ELSE
	      ovrn1 := true
	  ELSE
	    BEGIN
	      IF j <= (lastbyte-1)
	      THEN
		BEGIN
		  sector[j] := cr;
		  sector[j+1] := lf;
		  j := j+2
		END
	      ELSE
		IF j = lastbyte
		THEN
		  BEGIN
		    sector[j] := cr;
		    j := j+1;
		    ovrn1 := true
		  END
		ELSE
		  IF j > lastbyte
		  THEN
		    BEGIN
		      ovrn1 := true;
		      ovrn2 := true
		    END
	    END
	END
    END;
  CASE filemode OF
    'P' : IF j <= lastbyte
	  THEN
	    FOR j := j TO lastbyte DO
	      sector[j] := space;
    'C' : IF j <= lastbyte
	  THEN
	    FOR j := j TO lastbyte DO
	      sector[j] := ctrlz
  END
END;

PROCEDURE termcomp;

VAR kbdata, dchdata : integer;
    crflag : boolean;
BEGIN
  crflag := false;
  dchinitialize;
  WHILE stat(dchsp,cd,cd) AND (kbdata <> ctrle) DO
    BEGIN
      IF stat(kbsp,kbdrf,kbdrf)
      THEN
	BEGIN
	  kbdata := input(kbdp,kbmask);
	  IF option IN ['C']
	  THEN
	    write(chr(kbdata));
	  output(dchdp,kbdata)
	END;
      IF stat(dchsp,rrf,rrf)
      THEN
	BEGIN
	  dchdata := input(dchdp,dchmask);
	  IF option IN ['C']
	  THEN
	    output(dchdp,dchdata);
	  IF dchdata = cr
	  THEN
	    crflag := true;
	  IF (dchdata = lf) AND crflag
	  THEN
	    crflag := false
	  ELSE
	    write(chr(dchdata))
	END
    END
END;

PROCEDURE pdp10;

VAR wait10 : boolean;
    dchdata : integer;
    ch : char;
    filename, pdp10file : string;
BEGIN
  showrecv := false;
  showtrans := true;
  wait10 := false;
  write('Filename.Ext ? ');
  readln(filename);
  reset(file1,filename);
  IF option IN ['P']
  THEN
    BEGIN
      write('PDP-10 Filename.Ext ? ');
      readln(pdp10file);
      dchinitialize;
      sendline(cr);
      sendstr('R PIP');
      sendline(cr);
      REPEAT
      UNTIL readline(5) IN [ord('*'),timeout];
      sendstr(pdp10file);
      sendstr('=TTY:');
      sendline(cr)
    END
  ELSE
    BEGIN
      write('UNIX Filename.Ext ? ');
      readln(pdp10file);
      dchinitialize;
      sendline(cr);
      sendstr('cat > ');
      sendstr(pdp10file);
      sendline(cr)
    END;
  WHILE (NOT eof(file1)) AND (stat(dchsp,cd,cd)) DO
    BEGIN
      WHILE NOT eoln(file1) DO
	BEGIN
	  IF NOT wait10
	  THEN
	    BEGIN
	      read(file1,ch);
	      sendline(ord(ch))
	    END;
	  IF stat(dchsp,rrf,rrf)
	  THEN
	    BEGIN
	      dchdata := input(dchdp,dchmask);
	      IF dchdata = ctrls
	      THEN
		wait10 := true;
	      IF dchdata = ctrlq
	      THEN
		wait10 := false
	    END
	END;
      readln(file1);
      sendline(cr)
    END;
  close(file1);
  REPEAT
  UNTIL readline(1)=timeout;
  IF option IN ['P']
  THEN
    BEGIN
      sendline(ctrlz);
      sendline(ctrlc);
    END
  ELSE
    BEGIN
      sendline(eot)
    END;
  termcomp
END;

PROCEDURE sendfile;

VAR j, k, sectornum, counter, checksum : integer;
    filename : string;
BEGIN
  write('Filename.Ext ? ');
  readln(filename);
  reset(file1,filename);
  sectornum := 1;
  dchinitialize;
  ovrn1 := false;
  ovrn2 := false;
  REPEAT
    counter := 0;
    makesector;
    REPEAT
      writeln;
      writeln('Sending sector ', sectornum);
      sendline(soh);
      sendline(sectornum);
      sendline(-sectornum-1);
      checksum := 0;
      FOR j := 0 TO lastbyte DO
	BEGIN
	  sendline(sector[j]);
	  checksum := (checksum + sector[j]) MOD 256
	END;
      sendline(checksum);
      purgeline;
      counter := counter + 1;
    UNTIL (readline(10) = ack) OR (counter = retrymax);
    sectornum := sectornum + 1
  UNTIL (eof(file1)) OR (counter = retrymax);
  IF counter = retrymax
  THEN
    BEGIN
      writeln;
      writeln('No ACK on sector')
    END
  ELSE
    BEGIN
      counter := 0;
      REPEAT
	sendline(eot);
	counter := counter + 1
      UNTIL (readline(10) = ack) OR (counter = retrymax);
      IF counter = retrymax
      THEN
	BEGIN
	  writeln;
	  writeln('No ACK on EOT')
	END
      ELSE
	BEGIN
	  writeln;
	  writeln('Transfer complete')
	END
    END;
  close(file1)
END;

PROCEDURE readfile;

VAR j, firstchar, sectornum,sectorcurrent, sectorcomp, errors,
    checksum : integer;
    errorflag : boolean;
    filename : string;
BEGIN
  write('Filename.Ext ? ');
  readln(filename);
  rewrite(file1,filename);
  sectornum := 0;
  errors := 0;
  dchinitialize;
  sendline(nak);
  sendline(nak);
  REPEAT
    errorflag := false;
      REPEAT
	firstchar := readline(20)
      UNTIL firstchar IN [soh,eot,timeout];
    IF firstchar = timeout
    THEN
      BEGIN
	writeln;
	writeln('SOH error');
      END;
    IF firstchar = soh
    THEN
      BEGIN
	sectorcurrent := readline(1);
	sectorcomp := readline(1);
	IF (sectorcurrent+sectorcomp)=255
	THEN
	  BEGIN
	    IF (sectorcurrent=sectornum+1)
	    THEN
	      BEGIN
		checksum := 0;
		FOR j := 0 TO lastbyte DO
		  BEGIN
		    sector[j] := readline(1);
		    checksum := (checksum+sector[j]) MOD 256
		  END;
		IF checksum=readline(1)
		THEN
		  BEGIN
		    FOR j := 0 TO lastbyte DO
		      write(file1,chr(sector[j]));
		    errors := 0;
		    sectornum := sectorcurrent;
		    IF display <> 'R'
		    THEN
		      BEGIN
			writeln;
			writeln('Received sector ',sectorcurrent)
		      END;
		    sendline(ack)
		  END
		ELSE
		  BEGIN
		    writeln;
		    writeln('Checksum error');
		    errorflag := true
		  END
	      END
	    ELSE
	      IF (sectorcurrent=sectornum)
	      THEN
		BEGIN
		  REPEAT
		  UNTIL readline(1)=timeout;
		  writeln;
		  writeln('Received duplicate sector ', sectorcurrent);
		  sendline(ack)
		END
	      ELSE
		BEGIN
		  writeln;
		  writeln('Synchronization error');
		  errorflag := true
		END
	  END
	ELSE
	  BEGIN
	    writeln;
	    writeln('Sector number error');
	    errorflag := true
	  END
      END;
    IF (errorflag=true)
    THEN
      BEGIN
	errors := errors+1;
	REPEAT
	UNTIL readline(1)=timeout;
	sendline(nak)
      END;
  UNTIL (firstchar IN [eot,timeout]) OR (errors = errormax);
  IF (firstchar = eot) AND (errors < errormax)
  THEN
    BEGIN
      sendline(ack);
      close(file1,lock);
      writeln;
      writeln('Transfer complete')
    END
  ELSE
    BEGIN
      close(file1);
      writeln;
      writeln('Aborting')
    END
END;
BEGIN
  writeln('Modem, 7-July-79');
  REPEAT
    REPEAT
      write('Option : C(omputer), P(DP-10), R(eceive), S(end), T(erminal)');
      write(', U(nix) ? ');
      read(option);
      option := uppercase(option);
      writeln
    UNTIL option IN ['C','P','R','S','T','U'];
    REPEAT
      write('Mode : A(nswer), O(riginate) ? ');
      read(mode);
      mode := uppercase(mode);
      writeln
    UNTIL mode IN ['A','O'];
    IF mode IN ['O']
    THEN
      dchcw2 := ms
    ELSE
      dchcw2 := 0;
    REPEAT
      write('Baud rate : 1(00), 3(00) ? ');
      read(baudrate);
      writeln
    UNTIL baudrate IN ['1','3'];
    IF baudrate='3'
    THEN
      dchcw2 := dchcw2+brs;
    IF option IN ['R','S']
    THEN
      BEGIN
	REPEAT
	  write('Display : N(o), R(eceived), T(ransmitted) data ? ');
	  read(display);
	  display := uppercase(display);
	  writeln
	UNTIL display IN ['N','R','T'];
	IF option = 'S'
	THEN
	  BEGIN
	    REPEAT
	      write('File mode : C(pm), P(ascal) ? ');
	      read(filemode);
	      filemode := uppercase(filemode);
	      writeln
	    UNTIL filemode IN ['C','P']
	  END;
	CASE display OF
	  'N': BEGIN
		 showrecv := false;
		 showtrans := false
	       END;
	  'R': BEGIN
		 showrecv := true;
		 showtrans := false
	       END;
	  'T': BEGIN
		 showrecv := false;
		 showtrans := true
	       END
	END
      END;
    CASE option OF
      'C': termcomp;
      'P': pdp10;
      'R': readfile;
      'S': sendfile;
      'T': termcomp;
      'U': pdp10
    END;
    REPEAT
      writeln;
      write('Hangup : Y(es), N(o) ? ');
      read(hangup);
      hangup := uppercase(hangup);
      writeln
    UNTIL hangup IN ['Y','N'];
    IF hangup IN ['Y']
    THEN
      output(dchcp2,0);
    REPEAT
      writeln;
      write('Return to system : Y(es), N(o) ? ');
      read(return);
      return := uppercase(return);
      writeln
    UNTIL return IN ['Y','N'];
  UNTIL return IN ['Y']
END
.
