-- Copyright (C) 1983 by Xerox Corporation. All rights reserved.
-- CornPopper.mesa, HGM, 26-Nov-83 1:23:02
DIRECTORY
Ascii USING [ControlA, CR, LF, SP],
Process USING [Pause, SecondsToTicks],
Stream USING [Delete, GetChar, Handle, PutChar, PutString, SendNow, TimeOut],
String USING [AppendString, AppendChar],
System USING [
AdjustGreenwichMeanTime, GetClockPulses, GetGreenwichMeanTime,
gmtEpoch, GreenwichMeanTime, Pulses, PulsesToMicroseconds],
Time USING [Pack, Unpack, Unpacked],
PopCorn USING [ErrorType],
PupStream USING [PupByteStreamCreate, SecondsToTocks, StreamClosing],
PupTypes USING [PupAddress];
CornPopper: PROGRAM
IMPORTS Process, Stream, String, System, Time, PupStream
EXPORTS PopCorn =
BEGIN
Error: PUBLIC ERROR [why: PopCorn.ErrorType, text: LONG STRING] = CODE;
GetClockOffset: PUBLIC PROCEDURE [where: PupTypes.PupAddress, tries: CARDINAL]
RETURNS [msDiff: LONG INTEGER, msDelay: LONG CARDINAL] =
BEGIN
sh: Stream.Handle ← NIL;
IF System.GetGreenwichMeanTime[] = System.gmtEpoch THEN
ERROR Error[spare1, "Clock not set"L];
BEGIN ENABLE
BEGIN
PupStream.StreamClosing =>
BEGIN
SELECT why FROM
transmissionTimeout => ERROR Error[timeout, "Timeout during transmission"L];
remoteReject => ERROR Error[rejecting, text];
ENDCASE => ERROR Error[pupConfusion, text];
END;
Stream.TimeOut => ERROR Error[timeout, "Stream Timeout"L];
UNWIND => IF sh # NIL THEN Stream.Delete[sh];
END;
sh ← PupStream.PupByteStreamCreate[where, PupStream.SecondsToTocks[5]];
IF sh # NIL THEN
BEGIN
hits: CARDINAL ← 0;
err: PopCorn.ErrorType;
string: STRING = [200];
Stream.PutString[sh, "B2400"L]; -- DLS: Set Baud Rate
Stream.PutChar[sh, Ascii.CR];
Stream.PutChar[sh, 'C]; -- DLS: Connect
Stream.PutChar[sh, 'R]; -- Box: Reset everything (send every second)
Stream.SendNow[sh];
Process.Pause[Process.SecondsToTicks[2]];
Stream.PutChar[sh, 'T]; -- Box: Send current time (once)
Stream.SendNow[sh];
[] ← FlushThePipe[sh]; -- Skip over DLS header and partial lines
Stream.PutString[sh, "F000:00:00:00.000Q"L]; -- Box: Set Format
msDelay ← LAST[LONG CARDINAL];
FOR i: CARDINAL IN [0..20) UNTIL hits = tries DO
thisDelta, thisDelay: LONG CARDINAL;
[thisDelta, thisDelay] ← PokeBox[sh ! Error =>
BEGIN
err ← why;
string.length ← 0;
String.AppendString[string, text];
LOOP;
END ];
hits ← hits + 1;
IF thisDelay < msDelay THEN
BEGIN
msDelay ← thisDelay;
msDiff ← thisDelta;
END;
ENDLOOP;
IF hits = 0 THEN ERROR Error[err, string];
Stream.PutChar[sh, 'R]; -- Box: Reset everything
Stream.SendNow[sh];
Stream.Delete[sh];
sh ← NIL;
END;
END;
END;
FlushThePipe: PROCEDURE [sh: Stream.Handle] RETURNS [hit: BOOLEAN] =
BEGIN
n: CARDINAL ← 0;
DO
c: CHARACTER;
c ← Stream.GetChar[sh ! Stream.TimeOut => EXIT];
n ← n + 1;
IF n > 1000 THEN ERROR Error [cantParse, "Box stuck talking"L];
ENDLOOP;
RETURN[n > 0];
END;
PokeBox: PROCEDURE [sh: Stream.Handle]
RETURNS [msDiff: LONG INTEGER, msDelay: LONG CARDINAL] =
BEGIN
start, stop: System.Pulses;
local, remote, first: System.GreenwichMeanTime;
diff: LONG INTEGER;
string: STRING = [40];
days, hours, minutes, seconds, miliseconds: CARDINAL;
slop: LONG CARDINAL;
IF FlushThePipe[sh] THEN ERROR Error[cantParse, "Unexpected characters in pipeline"L];
start ← System.GetClockPulses[];
first ← System.GetGreenwichMeanTime[];
DO -- Wait until clock ticks
temp: System.Pulses ← System.GetClockPulses[];
local ← System.GetGreenwichMeanTime[];
stop ← System.GetClockPulses[];
IF first # local THEN EXIT;
start ← temp;
ENDLOOP;
slop ← System.PulsesToMicroseconds[[stop-start]]/1000 + 1;
-- This sometimes hangs in WaitToSend. Maybe probeCounter is confusing things
Stream.PutChar[sh, 'T]; -- Box: Send current time (once more)
Stream.SendNow[sh];
DO
c: CHARACTER ← Stream.GetChar[sh !
Stream.TimeOut => ERROR Error[cantParse, "Timeout while collecting answer"L]];
IF c = Ascii.CR THEN LOOP;
IF c = Ascii.LF THEN EXIT;
IF string.length = string.maxlength THEN ERROR Error[cantParse, "Too many characters"L];
String.AppendChar[string, c];
ENDLOOP;
stop ← System.GetClockPulses[];
msDelay ← System.PulsesToMicroseconds[[stop-start]]/1000 + 1 + slop;
-- Format should be ↑Addd:hh:mm:ss.sssx (x might be ?)
IF string.length < 18 THEN ERROR Error[cantParse, "18 characters expected"L];
IF string[0] # Ascii.ControlA THEN ERROR Error[cantParse, "↑A expected"L];
IF string[17] # Ascii.SP THEN
BEGIN
temp: STRING = [50];
String.AppendString[temp, "Not Locked ("L];
String.AppendString[temp, string];
SELECT string[17] FROM
'? => String.AppendString[temp, " => +/- 500ms"L];
'# => String.AppendString[temp, " => +/- 50ms"L];
'* => String.AppendString[temp, " => +/- 5ms"L];
'. => String.AppendString[temp, " => +/- 1ms"L];
ENDCASE => String.AppendString[temp, " = unknown quality indication"L];
String.AppendChar[temp, ')];
ERROR Error[notLocked, temp];
END;
days ← ExtractDecimal[string, 1, 3];
hours ← ExtractDecimal[string, 5, 2];
minutes ← ExtractDecimal[string, 8, 2];
seconds ← ExtractDecimal[string, 11, 2];
miliseconds ← ExtractDecimal[string, 14, 3];
remote ← [
StartOfThisYear[local, days] +
(days -1) * 86400 + -- Box thinks days are like Fortran
hours * LONG[3600] +
minutes * 60 +
seconds ];
diff ← remote - local;
IF ABS[diff] > 2*3600 THEN ERROR Error[localClockWayOff, "Local clock way off"L];
msDiff ← diff * 1000 + miliseconds;
BEGIN -- Correct for transmission time of RS232 characters
msPerCharacter: CARDINAL = 4; -- 2400 baud
msDiff ← msDiff - msPerCharacter; -- Box strobes time at end of "T"
msDelay ← msDelay - (1 + 20) * msPerCharacter;
END;
END;
ExtractDecimal: PROCEDURE [s: STRING, offset, digits: CARDINAL] RETURNS [n: CARDINAL] =
BEGIN
n ← 0;
FOR i: CARDINAL IN [offset..offset + digits) DO
c: CHARACTER = s[i];
SELECT c FROM
IN ['0..'9] => n ← n*10 + c-'0;
ENDCASE => ERROR Error[cantParse, "Decimal digit expected"L];
ENDLOOP;
END;
StartOfThisYear: PROCEDURE [
local: System.GreenwichMeanTime, days: CARDINAL] RETURNS [System.GreenwichMeanTime] =
BEGIN
temp: Time.Unpacked;
fudge: LONG INTEGER = 90*86400;
SELECT days FROM
IN [0..90] => local ← System.AdjustGreenwichMeanTime[local, fudge];
IN [365-90..366] => local ← System.AdjustGreenwichMeanTime[local, -fudge];
ENDCASE;
temp ← Time.Unpack[local];
temp.month ← 0;
temp.day ← 1;
temp.hour ← 0;
temp.minute ← 0;
temp.second ← 0;
temp.dst ← FALSE;
temp.zone ← [west, 0, 0, 366, 366];
RETURN[Time.Pack[temp, FALSE]];
END;
END.