SerialsVirtual.Mod 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. MODULE SerialsVirtual; (** AUTHOR "staubesv"; PURPOSE "Virtual serial port driver"; *)
  2. (**
  3. * This driver creates two virtual serial port instances that are linked using a virtual null-modem cable, i.e. the data sent to one
  4. * port is received by the other and vice versa.
  5. * 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,
  6. * simulated output of a serial port device.
  7. *
  8. * Usage:
  9. *
  10. * SerialsVirtual.Install ~ creates two virtual serial port instances that are cross-linked and registers them at Serials
  11. *
  12. * SerialsVirtual.SendFile portNbr filename [Loop] ~ sends the content of the specified file to the specified virtual serial port.
  13. * The data is then received by its companion port.
  14. * SerialsVirtual.StopSendFile portNbr ~ stops sending a file for the specified port
  15. *
  16. * SerialsVirtual.InstallSniffer ~ installs a virtal serial port that acts as proxy for the specified port
  17. *
  18. * System.Free SerialsVirtual ~ Unregisters virtual serial ports at Serials
  19. *
  20. * History:
  21. *
  22. * 20.06.2006 Created (staubesv)
  23. * 26.06.2006 Speed emulation (staubesv)
  24. * 27.06.2006 Implemented PortSniffer (staubesv)
  25. *)
  26. IMPORT
  27. KernelLog, Strings, Modules, Commands, Streams, Files, Kernel, Random,
  28. Serials;
  29. CONST
  30. Verbose = TRUE;
  31. BufferSize = 1024;
  32. (* If TRUE, the SendChar procedure is artificially slowed down to the speed approx. bps *)
  33. EnableSendSpeedLimitation = TRUE;
  34. ModuleName = "SerialsVirtual";
  35. TYPE
  36. SendProcedure = PROCEDURE {DELEGATE} (ch : CHAR; VAR res : WORD);
  37. (** Virtual serial port the can be linked to other virtual serial port *)
  38. VirtualPort = OBJECT (Serials.Port);
  39. VAR
  40. buffer : ARRAY BufferSize OF CHAR;
  41. head, tail : LONGINT;
  42. open : BOOLEAN;
  43. bps, data, parity, stop : LONGINT;
  44. mc : SET;
  45. sender : SendProcedure;
  46. (* Send speed emulation fields *)
  47. eachNCharacters, waitForMs : LONGINT;
  48. timer : Kernel.Timer;
  49. (** Virtual Port Interface *)
  50. PROCEDURE PutChar(ch : CHAR; VAR res : WORD);
  51. BEGIN {EXCLUSIVE}
  52. IF ~open THEN
  53. res := Serials.Closed;
  54. ELSE
  55. AWAIT(((tail + 1) MOD BufferSize # head) OR ~open); (* Wait until buffer is not full *)
  56. IF open THEN
  57. buffer[tail] := ch;
  58. tail := (tail + 1) MOD BufferSize;
  59. res := Serials.Ok;
  60. ELSE
  61. res := Serials.Closed;
  62. END;
  63. END;
  64. END PutChar;
  65. (** Serial Port Interface *)
  66. PROCEDURE Open*(bps, data, parity, stop : LONGINT; VAR res: WORD);
  67. BEGIN {EXCLUSIVE}
  68. IF open THEN
  69. IF Verbose THEN ShowModule; KernelLog.String(name); KernelLog.String(" already open"); KernelLog.Ln; END;
  70. res := Serials.PortInUse;
  71. RETURN
  72. END;
  73. SetPortState(bps, data, parity, stop, res);
  74. IF res = Serials.Ok THEN
  75. open := TRUE; head := 0; tail := 0;
  76. charactersSent := 0; charactersReceived := 0;
  77. IF Verbose THEN ShowModule; KernelLog.String(name); KernelLog.String(" opened"); KernelLog.Ln; END;
  78. END;
  79. END Open;
  80. PROCEDURE Close*;
  81. BEGIN {EXCLUSIVE}
  82. open := FALSE;
  83. tail := -1;
  84. IF Verbose THEN ShowModule; KernelLog.String(name); KernelLog.String(" closed"); KernelLog.Ln; END;
  85. END Close;
  86. PROCEDURE SendChar*(ch: CHAR; VAR res : WORD);
  87. VAR wait: BOOLEAN;
  88. BEGIN
  89. BEGIN{EXCLUSIVE}
  90. IF ~open THEN res := Serials.Closed; END;
  91. END;
  92. IF (errorRate > 0) & (random.Uniform() < errorRate) THEN RETURN; ch := CHR(ORD(ch) + random.Dice(20)) END;
  93. IF sender # NIL THEN
  94. BEGIN{EXCLUSIVE}
  95. INC(charactersSent);
  96. IF EnableSendSpeedLimitation & (waitForMs # 0) & (charactersSent MOD eachNCharacters = 0) THEN
  97. timer.Sleep(waitForMs)
  98. END;
  99. END;
  100. sender(ch, res);
  101. END;
  102. END SendChar;
  103. (** Wait for the next character is received in the input buffer. The buffer is fed by HandleInterrupt *)
  104. PROCEDURE ReceiveChar*(VAR ch: CHAR; VAR res: WORD);
  105. BEGIN {EXCLUSIVE}
  106. IF ~open THEN res := Serials.Closed; RETURN END;
  107. AWAIT((tail # head) OR ~open);
  108. IF ~open OR (tail = -1) THEN
  109. res := Serials.Closed;
  110. ELSE
  111. ch := buffer[head]; head := (head+1) MOD BufferSize;
  112. INC(charactersReceived);
  113. res := Serials.Ok;
  114. END
  115. END ReceiveChar;
  116. PROCEDURE Available*(): LONGINT;
  117. BEGIN {EXCLUSIVE}
  118. RETURN (tail - head) MOD BufferSize
  119. END Available;
  120. (* Set the port state: speed in bps, no. of data bits, parity, stop bit length. *)
  121. PROCEDURE SetPortState(bps, data, parity, stop : LONGINT; VAR res: WORD);
  122. BEGIN
  123. SELF.bps := bps; SELF.data := data; SELF.parity := parity; SELF.stop := stop;
  124. res := Serials.Ok;
  125. IF EnableSendSpeedLimitation THEN
  126. GetSlowdownValues(bps, eachNCharacters, waitForMs, res);
  127. END;
  128. END SetPortState;
  129. (** Get the port state: state (open, closed), speed in bps, no. of data bits, parity, stop bit length. *)
  130. PROCEDURE GetPortState*(VAR openstat : BOOLEAN; VAR bps, data, parity, stop : LONGINT);
  131. BEGIN {EXCLUSIVE}
  132. openstat := open;
  133. bps := SELF.bps; data := SELF.data; parity := SELF.parity; stop := SELF.stop;
  134. END GetPortState;
  135. (** Clear the specified modem control lines. s may contain DTR, RTS & Break. *)
  136. PROCEDURE ClearMC*(s: SET);
  137. BEGIN {EXCLUSIVE}
  138. mc := mc - s;
  139. END ClearMC;
  140. (** Set the specified modem control lines. s may contain DTR, RTS & Break. *)
  141. PROCEDURE SetMC*(s: SET);
  142. BEGIN {EXCLUSIVE}
  143. mc := mc + s;
  144. END SetMC;
  145. (** Return the state of the specified modem control lines. s contains
  146. the current state of DSR, CTS, RI, DCD & Break Interrupt. *)
  147. PROCEDURE GetMC*(VAR s: SET);
  148. BEGIN {EXCLUSIVE}
  149. s := mc;
  150. END GetMC;
  151. PROCEDURE &Init*;
  152. BEGIN
  153. NEW(timer);
  154. END Init;
  155. END VirtualPort;
  156. TYPE
  157. (* Note: If logging to the Kernel Log, be sure that associated real serial port is not the one which KernelLog uses *)
  158. PortSniffer = OBJECT(Serials.Port)
  159. VAR
  160. port : Serials.Port;
  161. in, out : Streams.Writer;
  162. PROCEDURE Open*(bps, data, parity, stop : LONGINT; VAR res: WORD);
  163. BEGIN {EXCLUSIVE}
  164. port.Open(bps, data, parity, stop, res);
  165. IF res = Serials.Ok THEN
  166. charactersSent := 0; charactersReceived := 0;
  167. END;
  168. END Open;
  169. PROCEDURE Close*;
  170. BEGIN {EXCLUSIVE}
  171. port.Close;
  172. END Close;
  173. PROCEDURE SendChar*(ch: CHAR; VAR res : WORD);
  174. BEGIN {EXCLUSIVE}
  175. port.SendChar(ch, res);
  176. IF res = Serials.Ok THEN
  177. IF out # NIL THEN
  178. out.Char(ch); out.Update;
  179. ELSE
  180. IF Verbose THEN KernelLog.Char(ch); END;
  181. END;
  182. INC(charactersSent);
  183. ELSE
  184. IF Verbose THEN
  185. ShowModule; KernelLog.String("Error while sending '"); KernelLog.Char(ch); KernelLog.String("': ");
  186. KernelLog.Int(res, 0); KernelLog.Ln;
  187. END;
  188. END;
  189. END SendChar;
  190. (** Wait for the next character is received in the input buffer. The buffer is fed by HandleInterrupt *)
  191. PROCEDURE ReceiveChar*(VAR ch: CHAR; VAR res: WORD);
  192. BEGIN {EXCLUSIVE}
  193. port.ReceiveChar(ch, res);
  194. IF res = Serials.Ok THEN
  195. IF in # NIL THEN
  196. in.Char(ch); in.Update;
  197. ELSE
  198. IF Verbose THEN KernelLog.Char(ch); END;
  199. END;
  200. INC(charactersReceived);
  201. ELSE
  202. IF Verbose THEN ShowModule; KernelLog.String("Error while receiving: "); KernelLog.Int(res, 0); KernelLog.Ln; END;
  203. END;
  204. END ReceiveChar;
  205. PROCEDURE Available*(): LONGINT;
  206. BEGIN {EXCLUSIVE}
  207. RETURN port.Available();
  208. END Available;
  209. (** Get the port state: state (open, closed), speed in bps, no. of data bits, parity, stop bit length. *)
  210. PROCEDURE GetPortState*(VAR openstat : BOOLEAN; VAR bps, data, parity, stop : LONGINT);
  211. BEGIN {EXCLUSIVE}
  212. port.GetPortState(openstat, bps, data, parity, stop);
  213. END GetPortState;
  214. (** Clear the specified modem control lines. s may contain DTR, RTS & Break. *)
  215. PROCEDURE ClearMC*(s: SET);
  216. BEGIN {EXCLUSIVE}
  217. port.ClearMC(s);
  218. END ClearMC;
  219. (** Set the specified modem control lines. s may contain DTR, RTS & Break. *)
  220. PROCEDURE SetMC*(s: SET);
  221. BEGIN {EXCLUSIVE}
  222. port.SetMC(s);
  223. END SetMC;
  224. (** Return the state of the specified modem control lines. s contains the current state of DSR, CTS, RI, DCD & Break Interrupt. *)
  225. PROCEDURE GetMC*(VAR s: SET);
  226. BEGIN {EXCLUSIVE}
  227. port.GetMC(s);
  228. END GetMC;
  229. PROCEDURE &Init*(port : Serials.Port; in, out : Streams.Writer);
  230. BEGIN
  231. ASSERT(port # NIL);
  232. SELF.port := port; SELF.in := in; SELF.out := out;
  233. END Init;
  234. END PortSniffer;
  235. VAR
  236. active : ARRAY Serials.MaxPorts+1 OF BOOLEAN;
  237. errorRate: LONGREAL;
  238. random: Random.Generator;
  239. PROCEDURE ShowModule;
  240. BEGIN
  241. KernelLog.String(ModuleName); KernelLog.String(": ");
  242. END ShowModule;
  243. PROCEDURE GetSlowdownValues(bps : LONGINT; VAR eachNCharacters, waitForMs: LONGINT; VAR res : WORD);
  244. BEGIN
  245. res := Serials.Ok;
  246. waitForMs := 1;
  247. IF bps = 0 THEN waitForMs := 0; (* Don't limit speed *)
  248. ELSIF bps = 300 THEN eachNCharacters := 1; waitForMs := 4;
  249. ELSIF bps = 600 THEN eachNCharacters := 1; waitForMs := 2;
  250. ELSIF bps = 1200 THEN eachNCharacters := 1;
  251. ELSIF bps = 2400 THEN eachNCharacters := 2;
  252. ELSIF bps = 4800 THEN eachNCharacters := 4;
  253. ELSIF bps = 9600 THEN eachNCharacters := 8;
  254. ELSIF bps = 19200 THEN eachNCharacters := 16;
  255. ELSIF bps = 38400 THEN eachNCharacters := 32;
  256. ELSIF bps = 115200 THEN eachNCharacters := 100;
  257. ELSIF bps = 230400 THEN eachNCharacters := 200;
  258. ELSIF bps = 460800 THEN eachNCharacters := 400;
  259. ELSIF bps = 921600 THEN eachNCharacters := 800;
  260. ELSE
  261. res := Serials.WrongBPS;
  262. END;
  263. END GetSlowdownValues;
  264. PROCEDURE IsValidPortNumber(portNbr : LONGINT) : BOOLEAN;
  265. BEGIN
  266. RETURN (1 <= portNbr) & (portNbr <= Serials.MaxPorts);
  267. END IsValidPortNumber;
  268. PROCEDURE SendFileIntern(portNbr : LONGINT; CONST filename : ARRAY OF CHAR; loop : BOOLEAN; context : Commands.Context);
  269. VAR
  270. port : Serials.Port;
  271. file : Files.File;
  272. len: LONGINT; res : WORD;
  273. in : Files.Reader; out : Streams.Writer;
  274. buffer : ARRAY BufferSize OF CHAR;
  275. BEGIN
  276. BEGIN {EXCLUSIVE}
  277. IF active[portNbr] THEN
  278. context.out.String("Port is already used for data generation"); context.out.Ln;
  279. RETURN;
  280. ELSE
  281. active[portNbr] := TRUE;
  282. END;
  283. END;
  284. port := Serials.GetPort(portNbr);
  285. IF port # NIL THEN
  286. file := Files.Old(filename);
  287. IF file # NIL THEN
  288. port.Open(600, 8, 2, 2, res);
  289. IF res = Serials.Ok THEN
  290. context.out.String("Sending file "); context.out.String(filename); context.out.String(" to serial port "); context.out.Int(portNbr, 0);
  291. IF loop THEN context.out.String(" [LOOP MODE]"); END; context.out.String("... ");
  292. NEW(out, port.Send, BufferSize);
  293. Files.OpenReader(in, file, 0);
  294. REPEAT
  295. in.Bytes(buffer, 0, BufferSize, len); out.Bytes(buffer, 0, len); out.Update;
  296. IF loop & (in.res = Streams.EOF) THEN Files.OpenReader(in, file, 0); END;
  297. UNTIL (in.res # Streams.Ok) OR (out.res # Streams.Ok) OR (active[portNbr] = FALSE);
  298. context.out.String("done."); context.out.Ln;
  299. 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;
  300. END;
  301. port.Close;
  302. ELSE context.out.String("Could not open file "); context.out.String(filename); context.out.Ln;
  303. END;
  304. ELSE context.out.String("Could not get serial port "); context.out.Int(portNbr, 0); context.out.Ln;
  305. END;
  306. BEGIN {EXCLUSIVE}
  307. IF active[portNbr] THEN active[portNbr] := FALSE; END;
  308. END;
  309. END SendFileIntern;
  310. (** Send the content of the specified file to the specified serial port. If the Loop parameter is used, the file is sent
  311. in a endless loop. Sending can be stopped using the StopSendFile commands *)
  312. PROCEDURE SendFile*(context : Commands.Context); (** portNbr filename [Loop] ~ *)
  313. VAR portNbr : LONGINT; filename, parString : ARRAY Files.NameLength OF CHAR; loop : BOOLEAN;
  314. BEGIN
  315. IF context.arg.GetInteger(portNbr, FALSE) & IsValidPortNumber(portNbr) THEN
  316. IF context.arg.GetString(filename) THEN
  317. IF context.arg.GetString(parString) & Strings.Match(parString, "Loop") THEN loop := TRUE; END;
  318. SendFileIntern(portNbr, filename, loop, context);
  319. context.out.String("Started generator on port "); context.out.Int(portNbr, 0);
  320. context.out.String(" (File: "); context.out.String(filename); context.out.String(")"); context.out.Ln;
  321. ELSE
  322. context.out.String("Expected portNbr filename parameters. Could not read filename."); context.out.Ln;
  323. END;
  324. ELSE
  325. context.out.String("Invalid port number"); context.out.Ln;
  326. END;
  327. END SendFile;
  328. (** Stop sending a file for the specified port *)
  329. PROCEDURE StopSendFile*(context : Commands.Context); (** portNbr ~ *)
  330. VAR portNbr : LONGINT;
  331. BEGIN
  332. IF context.arg.GetInteger(portNbr, FALSE) & IsValidPortNumber(portNbr) THEN
  333. BEGIN {EXCLUSIVE}
  334. IF active[portNbr] THEN
  335. active[portNbr] := FALSE;
  336. context.out.String("Stopped generator on port "); context.out.Int(portNbr, 0); context.out.Ln;
  337. ELSE
  338. context.out.String("No generator running on port "); context.out.Int(portNbr, 0); context.out.Ln;
  339. END;
  340. END;
  341. ELSE
  342. context.out.String("Invalid port number"); context.out.Ln;
  343. END;
  344. END StopSendFile;
  345. (** Installs two virtual serial ports which are linked to each other. Data sent by one port is received by the other and vice versa *)
  346. PROCEDURE Install*(context : Commands.Context); (** ~ *)
  347. VAR port1, port2 : VirtualPort; description : ARRAY 128 OF CHAR;
  348. BEGIN
  349. NEW(port1); NEW(port2);
  350. port1.sender := port2.PutChar;
  351. port2.sender := port1.PutChar;
  352. description := "Virtual Serial Port";
  353. Serials.RegisterPort(port1, description);
  354. Strings.Append(description, " (Linked to "); Strings.Append(description, port1.name); Strings.Append(description, ")");
  355. Serials.RegisterPort(port2, description);
  356. END Install;
  357. (** Install a virtual sniffer port as proxy for the specified serial port *)
  358. PROCEDURE InstallSniffer*(context : Commands.Context); (** [portNbr] ~ *)
  359. VAR
  360. portSniffer : PortSniffer; port : Serials.Port;
  361. portNbr : LONGINT;
  362. description : ARRAY 128 OF CHAR;
  363. BEGIN
  364. IF context.arg.GetInteger(portNbr, FALSE) & IsValidPortNumber(portNbr) THEN
  365. port := Serials.GetPort(portNbr);
  366. IF port # NIL THEN
  367. NEW(portSniffer, port, NIL, NIL);
  368. description := "Virtual Serial Port (Sniffer linked to ";
  369. Strings.Append(description, port.name); Strings.Append(description, ")");
  370. Serials.RegisterPort(portSniffer, description);
  371. context.out.String("Registered serial port sniffer for port "); context.out.Int(portNbr, 0); context.out.Ln;
  372. ELSE
  373. context.out.String("Port "); context.out.Int(portNbr, 0); context.out.String(" not found."); context.out.Ln;
  374. END;
  375. ELSE
  376. context.out.String("Invalid port number"); context.out.Ln;
  377. END;
  378. END InstallSniffer;
  379. PROCEDURE Cleanup;
  380. VAR portNbr : LONGINT;
  381. BEGIN
  382. FOR portNbr := 1 TO Serials.MaxPorts DO
  383. active[portNbr] := FALSE;
  384. END;
  385. END Cleanup;
  386. PROCEDURE SetErrorRate*(context: Commands.Context);
  387. VAR rate, divisor: LONGINT;
  388. BEGIN
  389. IF context.arg.GetInteger(rate,FALSE) & context.arg.GetInteger(divisor,FALSE) THEN
  390. errorRate := rate / divisor;
  391. END;
  392. END SetErrorRate;
  393. BEGIN
  394. Modules.InstallTermHandler(Cleanup);
  395. NEW(random); errorRate := 0;
  396. END SerialsVirtual.
  397. SerialsVirtual.SetErrorRate 0 1 ~
  398. SerialsVirtual.SetErrorRate 1 100000 ~