tbf/TestBenchFramework/BenchControl/Cameras/IdcCamera/SerialLayer.cs
2014-07-28 09:56:40 +02:00

620 lines
17 KiB
C#

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<byte> 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;
/// <summary>
/// Multiplexed channel content ID (destination ID)
/// </summary>
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
/// <summary>
/// Create a SerialLayer instance.
/// </summary>
/// <param name="cameraNr">0-based camera number</param>
/// <param name="comPortNr">COM port number 1..999</param>
/// <param name="baudRate">Baud rate 57600 or 115200</param>
/// <param name="readTimeout">Read timeout in ms</param>
/// <param name="imagesPath">Path to a folder where received files are stored</param>
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<byte>();
queueNotEmptyEvent = new ManualResetEvent(false);
serialPort = new SerialPort();
serialPort.DataReceived += serialPort_DataReceived;
}
/// <summary>
/// Open the serial port.
/// </summary>
/// <returns>RetVal.OK or RetVal.CANNOT_OPEN_COMPORT</returns>
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;
}
/// <summary>
/// Close the serial port.
/// </summary>
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
}
/// <summary>
/// Serial port data received event handler method
/// </summary>
/// <param name="sender">SerialPort</param>
/// <param name="e"></param>
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
/// <summary>
/// Read one byte from the console content FIFO.
/// </summary>
/// <param name="response">Received byte</param>
/// <returns>RetVal.OK, RetVal.COMM_TIMEOUT or RetVal.SHUTDOWN</returns>
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;
}
}
/// <summary>
/// Read bytes and form the output string until a delimiter character is received.
/// Output string excludes the delimiter.
/// </summary>
/// <param name="delimiter">Delimiter character</param>
/// <param name="text">Output string</param>
/// <returns>RetVal.OK, RetVal.COMM_TIMEOUT or RetVal.SHUTDOWN</returns>
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;
}
/// <summary>
/// Read bytes and form the output string until "\r\n" is received.
/// Output string excludes "\r\n".
/// </summary>
/// <param name="text">Output string</param>
/// <returns>RetVal.OK, RetVal.COMM_TIMEOUT, RetVal.COMM_ERROR or RetVal.SHUTDOWN</returns>
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
/// <summary>
/// Write one byte (console content) to the serial port.
/// </summary>
/// <param name="b">Byte to send</param>
public void WriteByte(byte b)
{
WriteByte_Mux(b, Mux.Console);
}
/// <summary>
/// Write one byte from any source (either console or file) to the serial port.
/// Perform multiplexing - expansion to escape sequences using CTRLZ.
/// </summary>
/// <param name="b">Byte to send</param>
/// <param name="contentId">Mux.Console or Mux.File</param>
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;
}
}
/// <summary>
/// Write a string (console content) to the serial port.
/// </summary>
/// <param name="str">String to send</param>
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 <cr>
// 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 <cr>
// 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 <cr>
// 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;
}
/// <summary>
/// Get camera version string
/// </summary>
/// <param name="versionStr">Version string</param>
/// <returns>RetVal.OK, RetVal.COMM_TIMEOUT, RetVal.COMM_ERROR, RetVal.SHUTDOWN or a camera ret.code (1..999)</returns>
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
}
}
}