using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Ports; using System.Text; using System.Threading; using log4net; namespace TBF.BenchControl.Cameras.IdcCamera { public class SerialLayer { static readonly ILog log = LogManager.GetLogger(typeof(SerialLayer)); /// /// Set to true when shut-down is in progress /// public static bool g_shutDown = false; public bool m_disconnecting; int cameraNr; int comPortNr; int baudRate; int readTimeout; string imagesPath; Queue readQueue; ManualResetEvent queueNotEmptyEvent; SerialFileIO serialFileIO; SerialPort serialPort; byte[] singleCharWrBuff; /// predefined serial write buffer for 1 byte byte[] twoCharsWrBuff; /// predefined serial write buffer for 2 bytes #region Content switching, multiplexing const byte CTRLZ = 26; const byte CTRLQ = 17; const byte XON = 17; const byte CTRLS = 19; const byte XOFF = 19; /// /// Multiplexed channel content ID (destination ID) /// enum Mux { Console, /// Command console, identified by CTRLZ + 'C' byte sequence File, /// File download (ICD camera -> file), identified by CTRLZ = 'F' byte sequence } Mux lastWriteContentId; Mux currentReadContentId; bool lastReadCtrlZ; #endregion /// /// Create a SerialLayer instance. /// /// 0-based camera number /// COM port number 1..999 /// Baud rate 57600 or 115200 /// Read timeout in ms /// Path to a folder where received files are stored public SerialLayer(int cameraNr, int comPortNr, int baudRate, int readTimeout, string imagesPath) { log.InfoFormat("new SerialLayer(ix={0}, com{1}, {2}Bd, tmout={3}ms, ipath={4})", cameraNr, comPortNr, baudRate, readTimeout, imagesPath); this.cameraNr = cameraNr; this.comPortNr = comPortNr; this.baudRate = baudRate; this.readTimeout = readTimeout; this.imagesPath = imagesPath; singleCharWrBuff = new byte[1]; twoCharsWrBuff = new byte[2]; twoCharsWrBuff[0] = CTRLZ; m_disconnecting = false; readQueue = new Queue(); queueNotEmptyEvent = new ManualResetEvent(false); serialPort = new SerialPort(); serialPort.DataReceived += serialPort_DataReceived; } /// /// Open the serial port. /// /// RetVal.OK or RetVal.CANNOT_OPEN_COMPORT public RetVal OpenPort() { log.Info("OpenPort()"); if (serialPort.IsOpen) return RetVal.OK; if (comPortNr<1 || comPortNr>999) return RetVal.CannotOpenComPort; m_disconnecting = false; /// Initialize FIFO, content switching, multiplexing readQueue.Clear(); lastWriteContentId = Mux.Console; currentReadContentId = Mux.Console; lastReadCtrlZ = false; serialFileIO = new SerialFileIO(cameraNr, imagesPath); /// Finally open the serial port try { serialPort.PortName = "COM" + comPortNr.ToString(); serialPort.BaudRate = baudRate; serialPort.Parity = Parity.None; serialPort.DataBits = 8; serialPort.StopBits = StopBits.One; serialPort.Handshake = Handshake.None; serialPort.RtsEnable = true; serialPort.DtrEnable = true; serialPort.ReadTimeout = 1000000; /// Sufficiently large, read timeout is applied to FIFO read serialPort.WriteTimeout = SerialPort.InfiniteTimeout; serialPort.Open(); } catch (Exception e) { log.Error("Cannot open serial port COM" + comPortNr.ToString(), e); return RetVal.CannotOpenComPort; } return RetVal.OK; } /// /// Close the serial port. /// public void ClosePort() { log.InfoFormat("{0}: ClosePort()", cameraNr); if (!serialPort.IsOpen) return; m_disconnecting = true; /// Complete any pending I/O operations serialPort.Close(); } public void FifoFlush() { lock (readQueue) { readQueue.Clear(); } } public void UnlockLastRead() { lock (readQueue) { readQueue.Enqueue((byte)' '); } /// quit infinite fifo.Get() waiting } /// /// Serial port data received event handler method /// /// SerialPort /// void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { byte[] data = new byte[serialPort.BytesToRead]; serialPort.Read(data, 0, data.Length); foreach (byte dat in data) { byte b = dat; if (lastReadCtrlZ) { lastReadCtrlZ = false; if (dat == (byte)'C') { currentReadContentId = Mux.Console; /// Ctrl-Z + 'C' sequence detected: Mux.Console continue; } if (dat == (byte)'F') { currentReadContentId = Mux.File; /// Ctrl-Z + 'F' sequence detected: Mux.File continue; } if (dat == (byte)'1') b = (byte)XON; /// Ctrl-Z + '1' sequence detected: XON if (dat == (byte)'2') b = (byte)XOFF; /// Ctrl-Z + '2' sequence detected: XOFF } else if (dat == CTRLZ) { lastReadCtrlZ = true; continue; } /// /// Process demultiplexed data byte b appropriately /// if (currentReadContentId == Mux.Console) { lock (readQueue) { readQueue.Enqueue(b); queueNotEmptyEvent.Set(); } } else { serialFileIO.ProcessByte(b); } } } #region Read from readQueue /// /// Read one byte from the console content FIFO. /// /// Received byte /// RetVal.OK, RetVal.COMM_TIMEOUT or RetVal.SHUTDOWN public RetVal ReadByte(out byte response) { if (g_shutDown || m_disconnecting) { response = 0; log.DebugFormat("{0}: ReadByte() returns RetVal.SHUTDOWN", cameraNr); return RetVal.ShutdownInProgress; } lock (readQueue) { if (readQueue.Count > 0) { response = readQueue.Dequeue(); return RetVal.OK; } else { queueNotEmptyEvent.Reset(); } } if (queueNotEmptyEvent.WaitOne(readTimeout)) { lock (readQueue) { response = readQueue.Dequeue(); return RetVal.OK; } } else { response = 0; log.ErrorFormat("{0}: ReadByte() returns RetVal.COMM_TIMEOUT", cameraNr); return RetVal.CommTimeout; } } /// /// Read bytes and form the output string until a delimiter character is received. /// Output string excludes the delimiter. /// /// Delimiter character /// Output string /// RetVal.OK, RetVal.COMM_TIMEOUT or RetVal.SHUTDOWN RetVal ReadUntil(char delimiter, out string output) { StringBuilder buffer = new StringBuilder(); while (true) { RetVal retv; byte znak; if (RetVal.OK != (retv = ReadByte(out znak))) { output = buffer.ToString(); /// Copy data received so far to the output log.Error("ReadUntil('" + delimiter.ToString() + "', out \"" + output + "\") returns " + retv.ToString()); return retv; /// Communication error } char c = (char)znak; if (c == delimiter) break; buffer.Append(c); } output = buffer.ToString(); return RetVal.OK; } /// /// Read bytes and form the output string until "\r\n" is received. /// Output string excludes "\r\n". /// /// Output string /// RetVal.OK, RetVal.COMM_TIMEOUT, RetVal.COMM_ERROR or RetVal.SHUTDOWN RetVal ReadLine(out string output) { RetVal retv = ReadUntil('\r', out output); if (retv != RetVal.OK) return retv; /// Read the expected '\n' byte znak; if (RetVal.OK != (retv = ReadByte(out znak))) return retv; if (znak != (byte)'\n') { log.Error("ReadLine(out \"" + output + "\") returns " + retv.ToString()); return RetVal.CommError; } return RetVal.OK; } #endregion #region Write to serial port /// /// Write one byte (console content) to the serial port. /// /// Byte to send public void WriteByte(byte b) { WriteByte_Mux(b, Mux.Console); } /// /// Write one byte from any source (either console or file) to the serial port. /// Perform multiplexing - expansion to escape sequences using CTRLZ. /// /// Byte to send /// Mux.Console or Mux.File void WriteByte_Mux(byte b, Mux contentId) { if (contentId != lastWriteContentId) { /// Switching contentId when writing to a camera is not supported yet, /// therefore this code within { } wont be invoked right now. /// Send the content switch command CTRLZ + contentId twoCharsWrBuff[1] = (contentId == Mux.Console) ? (byte)'C' : (byte)'F'; serialPort.Write(twoCharsWrBuff, 0, 2); lastWriteContentId = contentId; return; } if (b == CTRLZ) { /// If 'b'=CTRLZ send CTRLZ + CTRLZ twoCharsWrBuff[1] = CTRLZ; serialPort.Write(twoCharsWrBuff, 0, 2); return; } else if (b == XON) { /// If 'b'=XON send CTRLZ + '1' twoCharsWrBuff[1] = (byte)'1'; serialPort.Write(twoCharsWrBuff, 0, 2); return; } else if (b == XOFF) { /// If 'b'=XON send CTRLZ + '2' twoCharsWrBuff[1] = (byte)'2'; serialPort.Write(twoCharsWrBuff, 0, 2); return; } else { /// Send a single regular byte 'b' singleCharWrBuff[0] = b; serialPort.Write(singleCharWrBuff, 0, 1); return; } } /// /// Write a string (console content) to the serial port. /// /// String to send public void WriteString(string str) { if (str == null) return; /// There is nothing to send - return if (str.EndsWith("\r\n")) { log.InfoFormat("{0}: WriteString(\"{1}\\r\\n\")", cameraNr, str.Substring(0, str.Length - 2)); } else { log.InfoFormat("{0}: WriteString(\"{1}\")", cameraNr, str); } for (int i = 0; i < str.Length; i++) WriteByte((byte)str[i]); } #endregion //------------------------------------------------------------------------------- ///////////////////////// Higher level console commands /////////////////////// //------------------------------------------------------------------------------- //--------------------------------------------------------------------------- // Sends the specified command and waits for the following response. // strings separated by spaces and terminated by // Stores response into an array of CStrings pointed by pStrArray. // // Returns: // positive numbers and 0 // The number of fields in the response. Returned number may be // greater then arrayLen. Only the first arrayLen strings are saved. // negative numbers // negative error code returned by lower level functions: // -RetVal.COMM_TIMEOUT = timeout occurred // -RetVal.COMM_ERROR = all other errors //--------------------------------------------------------------------------- public int CommandStr(string cmd, out string[] strArray, int nrFields, out string remaining) { StringBuilder tmp = new StringBuilder(); StringBuilder logStr = new StringBuilder(); StringBuilder rem = new StringBuilder(); lock (readQueue) { readQueue.Clear(); } WriteString(cmd); /// send strArray = null; remaining = String.Empty; string beforeGT; RetVal retv = ReadUntil('>', out beforeGT); if (retv != RetVal.OK) return -(int)retv; /// If Read... fails quit the loop ... /// ... and return negative error code /// Read the expected ' ' byte znak; if (RetVal.OK != (retv = ReadByte(out znak))) return -(int)retv; if (znak != (byte)' ') return -(int)RetVal.CommError; /// Read fields after '>', parse the first field (the return code) string received; retv = ReadLine(out received); if (retv != RetVal.OK) return -(int)retv; strArray = received.Split(new char[] { ' ' }, nrFields + 1); log.InfoFormat("{0}: Received: > {1}", cameraNr, received); /// Skip until '$' received retv = ReadUntil('$', out received); if (retv != RetVal.OK) return -(int)retv; /// Parse the return code (the 1st field) int returnCode; if (!int.TryParse(strArray[0], out returnCode) || returnCode < 0) return returnCode; if (strArray.GetLength(0) == nrFields + 1) remaining = strArray[nrFields]; return Math.Min(strArray.GetLength(0), nrFields); } //--------------------------------------------------------------------------- // Sends the specified command and waits for the following response. // integers separated by spaces and terminated by // Stores response into an integer array pointed by pIntArray; // // Returns: // positive numbers and 0 // The number of fields in the response. Returned number may be // greater then arrayLen. Only the first arrayLen strings are saved. // negative numbers // negative error code returned by lower level functions //--------------------------------------------------------------------------- public int CommandInt(string cmd, out int[] intArray, int nrFields, out string remaining) { string[] strArray; int retv = CommandStr(cmd, out strArray, nrFields, out remaining); if (retv <= 0) { intArray = null; return retv; } intArray = new int[retv]; for (int i = 0; i < retv; i++) { int.TryParse(strArray[i], out intArray[i]); } return retv; } //--------------------------------------------------------------------------- // Sends the specified command and waits for the following response. // real numbers separated by spaces and terminated by // Stores response into a double array pointed by pDblArray; // // Returns: // positive numbers and 0 // The number of fields in the response. Returned number may be // greater then arrayLen. Only the first arrayLen strings are saved. // negative numbers // negative error code returned by lower level functions //--------------------------------------------------------------------------- public int CommandDbl(string cmd, out double[] dblArray, int arrayLen, out string remaining) { string[] strArray; int retv = CommandStr(cmd, out strArray, arrayLen, out remaining); if (retv <= 0) { dblArray = null; return retv; } dblArray = new double[retv]; for (int i = 0; i < retv; i++) { double.TryParse(strArray[i], out dblArray[i]); } return retv; } /// /// Get camera version string /// /// Version string /// RetVal.OK, RetVal.COMM_TIMEOUT, RetVal.COMM_ERROR, RetVal.SHUTDOWN or a camera ret.code (1..999) public RetVal GetVer(out string versionStr) { log.DebugFormat("{0}: GetVer()", cameraNr); versionStr = String.Empty; StringBuilder tmp = new StringBuilder(); const int arrayLen = 1; string[] strArray = new string[arrayLen]; lock (readQueue) { readQueue.Clear(); } WriteString("info\r\n"); /// Send /// Read info message until '>' string infoMessage; RetVal retv = ReadUntil('>', out infoMessage); if (retv != RetVal.OK) return retv; log.DebugFormat("{0}: InfoMessage: {1}", cameraNr, infoMessage); /// Read the expected ' ' byte znak; if (RetVal.OK != (retv = ReadByte(out znak))) return retv; if (znak != (byte)' ') return RetVal.CommError; /// Read fields after '>', parse the first field (the return code) string tempStr; retv = ReadLine(out tempStr); if (retv != RetVal.OK) return retv; string[] fields = tempStr.Split(new char[] { ' ' }, 2); int returnCode; if (!int.TryParse(fields[0], out returnCode) || returnCode < 0) return (RetVal)(-returnCode); /// Skip until '$' received retv = ReadUntil('$', out tempStr); if (retv != RetVal.OK) return retv; /// Parse the infoMessage int pos1 = infoMessage.IndexOf("version = "); if (pos1 != -1) { pos1 += "version = ".Length; int pos2 = infoMessage.IndexOf("\r\n", pos1); versionStr = infoMessage.Substring(pos1, pos2 - pos1); log.InfoFormat("{0}: GetVer(out \"" + versionStr + "\")", cameraNr); return RetVal.OK; } log.ErrorFormat("{0}: GetVer(...) returns RetVal.OP_FAILED", cameraNr); return RetVal.CommError; /// Cannot read the version string } //---------------------------------------------------------------------------- // Check if livestream of serial characters is coming. // // Returns: // RetVal.OK ..... not (OK), // RetVal.BUSY ... yes (camera needs to be restarted) //---------------------------------------------------------------------------- RetVal CheckLiveStream() { byte znak; // 500 characters without timeout = stream // copyright message is +/-350 characters long for (int i=0; i<500; i++) { if (RetVal.CommTimeout == ReadByte(out znak)) return RetVal.OK; /// timeout ... no stream (OK) } #if true // try to stop the stream lock (readQueue) { readQueue.Clear(); } WriteString("\r\nstopstream\r\n"); while (RetVal.CommTimeout != ReadByte(out znak)) { }; return RetVal.OK; #else // just report a running stream (RetVal.Busy) return RetVal.Busy; #endif } } }