language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/control/sources/logger.mod
IMPLEMENTATION MODULE Logger;
(********************************************************)
(* *)
(* Logging data to disk file *)
(* *)
(* Programmer: P. Moylan *)
(* Last edited: 17 August 1994 *)
(* Status: Working *)
(* *)
(********************************************************)
(* Depending on what device you are using for logging, you may need *)
(* to import only one of the following two modules. *)
IMPORT Floppy;
(*IMPORT HardDisk;*)
FROM Files IMPORT
(* type *) File,
(* proc *) OpenFile, CloseFile, WriteByte;
FROM IOErrorCodes IMPORT
(* type *) ErrorCode,
(* proc *) TranslateErrorCode;
FROM Keyboard IMPORT
(* proc *) InKey, PutBack;
FROM MaintenancePages IMPORT
(* type *) MaintenancePage,
(* proc *) CreateMaintenancePage, Associate;
FROM Windows IMPORT
(* type *) Window, Colour, FrameType, DividerType,
(* proc *) OpenWindow, CloseWindow, WriteString, WriteLn, EditString,
SetCursor, PressAnyKey, WriteChar;
FROM NumericIO IMPORT
(* proc *) ReadBufferedCardinal, WriteRJCard;
FROM Semaphores IMPORT
(* type *) Semaphore,
(* proc *) CreateSemaphore, Wait, Signal;
FROM LossyQueues IMPORT
(* type *) LossyQueue,
(* proc *) CreateQueue, PutQueue, GetQueue;
FROM TaskControl IMPORT
(* proc *) CreateTask;
FROM Timer IMPORT
(* proc *) Sleep;
FROM TerminationControl IMPORT
(* proc *) SetTerminationProcedure;
FROM Mouse IMPORT
(* proc *) MouseAvailable;
FROM UserInterface IMPORT
(* type *) UIWindow, Capability, CapabilitySet,
(* proc *) AllowMouseControl;
(************************************************************************)
CONST testing = TRUE;
VAR Mpage: MaintenancePage;
heading, debug: Window;
CONST
ElementCapacity = 4;
TYPE
(* For a lossy queue element, there are two special cases: *)
(* count = -1 means open the log file *)
(* count = 0 means close the log file *)
LossyQueueElement = RECORD
count: INTEGER;
datum: ARRAY [0..ElementCapacity-1] OF INTEGER;
END (*RECORD*);
VAR
(* The file to which data are being written. *)
log: File;
(* The name of the log file. *)
filename: ARRAY [0..31] OF CHAR;
(* The queue holding the data to be sent to the output file. *)
Lossy: LossyQueue;
(* A screen window which is displayed while logging is in progress. *)
LogWindow: Window;
(* Semaphore to control access to the log window. *)
LogWindowAccess: Semaphore;
(* Second log window for testing purposes. *)
Log2Window: Window;
(* Semaphore to synchronise file opening. *)
FileOpenHandled: Semaphore;
(* Logging=TRUE while data logging is in progress. AbortRequested *)
(* can be set to TRUE to request that logging be terminated. *)
Logging, AbortRequested: BOOLEAN;
(* A count of the number of data values yet to be collected. *)
LogCount: CARDINAL;
(* Count of data which could not be sent to disk because the output *)
(* queue overflowed. *)
DataLost: CARDINAL;
(* Count of output errors. We abort logging if it gets too large. *)
ErrorCount: CARDINAL;
(* Flags involved in shutting down the module. *)
ShutDownDesired: BOOLEAN;
ShutDownCompleted: Semaphore;
(************************************************************************)
(* ERROR MESSAGE HANDLER *)
(************************************************************************)
PROCEDURE ReportError (code: ErrorCode);
(* Puts out a screen message about the I/O error, stops logging if *)
(* error count gets too large. *)
CONST ErrorLimit = 20;
VAR ErrorWindow: Window; dummy: UIWindow;
messbuffer: ARRAY [0..39] OF CHAR;
BEGIN
OpenWindow (ErrorWindow, white, blue, 14, 17, 22, 58,
doubleframe, nodivider);
IF MouseAvailable() THEN
dummy := AllowMouseControl (ErrorWindow, "Disk error message",
CapabilitySet {wshow, wmove, whide});
END (*IF*);
WriteString (ErrorWindow, "Disk error ");
TranslateErrorCode (code, messbuffer);
WriteString (ErrorWindow, messbuffer);
INC (ErrorCount);
IF ErrorCount > ErrorLimit THEN
WriteLn (ErrorWindow);
WriteString (ErrorWindow, "Too many errors, aborting logging.");
ErrorCount := 0; AbortRequested := TRUE;
END (*IF*);
PressAnyKey (ErrorWindow); CloseWindow (ErrorWindow);
IF testing THEN
WriteLn (debug);
WriteString (debug, "Return from ReportError");
END (*IF*);
END ReportError;
(************************************************************************)
(* FILE OUTPUT *)
(************************************************************************)
PROCEDURE PutChar (ch: CHAR);
(* Sends one character to the log file. *)
VAR errorcode: ErrorCode;
BEGIN
IF testing THEN
IF ch = CHR(13) THEN
WriteLn (Log2Window);
ELSIF ch <> CHR(10) THEN
WriteChar (Log2Window, ch);
END (*IF*);
END (*IF*);
errorcode := WriteByte (log, ch);
IF errorcode <> OK THEN
ReportError (errorcode);
END (*IF*);
END PutChar;
(************************************************************************)
PROCEDURE PutInteger (value: INTEGER);
(* Converts value to ASCII, sends it to the log file. *)
BEGIN
IF value < 0 THEN
PutChar ("-"); value := -value;
END (*IF*);
IF value > 9 THEN
PutInteger (value DIV 10);
value := value MOD 10;
END (*IF*);
PutChar (CHR(ORD("0")+ORD(value)));
END PutInteger;
(************************************************************************)
(* SENDING QUEUED DATA TO THE LOG FILE *)
(************************************************************************)
PROCEDURE LogTask;
(* Takes data from the lossy queue, converts it to ASCII and sends *)
(* it to the log file. Data received while the log file is closed *)
(* (this is possible, if logging is aborted) are discarded. *)
CONST CR = CHR(13); LF = CHR(10);
VAR j: CARDINAL; status: ErrorCode;
buffer: LossyQueueElement;
LogFileOpen: BOOLEAN;
w: Window; dummy: UIWindow;
BEGIN
IF testing THEN
OpenWindow (Log2Window,white,red,15,24,0,47,simpleframe,nodivider);
Associate (Log2Window, Mpage);
WriteString (Log2Window, "Log2Window opened.");
WriteLn (Log2Window);
OpenWindow (w,white,black, 11,14,0,79,simpleframe,nodivider);
Associate (w, Mpage);
IF MouseAvailable() THEN
dummy := AllowMouseControl (Log2Window, "Log task testing #2",
CapabilitySet {wshow, wmove, whide});
dummy := AllowMouseControl (w, "Log task progress",
CapabilitySet {wshow, wmove, whide});
END (*IF*);
WriteString (w, "LogTask started."); WriteLn (w);
END (*IF*);
LogFileOpen := FALSE;
LOOP (* until file closed and ShutDownDesired *)
GetQueue (Lossy, buffer);
IF testing THEN
WriteLn(w);
WriteString(w, "Return from GetQueue.");
END (*IF*);
WITH buffer DO
IF (count = -1) THEN
IF testing THEN
WriteString(w, ".. count is -1");
END (*IF*);
ErrorCount := 0;
IF NOT LogFileOpen THEN
IF testing THEN
WriteLn (w);
WriteString(w, "About to open log file");
END (*IF*);
status := OpenFile (log, filename, TRUE);
IF testing THEN
WriteLn (w);
WriteString(w, "Have opened log file");
END (*IF*);
IF status = OK THEN
LogFileOpen := TRUE;
ELSE
ReportError (status); AbortRequested := TRUE;
END (*IF*);
END (*IF*);
Signal (FileOpenHandled);
ELSIF count = 0 THEN
IF testing THEN
WriteString(w, ".. count is zero");
END (*IF*);
IF LogFileOpen THEN
IF testing THEN
WriteLn(w);
WriteString(w, "Closing log file.");
END (*IF*);
CloseFile (log); LogFileOpen := FALSE;
END (*IF*);
IF ShutDownDesired THEN
IF testing THEN
WriteLn(w);
WriteString(w, "Stopping LogTask.");
END (*IF*);
EXIT (*LOOP*);
END (*IF*);
ELSIF LogFileOpen THEN
FOR j := 0 TO CARDINAL(count)-1 DO
PutInteger (datum[j]);
PutChar (" ");
END (*FOR*);
PutChar (CR); PutChar (LF);
ELSE
AbortRequested := TRUE;
END (*IF*);
END (*WITH*);
END (*LOOP*);
IF testing THEN
WriteLn(w);
WriteString(w, "Left LogTask main loop.");
END (*IF*);
IF testing THEN
WriteLn(w);
WriteString(w, "LogTask is terminating.");
CloseWindow (w);
END (*IF*);
Signal (ShutDownCompleted);
END LogTask;
(************************************************************************)
PROCEDURE PutSpecialBuffer (code: INTEGER);
(* Queues a command to open or close the log file. This procedure *)
(* is called only by user tasks and initialisation code. *)
VAR buffer: LossyQueueElement;
BEGIN
buffer.count := code;
LOOP
(*
IF testing THEN
WriteLn (debug);
WriteString (debug, "Putting special buffer element.");
END (*IF*);
*)
IF PutQueue (Lossy, buffer) THEN
EXIT (*LOOP*);
END (*IF*);
(*
IF testing THEN
WriteString (debug, " - Put was unsuccessful.");
END (*IF*);
*)
Sleep (1);
END (*LOOP*);
(*
IF testing THEN
WriteLn (debug);
WriteString (debug, "Leaving procedure PutSpecialBuffer.");
END (*IF*);
*)
END PutSpecialBuffer;
(************************************************************************)
(* THE USER-CALLABLE PROCEDURES *)
(************************************************************************)
PROCEDURE StartLogging(): BOOLEAN;
(* Opens the log file. Returns FALSE if open failed. *)
CONST Return = CHR(13);
VAR ch: CHAR; dummy: UIWindow;
BEGIN
IF Logging THEN
Wait (LogWindowAccess);
SetCursor (LogWindow, 1, 1);
WriteString (LogWindow, "Logging is already in progress.");
Signal (LogWindowAccess);
ELSE
AbortRequested := FALSE;
Wait (LogWindowAccess);
OpenWindow (LogWindow,white,red,0,5,0,47,simpleframe,nodivider);
IF MouseAvailable() THEN
dummy := AllowMouseControl (LogWindow, "Logger status",
CapabilitySet {wshow, wmove, whide});
END (*IF*);
WriteString (LogWindow, "Enter the name of the log file:");
WriteLn (LogWindow);
EditString (LogWindow, filename, HIGH(filename));
ch := InKey();
IF ch <> Return THEN PutBack(ch) END(*IF*);
WriteLn (LogWindow);
Signal (LogWindowAccess);
PutSpecialBuffer (-1);
Wait (FileOpenHandled);
IF AbortRequested THEN
LogCount := 0
ELSE
Wait (LogWindowAccess);
WriteString (LogWindow, "Maximum number of readings to log?");
LogCount := ReadBufferedCardinal (LogWindow, 6);
Signal (LogWindowAccess);
ch := InKey();
IF ch <> Return THEN PutBack(ch) END(*IF*);
END (*IF*);
Logging := TRUE;
IF LogCount = 0 THEN
StopLogging;
ELSE
DataLost := 0;
Wait (LogWindowAccess);
SetCursor (LogWindow, 4, 1);
WriteString (LogWindow, "Data lost: 0");
Signal (LogWindowAccess);
END (*IF*);
END (*IF*);
RETURN Logging;
END StartLogging;
(************************************************************************)
PROCEDURE Log (VAR (*IN*) data: ARRAY OF INTEGER);
(* Sends the data to the log file (assumed to be already open). *)
(* This is done via a lossy queue. *)
CONST CR = CHR(13); LF = CHR(10);
VAR j: CARDINAL; success: BOOLEAN;
buffer: LossyQueueElement;
BEGIN
IF testing THEN
WriteLn (debug);
WriteString (debug, "Entering procedure Log");
END (*IF*);
IF AbortRequested THEN
StopLogging;
END (*IF*);
IF NOT Logging THEN RETURN END (*IF*);
WITH buffer DO
count := HIGH(data)+1;
IF count > ElementCapacity THEN
count := ElementCapacity;
END (*IF*);
FOR j := 0 TO CARDINAL(count-1) DO
datum[j] := data[j];
END (*FOR*);
END (*WITH*);
IF testing THEN
WriteLn (debug);
WriteString (debug, "Log: calling PutQueue");
END (*IF*);
success := PutQueue (Lossy, buffer);
IF testing THEN
WriteLn (debug);
WriteString (debug, "Log: return from PutQueue");
END (*IF*);
IF NOT success THEN
INC (DataLost);
Wait (LogWindowAccess);
SetCursor (LogWindow, 4, 12);
WriteRJCard (LogWindow, DataLost, 6);
Signal (LogWindowAccess);
RETURN;
END (*IF*);
DEC (LogCount);
Wait (LogWindowAccess);
SetCursor (LogWindow, 3, 35); WriteRJCard (LogWindow, LogCount, 6);
Signal (LogWindowAccess);
IF LogCount = 0 THEN
StopLogging;
END (*IF*);
IF testing THEN
WriteLn (debug);
WriteString (debug, "Leaving procedure Log");
END (*IF*);
END Log;
(************************************************************************)
PROCEDURE StopLogging;
(* Closes the log file, after writing out any pending data. *)
BEGIN
IF Logging THEN
PutSpecialBuffer (0);
Wait (LogWindowAccess);
CloseWindow (LogWindow);
Signal (LogWindowAccess);
Logging := FALSE; AbortRequested := FALSE;
END (*IF*);
END StopLogging;
(************************************************************************)
(* PROGRAM CLOSEDOWN *)
(************************************************************************)
PROCEDURE CloseDown;
BEGIN
IF testing THEN
WriteLn (debug);
WriteString (debug, "Starting closedown of logger.");
END (*IF*);
StopLogging;
ShutDownDesired := TRUE;
PutSpecialBuffer(0);
IF testing THEN
WriteLn (debug);
WriteString (debug, "Waiting for logger shutdown to complete.");
END (*IF*);
Wait (ShutDownCompleted);
IF testing THEN
WriteLn (debug);
WriteString (debug, "Logger has shut down.");
END (*IF*);
END CloseDown;
(************************************************************************)
(* MODULE INITIALISATION *)
(************************************************************************)
VAR dummy: UIWindow;
BEGIN
IF testing THEN
CreateMaintenancePage (Mpage);
OpenWindow (heading, black, white, 0,2, 0,79, simpleframe, nodivider);
OpenWindow (debug, black, white, 18,23, 0,79, simpleframe, nodivider);
Associate (heading, Mpage);
Associate (debug, Mpage);
IF MouseAvailable() THEN
dummy := AllowMouseControl (debug, "Logger debugging",
CapabilitySet {wshow, wmove, whide});
END (*IF*);
SetCursor (heading, 1, 22);
WriteString (heading,"Diagnostic output from Logger module");
WriteString (debug, "Logger: initialisation code executing");
END (*IF*);
Logging := FALSE; ShutDownDesired := FALSE; AbortRequested := FALSE;
SetTerminationProcedure (CloseDown);
CreateSemaphore (FileOpenHandled, 0);
CreateSemaphore (ShutDownCompleted, 0);
CreateSemaphore (LogWindowAccess, 1);
filename := "A:LOG.DAT";
CreateQueue (Lossy, 100, SIZE(LossyQueueElement));
CreateTask (LogTask, 2, "Log task");
END Logger.