-- 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.