123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- MODULE SerialsVirtual; (** AUTHOR "staubesv"; PURPOSE "Virtual serial port driver"; *)
- (**
- * This driver creates two virtual serial port instances that are linked using a virtual null-modem cable, i.e. the data sent to one
- * port is received by the other and vice versa.
- * Idea: One of the ports can be used by the application under development and the other is used to send data to this application, for example,
- * simulated output of a serial port device.
- *
- * Usage:
- *
- * SerialsVirtual.Install ~ creates two virtual serial port instances that are cross-linked and registers them at Serials
- *
- * SerialsVirtual.SendFile portNbr filename [Loop] ~ sends the content of the specified file to the specified virtual serial port.
- * The data is then received by its companion port.
- * SerialsVirtual.StopSendFile portNbr ~ stops sending a file for the specified port
- *
- * SerialsVirtual.InstallSniffer ~ installs a virtal serial port that acts as proxy for the specified port
- *
- * System.Free SerialsVirtual ~ Unregisters virtual serial ports at Serials
- *
- * History:
- *
- * 20.06.2006 Created (staubesv)
- * 26.06.2006 Speed emulation (staubesv)
- * 27.06.2006 Implemented PortSniffer (staubesv)
- *)
- IMPORT
- KernelLog, Strings, Modules, Commands, Streams, Files, Kernel, Random,
- Serials;
- CONST
- Verbose = TRUE;
- BufferSize = 1024;
- (* If TRUE, the SendChar procedure is artificially slowed down to the speed approx. bps *)
- EnableSendSpeedLimitation = TRUE;
- ModuleName = "SerialsVirtual";
- TYPE
- SendProcedure = PROCEDURE {DELEGATE} (ch : CHAR; VAR res : WORD);
- (** Virtual serial port the can be linked to other virtual serial port *)
- VirtualPort = OBJECT (Serials.Port);
- VAR
- buffer : ARRAY BufferSize OF CHAR;
- head, tail : LONGINT;
- open : BOOLEAN;
- bps, data, parity, stop : LONGINT;
- mc : SET;
- sender : SendProcedure;
- (* Send speed emulation fields *)
- eachNCharacters, waitForMs : LONGINT;
- timer : Kernel.Timer;
- (** Virtual Port Interface *)
- PROCEDURE PutChar(ch : CHAR; VAR res : WORD);
- BEGIN {EXCLUSIVE}
- IF ~open THEN
- res := Serials.Closed;
- ELSE
- AWAIT(((tail + 1) MOD BufferSize # head) OR ~open); (* Wait until buffer is not full *)
- IF open THEN
- buffer[tail] := ch;
- tail := (tail + 1) MOD BufferSize;
- res := Serials.Ok;
- ELSE
- res := Serials.Closed;
- END;
- END;
- END PutChar;
- (** Serial Port Interface *)
- PROCEDURE Open*(bps, data, parity, stop : LONGINT; VAR res: WORD);
- BEGIN {EXCLUSIVE}
- IF open THEN
- IF Verbose THEN ShowModule; KernelLog.String(name); KernelLog.String(" already open"); KernelLog.Ln; END;
- res := Serials.PortInUse;
- RETURN
- END;
- SetPortState(bps, data, parity, stop, res);
- IF res = Serials.Ok THEN
- open := TRUE; head := 0; tail := 0;
- charactersSent := 0; charactersReceived := 0;
- IF Verbose THEN ShowModule; KernelLog.String(name); KernelLog.String(" opened"); KernelLog.Ln; END;
- END;
- END Open;
- PROCEDURE Close*;
- BEGIN {EXCLUSIVE}
- open := FALSE;
- tail := -1;
- IF Verbose THEN ShowModule; KernelLog.String(name); KernelLog.String(" closed"); KernelLog.Ln; END;
- END Close;
- PROCEDURE SendChar*(ch: CHAR; VAR res : WORD);
- VAR wait: BOOLEAN;
- BEGIN
- BEGIN{EXCLUSIVE}
- IF ~open THEN res := Serials.Closed; END;
- END;
- IF (errorRate > 0) & (random.Uniform() < errorRate) THEN RETURN; ch := CHR(ORD(ch) + random.Dice(20)) END;
- IF sender # NIL THEN
- BEGIN{EXCLUSIVE}
- INC(charactersSent);
- IF EnableSendSpeedLimitation & (waitForMs # 0) & (charactersSent MOD eachNCharacters = 0) THEN
- timer.Sleep(waitForMs)
- END;
- END;
- sender(ch, res);
- END;
- END SendChar;
- (** Wait for the next character is received in the input buffer. The buffer is fed by HandleInterrupt *)
- PROCEDURE ReceiveChar*(VAR ch: CHAR; VAR res: WORD);
- BEGIN {EXCLUSIVE}
- IF ~open THEN res := Serials.Closed; RETURN END;
- AWAIT((tail # head) OR ~open);
- IF ~open OR (tail = -1) THEN
- res := Serials.Closed;
- ELSE
- ch := buffer[head]; head := (head+1) MOD BufferSize;
- INC(charactersReceived);
- res := Serials.Ok;
- END
- END ReceiveChar;
- PROCEDURE Available*(): LONGINT;
- BEGIN {EXCLUSIVE}
- RETURN (tail - head) MOD BufferSize
- END Available;
- (* Set the port state: speed in bps, no. of data bits, parity, stop bit length. *)
- PROCEDURE SetPortState(bps, data, parity, stop : LONGINT; VAR res: WORD);
- BEGIN
- SELF.bps := bps; SELF.data := data; SELF.parity := parity; SELF.stop := stop;
- res := Serials.Ok;
- IF EnableSendSpeedLimitation THEN
- GetSlowdownValues(bps, eachNCharacters, waitForMs, res);
- END;
- END SetPortState;
- (** Get the port state: state (open, closed), speed in bps, no. of data bits, parity, stop bit length. *)
- PROCEDURE GetPortState*(VAR openstat : BOOLEAN; VAR bps, data, parity, stop : LONGINT);
- BEGIN {EXCLUSIVE}
- openstat := open;
- bps := SELF.bps; data := SELF.data; parity := SELF.parity; stop := SELF.stop;
- END GetPortState;
- (** Clear the specified modem control lines. s may contain DTR, RTS & Break. *)
- PROCEDURE ClearMC*(s: SET);
- BEGIN {EXCLUSIVE}
- mc := mc - s;
- END ClearMC;
- (** Set the specified modem control lines. s may contain DTR, RTS & Break. *)
- PROCEDURE SetMC*(s: SET);
- BEGIN {EXCLUSIVE}
- mc := mc + s;
- END SetMC;
- (** Return the state of the specified modem control lines. s contains
- the current state of DSR, CTS, RI, DCD & Break Interrupt. *)
- PROCEDURE GetMC*(VAR s: SET);
- BEGIN {EXCLUSIVE}
- s := mc;
- END GetMC;
- PROCEDURE &Init*;
- BEGIN
- NEW(timer);
- END Init;
- END VirtualPort;
- TYPE
- (* Note: If logging to the Kernel Log, be sure that associated real serial port is not the one which KernelLog uses *)
- PortSniffer = OBJECT(Serials.Port)
- VAR
- port : Serials.Port;
- in, out : Streams.Writer;
- PROCEDURE Open*(bps, data, parity, stop : LONGINT; VAR res: WORD);
- BEGIN {EXCLUSIVE}
- port.Open(bps, data, parity, stop, res);
- IF res = Serials.Ok THEN
- charactersSent := 0; charactersReceived := 0;
- END;
- END Open;
- PROCEDURE Close*;
- BEGIN {EXCLUSIVE}
- port.Close;
- END Close;
- PROCEDURE SendChar*(ch: CHAR; VAR res : WORD);
- BEGIN {EXCLUSIVE}
- port.SendChar(ch, res);
- IF res = Serials.Ok THEN
- IF out # NIL THEN
- out.Char(ch); out.Update;
- ELSE
- IF Verbose THEN KernelLog.Char(ch); END;
- END;
- INC(charactersSent);
- ELSE
- IF Verbose THEN
- ShowModule; KernelLog.String("Error while sending '"); KernelLog.Char(ch); KernelLog.String("': ");
- KernelLog.Int(res, 0); KernelLog.Ln;
- END;
- END;
- END SendChar;
- (** Wait for the next character is received in the input buffer. The buffer is fed by HandleInterrupt *)
- PROCEDURE ReceiveChar*(VAR ch: CHAR; VAR res: WORD);
- BEGIN {EXCLUSIVE}
- port.ReceiveChar(ch, res);
- IF res = Serials.Ok THEN
- IF in # NIL THEN
- in.Char(ch); in.Update;
- ELSE
- IF Verbose THEN KernelLog.Char(ch); END;
- END;
- INC(charactersReceived);
- ELSE
- IF Verbose THEN ShowModule; KernelLog.String("Error while receiving: "); KernelLog.Int(res, 0); KernelLog.Ln; END;
- END;
- END ReceiveChar;
- PROCEDURE Available*(): LONGINT;
- BEGIN {EXCLUSIVE}
- RETURN port.Available();
- END Available;
- (** Get the port state: state (open, closed), speed in bps, no. of data bits, parity, stop bit length. *)
- PROCEDURE GetPortState*(VAR openstat : BOOLEAN; VAR bps, data, parity, stop : LONGINT);
- BEGIN {EXCLUSIVE}
- port.GetPortState(openstat, bps, data, parity, stop);
- END GetPortState;
- (** Clear the specified modem control lines. s may contain DTR, RTS & Break. *)
- PROCEDURE ClearMC*(s: SET);
- BEGIN {EXCLUSIVE}
- port.ClearMC(s);
- END ClearMC;
- (** Set the specified modem control lines. s may contain DTR, RTS & Break. *)
- PROCEDURE SetMC*(s: SET);
- BEGIN {EXCLUSIVE}
- port.SetMC(s);
- END SetMC;
- (** Return the state of the specified modem control lines. s contains the current state of DSR, CTS, RI, DCD & Break Interrupt. *)
- PROCEDURE GetMC*(VAR s: SET);
- BEGIN {EXCLUSIVE}
- port.GetMC(s);
- END GetMC;
- PROCEDURE &Init*(port : Serials.Port; in, out : Streams.Writer);
- BEGIN
- ASSERT(port # NIL);
- SELF.port := port; SELF.in := in; SELF.out := out;
- END Init;
- END PortSniffer;
- VAR
- active : ARRAY Serials.MaxPorts+1 OF BOOLEAN;
- errorRate: LONGREAL;
- random: Random.Generator;
- PROCEDURE ShowModule;
- BEGIN
- KernelLog.String(ModuleName); KernelLog.String(": ");
- END ShowModule;
- PROCEDURE GetSlowdownValues(bps : LONGINT; VAR eachNCharacters, waitForMs: LONGINT; VAR res : WORD);
- BEGIN
- res := Serials.Ok;
- waitForMs := 1;
- IF bps = 0 THEN waitForMs := 0; (* Don't limit speed *)
- ELSIF bps = 300 THEN eachNCharacters := 1; waitForMs := 4;
- ELSIF bps = 600 THEN eachNCharacters := 1; waitForMs := 2;
- ELSIF bps = 1200 THEN eachNCharacters := 1;
- ELSIF bps = 2400 THEN eachNCharacters := 2;
- ELSIF bps = 4800 THEN eachNCharacters := 4;
- ELSIF bps = 9600 THEN eachNCharacters := 8;
- ELSIF bps = 19200 THEN eachNCharacters := 16;
- ELSIF bps = 38400 THEN eachNCharacters := 32;
- ELSIF bps = 115200 THEN eachNCharacters := 100;
- ELSIF bps = 230400 THEN eachNCharacters := 200;
- ELSIF bps = 460800 THEN eachNCharacters := 400;
- ELSIF bps = 921600 THEN eachNCharacters := 800;
- ELSE
- res := Serials.WrongBPS;
- END;
- END GetSlowdownValues;
- PROCEDURE IsValidPortNumber(portNbr : LONGINT) : BOOLEAN;
- BEGIN
- RETURN (1 <= portNbr) & (portNbr <= Serials.MaxPorts);
- END IsValidPortNumber;
- PROCEDURE SendFileIntern(portNbr : LONGINT; CONST filename : ARRAY OF CHAR; loop : BOOLEAN; context : Commands.Context);
- VAR
- port : Serials.Port;
- file : Files.File;
- len: LONGINT; res : WORD;
- in : Files.Reader; out : Streams.Writer;
- buffer : ARRAY BufferSize OF CHAR;
- BEGIN
- BEGIN {EXCLUSIVE}
- IF active[portNbr] THEN
- context.out.String("Port is already used for data generation"); context.out.Ln;
- RETURN;
- ELSE
- active[portNbr] := TRUE;
- END;
- END;
- port := Serials.GetPort(portNbr);
- IF port # NIL THEN
- file := Files.Old(filename);
- IF file # NIL THEN
- port.Open(600, 8, 2, 2, res);
- IF res = Serials.Ok THEN
- context.out.String("Sending file "); context.out.String(filename); context.out.String(" to serial port "); context.out.Int(portNbr, 0);
- IF loop THEN context.out.String(" [LOOP MODE]"); END; context.out.String("... ");
- NEW(out, port.Send, BufferSize);
- Files.OpenReader(in, file, 0);
- REPEAT
- in.Bytes(buffer, 0, BufferSize, len); out.Bytes(buffer, 0, len); out.Update;
- IF loop & (in.res = Streams.EOF) THEN Files.OpenReader(in, file, 0); END;
- UNTIL (in.res # Streams.Ok) OR (out.res # Streams.Ok) OR (active[portNbr] = FALSE);
- context.out.String("done."); context.out.Ln;
- ELSE context.out.String("Could not open port "); context.out.Int(portNbr, 0); context.out.String(", res: "); context.out.Int(res, 0); context.out.Ln;
- END;
- port.Close;
- ELSE context.out.String("Could not open file "); context.out.String(filename); context.out.Ln;
- END;
- ELSE context.out.String("Could not get serial port "); context.out.Int(portNbr, 0); context.out.Ln;
- END;
- BEGIN {EXCLUSIVE}
- IF active[portNbr] THEN active[portNbr] := FALSE; END;
- END;
- END SendFileIntern;
- (** Send the content of the specified file to the specified serial port. If the Loop parameter is used, the file is sent
- in a endless loop. Sending can be stopped using the StopSendFile commands *)
- PROCEDURE SendFile*(context : Commands.Context); (** portNbr filename [Loop] ~ *)
- VAR portNbr : LONGINT; filename, parString : ARRAY Files.NameLength OF CHAR; loop : BOOLEAN;
- BEGIN
- IF context.arg.GetInteger(portNbr, FALSE) & IsValidPortNumber(portNbr) THEN
- IF context.arg.GetString(filename) THEN
- IF context.arg.GetString(parString) & Strings.Match(parString, "Loop") THEN loop := TRUE; END;
- SendFileIntern(portNbr, filename, loop, context);
- context.out.String("Started generator on port "); context.out.Int(portNbr, 0);
- context.out.String(" (File: "); context.out.String(filename); context.out.String(")"); context.out.Ln;
- ELSE
- context.out.String("Expected portNbr filename parameters. Could not read filename."); context.out.Ln;
- END;
- ELSE
- context.out.String("Invalid port number"); context.out.Ln;
- END;
- END SendFile;
- (** Stop sending a file for the specified port *)
- PROCEDURE StopSendFile*(context : Commands.Context); (** portNbr ~ *)
- VAR portNbr : LONGINT;
- BEGIN
- IF context.arg.GetInteger(portNbr, FALSE) & IsValidPortNumber(portNbr) THEN
- BEGIN {EXCLUSIVE}
- IF active[portNbr] THEN
- active[portNbr] := FALSE;
- context.out.String("Stopped generator on port "); context.out.Int(portNbr, 0); context.out.Ln;
- ELSE
- context.out.String("No generator running on port "); context.out.Int(portNbr, 0); context.out.Ln;
- END;
- END;
- ELSE
- context.out.String("Invalid port number"); context.out.Ln;
- END;
- END StopSendFile;
- (** Installs two virtual serial ports which are linked to each other. Data sent by one port is received by the other and vice versa *)
- PROCEDURE Install*(context : Commands.Context); (** ~ *)
- VAR port1, port2 : VirtualPort; description : ARRAY 128 OF CHAR;
- BEGIN
- NEW(port1); NEW(port2);
- port1.sender := port2.PutChar;
- port2.sender := port1.PutChar;
- description := "Virtual Serial Port";
- Serials.RegisterPort(port1, description);
- Strings.Append(description, " (Linked to "); Strings.Append(description, port1.name); Strings.Append(description, ")");
- Serials.RegisterPort(port2, description);
- END Install;
- (** Install a virtual sniffer port as proxy for the specified serial port *)
- PROCEDURE InstallSniffer*(context : Commands.Context); (** [portNbr] ~ *)
- VAR
- portSniffer : PortSniffer; port : Serials.Port;
- portNbr : LONGINT;
- description : ARRAY 128 OF CHAR;
- BEGIN
- IF context.arg.GetInteger(portNbr, FALSE) & IsValidPortNumber(portNbr) THEN
- port := Serials.GetPort(portNbr);
- IF port # NIL THEN
- NEW(portSniffer, port, NIL, NIL);
- description := "Virtual Serial Port (Sniffer linked to ";
- Strings.Append(description, port.name); Strings.Append(description, ")");
- Serials.RegisterPort(portSniffer, description);
- context.out.String("Registered serial port sniffer for port "); context.out.Int(portNbr, 0); context.out.Ln;
- ELSE
- context.out.String("Port "); context.out.Int(portNbr, 0); context.out.String(" not found."); context.out.Ln;
- END;
- ELSE
- context.out.String("Invalid port number"); context.out.Ln;
- END;
- END InstallSniffer;
- PROCEDURE Cleanup;
- VAR portNbr : LONGINT;
- BEGIN
- FOR portNbr := 1 TO Serials.MaxPorts DO
- active[portNbr] := FALSE;
- END;
- END Cleanup;
- PROCEDURE SetErrorRate*(context: Commands.Context);
- VAR rate, divisor: LONGINT;
- BEGIN
- IF context.arg.GetInteger(rate,FALSE) & context.arg.GetInteger(divisor,FALSE) THEN
- errorRate := rate / divisor;
- END;
- END SetErrorRate;
- BEGIN
- Modules.InstallTermHandler(Cleanup);
- NEW(random); errorRate := 0;
- END SerialsVirtual.
- SerialsVirtual.SetErrorRate 0 1 ~
- SerialsVirtual.SetErrorRate 1 100000 ~
|