623 lines
17 KiB
C#
623 lines
17 KiB
C#
///
|
|
/// Copyright (c) 2013-2015 Sensus Metering Systems
|
|
///
|
|
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
|
|
}
|
|
|
|
}
|
|
}
|