AirScout/ScoutBase/ScoutBase.CAT/CommPort.cs

881 wiersze
30 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.IO.Ports;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.Remoting.Channels;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace ScoutBase.CAT
{
// enums
public enum CommWorkerEvent
{
evErr = -1,
evNotify,
evPortStat,
evOnlineStat,
evSent,
evRcvd,
evStatusLine,
evTimeout
// not supported: ,evRlsd
}
[Description("Stopbits")]
public enum StopBits
{
[Description("1")]
sbOne,
[Description("1,5")]
sbOne_5,
[Description("2")]
sbTwo
}
[Description("Parity")]
public enum Parity
{
[Description("None")]
ptNone,
[Description("Odd")]
ptOdd,
[Description("Even")]
ptEven,
[Description("Mark")]
ptMark,
[Description("Space")]
ptSpace
}
[Description("Flow Control")]
public enum FlowControl
{
[Description("Low")]
fcLow,
[Description("High")]
fcHigh,
[Description("Handshake")]
fcHandShake
}
public enum RxBlockMode { rbChar, rbBlockSize, rbTerminator }
public class CommPort : BackgroundWorker
{
public readonly int BufSize = 1024;
private DateTime FNextStatusTime = DateTime.MinValue;
private DateTime FDeadLineTime = DateTime.MinValue;
// command queue
public CommandQueue FQueue = new CommandQueue();
// serial port
private SerialPort COM = new SerialPort();
private FlowControl FRtsMode = FlowControl.fcLow;
private FlowControl FDtrMode = FlowControl.fcLow;
// public properties
public string PortName { get { return GetPortName(); } set { SetPortName(value); } }
public int BaudRate { get { return GetBaudRate(); } set { SetBaudRate(value); } }
public int DataBits { get { return GetDataBits(); } set { SetDataBits(value); } }
public StopBits StopBits { get { return GetStopBits(); } set { SetStopBits(value); } }
public Parity Parity { get { return GetParity(); } set { SetParity(value); } }
public FlowControl DtrMode { get { return FDtrMode; } set { SetDtrMode(value); } }
public FlowControl RtsMode { get { return FRtsMode; } set { SetRtsMode(value); } }
public bool Open { get { return GetOpen(); } }
public bool RtsBit { get { return GetRtsBit(); } set { SetRtsBit(value); } }
public bool DtrBit { get { return GetDtrBit(); } set { SetDtrBit(value); } }
public bool CtsBit { get; internal set; }
public bool DsrBit { get; internal set; }
public bool RlsdBit { get { return GetRlsdBit(); } } //(receive-line-signal-detect, not supported yet
// public events
public delegate void CommNotifyEventHandler(object sender);
public event CommNotifyEventHandler OnError;
public event CommNotifyEventHandler OnReceived;
public event CommNotifyEventHandler OnSent;
public event CommNotifyEventHandler OnCtsDsr;
//------------------------------------------------------------------------------
// Initialization
//------------------------------------------------------------------------------
public CommPort()
{
// enable progress report & cancellation
WorkerReportsProgress = true;
WorkerSupportsCancellation = true;
// create serial port object
SerialPort COM = new SerialPort();
COM.ReadBufferSize = BufSize;
//set default comm paraams
PortName = "COM1";
BaudRate = 19200;
DataBits = 8;
StopBits = StopBits.sbOne;
Parity = Parity.ptNone;
FDtrMode = FlowControl.fcLow;
RtsMode = FlowControl.fcLow;
// do not use xon/off so far
/*
FDcb.XonLim := BUF_SIZE div 2;
FDcb.XoffLim := MulDiv(BUF_SIZE, 3, 4);
FDcb.XonChar := #17; //$11
FDcb.XoffChar := #19; //$13
*/
}
//------------------------------------------------------------------------------
// Get/set
//------------------------------------------------------------------------------
private bool GetOpen()
{
if (COM != null)
{
return COM.IsOpen;
}
return false;
}
private string GetPortName()
{
if (COM != null)
{
return COM.PortName;
}
return "";
}
private int GetBaudRate()
{
if (COM != null)
{
return COM.BaudRate;
}
return 0;
}
private int GetDataBits()
{
if (COM != null)
{
return COM.DataBits;
}
return 0;
}
private Parity GetParity()
{
if (COM != null)
{
switch (COM.Parity)
{
case System.IO.Ports.Parity.None:
return Parity.ptNone;
case System.IO.Ports.Parity.Odd:
return Parity.ptOdd;
case System.IO.Ports.Parity.Even:
return Parity.ptEven;
case System.IO.Ports.Parity.Mark:
return Parity.ptMark;
case System.IO.Ports.Parity.Space:
return Parity.ptSpace;
}
}
return Parity.ptNone;
}
private StopBits GetStopBits()
{
if (COM != null)
{
switch(COM.StopBits)
{
case System.IO.Ports.StopBits.One:
return StopBits.sbOne;
case System.IO.Ports.StopBits.OnePointFive:
return StopBits.sbOne_5;
case System.IO.Ports.StopBits.Two:
return StopBits.sbTwo;
}
}
return StopBits.sbOne;
}
private void SetPortName(string value)
{
if (COM != null)
{
// check for empty values --> not allowed!
if (!String.IsNullOrEmpty(value))
{
COM.PortName = value;
}
}
}
private void SetBaudRate(int value)
{
if (COM != null)
{
COM.BaudRate = value;
}
}
private void SetDataBits(int value)
{
if (COM != null)
{
COM.DataBits = Math.Max(5, Math.Min(8, value));
}
}
private void SetParity(Parity value)
{
if (COM != null)
{
switch(value)
{
case Parity.ptNone:
COM.Parity = System.IO.Ports.Parity.None;
break;
case Parity.ptOdd:
COM.Parity = System.IO.Ports.Parity.Odd;
break;
case Parity.ptEven:
COM.Parity = System.IO.Ports.Parity.Even;
break;
case Parity.ptMark:
COM.Parity = System.IO.Ports.Parity.Mark;
break;
case Parity.ptSpace:
COM.Parity = System.IO.Ports.Parity.Space;
break;
}
}
}
private void SetStopBits(StopBits value)
{
if (COM != null)
{
switch (value)
{
case StopBits.sbOne:
COM.StopBits = System.IO.Ports.StopBits.One;
break;
case StopBits.sbOne_5:
COM.StopBits = System.IO.Ports.StopBits.OnePointFive;
break;
case StopBits.sbTwo:
COM.StopBits = System.IO.Ports.StopBits.Two;
break;
}
}
}
//------------------------------------------------------------------------------
// Read/write
//------------------------------------------------------------------------------
public void PurgeRx()
{
if (COM == null)
return;
COM.DiscardInBuffer();
}
public void PurgeTx()
{
if (COM == null)
return;
COM.DiscardOutBuffer();
}
//------------------------------------------------------------------------------
// Fire events
//------------------------------------------------------------------------------
private void FireErrEvent()
{
if (OnError != null)
{
OnError.Invoke(this);
}
}
private void FireTxEvent()
{
if (OnSent != null)
{
OnSent.Invoke(this);
}
}
private void FireRxEvent()
{
if (OnReceived != null)
{
OnReceived.Invoke(this);
}
}
private void FireCtsDsrEvent()
{
if (OnCtsDsr != null)
{
OnCtsDsr.Invoke(this);
}
}
//------------------------------------------------------------------------------
// Control bits
//------------------------------------------------------------------------------
private bool GetCtsBit()
{
if (COM != null)
{
return COM.CtsHolding;
}
return false;
}
private bool GetDsrBit()
{
if (COM != null)
{
return COM.DsrHolding;
}
return false;
}
private bool GetDtrBit()
{
return COM.DtrEnable;
}
private bool GetRtsBit()
{
return COM.RtsEnable;
}
private bool GetRlsdBit()
{
// not implemented
return false;
}
private void SetDtrBit(bool value)
{
if (COM != null)
{
COM.DtrEnable = value;
}
}
private void SetRtsBit(bool value)
{
if (COM != null)
{
COM.RtsEnable = value;
}
}
private void SetDtrMode(FlowControl value)
{
if (COM != null)
{
switch (value)
{
case FlowControl.fcLow:
COM.Handshake = Handshake.None;
COM.DtrEnable = false;
break;
case FlowControl.fcHigh:
COM.Handshake = Handshake.None;
COM.DtrEnable = true;
break;
case FlowControl.fcHandShake:
COM.Handshake = Handshake.RequestToSend;
break;
}
}
}
private void SetRtsMode(FlowControl value)
{
if (COM != null)
{
switch (value)
{
case FlowControl.fcLow:
COM.Handshake = Handshake.None;
COM.RtsEnable = false;
break;
case FlowControl.fcHigh:
COM.Handshake = Handshake.None;
COM.RtsEnable = true;
break;
case FlowControl.fcHandShake:
COM.Handshake = Handshake.RequestToSend;
break;
}
}
}
//------------------------------------------------------------------------------
// Queue
//------------------------------------------------------------------------------
public void AddCommands(List<RigCommand> cmds, CommandKind kind)
{
lock (FQueue)
{
for (int i = 0; i < cmds.Count; i++)
{
QueueItem item = new QueueItem();
item.Code = cmds[i].Code;
item.Number = i;
item.ReplyLength = cmds[i].ReplyLength;
item.ReplyEnd = ByteFuns.BytesToStr(cmds[i].ReplyEnd);
item.Kind = kind;
FQueue.Add(item);
}
}
}
//------------------------------------------------------------------------------
// Background worker
//------------------------------------------------------------------------------
protected override void OnDoWork(DoWorkEventArgs e)
{
// get the startup parameters
CommPortStartParams StartParams = (CommPortStartParams)e.Argument;
byte[] RxBuffer = null;
RxBlockMode RxBlockMode = RxBlockMode.rbChar;
int RxBlockSize = 0;
string RxBlockTerminator = "";
bool PortInitialized = true; // set to true to get a possible error message once
bool PortOpen = false;
bool Online = false;
// remember old values for check on changes
int OldRxChars = 0;
int OldTxChars = 0;
bool oldrts = false;
bool olddtr = false;
bool oldcts = false;
bool olddsr = false;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
{
Thread.CurrentThread.Name = "CommWorker";
}
// .NET SerialPort implementation does not have full functionality
// so we try to poll the interface for all information needed and fire according events
while (!this.CancellationPending)
{
try
{
// hopefully we got a valid SerialPort object
// skip, if not
if (COM == null)
{
throw new NullReferenceException("COM port is null.");
}
// try to start communication, if not started
if (!COM.IsOpen)
{
try
{
// try to open port, if not open
COM.Open();
OldRxChars = COM.BytesToRead;
OldTxChars = COM.BytesToWrite;
CtsBit = COM.CtsHolding;
DsrBit = COM.DsrHolding;
PortInitialized = true;
// clear buffers
PurgeRx();
PurgeTx();
RxBuffer = null;
// set initial queue and phase, status
lock (FQueue)
{
FQueue.Clear();
FQueue.Phase = ExchangePhase.phIdle;
}
Online = false;
// queue initial commands
this.ReportProgress((int)CommWorkerEvent.evNotify, "Adding init commands to queue.");
AddCommands(StartParams.RigCommands.InitCmd, CommandKind.ckInit);
AddCommands(StartParams.RigCommands.StatusCmd, CommandKind.ckStatus);
}
catch (Exception ex)
{
// report port status on change
if (PortOpen)
{
PortOpen = false;
this.ReportProgress((int)CommWorkerEvent.evPortStat, false);
}
if (PortInitialized)
{
PortInitialized = false;
throw new InvalidOperationException("Cannot start communication (" + ex.Message + ")");
}
}
}
// skip if COM is not open
if (COM.IsOpen)
{
// report port status on change
if (!PortOpen)
{
PortOpen = true;
this.ReportProgress((int)CommWorkerEvent.evPortStat, true);
}
}
else
{
// report port status on change
if (PortOpen)
{
PortOpen = false;
this.ReportProgress((int)CommWorkerEvent.evPortStat, false);
}
Thread.Sleep(1000);
continue;
}
// check if status refresh is necessary
if (Online && (DateTime.Now > FNextStatusTime))
{
// are there already status commands in queue?
// add, if not
if (FQueue.HasStatusCommands())
{
this.ReportProgress((int)CommWorkerEvent.evNotify, "Status commands already in queue.");
}
else
{
this.ReportProgress((int)CommWorkerEvent.evNotify, "Adding status commands to queue.");
AddCommands(StartParams.RigCommands.StatusCmd, CommandKind.ckStatus);
}
// set next status refresh time
FNextStatusTime = DateTime.Now.AddMilliseconds(StartParams.PollMs);
}
// are we in phIdle phase?
if (FQueue.Phase == ExchangePhase.phIdle)
{
// rx chars available? --> read them
int rxcount = COM.BytesToRead;
if (rxcount > 0)
{
byte[] buf = new byte[rxcount];
COM.Read(buf, 0, rxcount);
// report unexpected bytes
this.ReportProgress((int)CommWorkerEvent.evErr,
"Unexpected bytes in RX buffer: " + ((StartParams.RigCommands.CmdType == CommandType.ctBinary)? ByteFuns.BytesToHex(buf) : "\"" + ByteFuns.BytesToStr(buf) + "\""));
PurgeRx();
RxBuffer = null;
}
// check queue and send next command if any
lock (FQueue)
{
if (FQueue.Count > 0)
{
// clear all buffers
PurgeRx();
PurgeTx();
RxBuffer = null;
// prepare for receiving reply
RxBlockSize = FQueue[0].ReplyLength;
RxBlockTerminator = FQueue[0].ReplyEnd;
if (!String.IsNullOrEmpty(FQueue[0].ReplyEnd))
RxBlockMode = RxBlockMode.rbTerminator;
else if (FQueue[0].ReplyLength > 0)
RxBlockMode = RxBlockMode.rbBlockSize;
else RxBlockMode = RxBlockMode.rbChar;
// log
string s = "";
switch (FQueue[0].Kind)
{
case CommandKind.ckInit:
s = "init";
break;
case CommandKind.ckWrite:
s = StartParams.RigCommands.ParamToStr(FQueue[0].Param);
break;
case CommandKind.ckStatus:
s = "status";
break;
case CommandKind.ckCustom:
s = "custom";
break;
}
this.ReportProgress((int)CommWorkerEvent.evNotify, "Sending " + s + " command (" +
((StartParams.RigCommands.CmdType == CommandType.ctBinary)? ByteFuns.BytesToHex(FQueue[0].Code) : "\"" + ByteFuns.BytesToStr(FQueue[0].Code) + "\"") + ")");
// send command
byte[] buf = FQueue[0].Code;
COM.Write(buf, 0, buf.Length);
FQueue.Phase = ExchangePhase.phSending;
}
}
}
// are we in phSending phase?
else if (FQueue.Phase == ExchangePhase.phSending)
{
// still bytes to send?
int txcount = COM.BytesToWrite;
if (txcount > 0)
{
// do nothing and wait until all bytes are sent
}
else
{
// report finish
this.ReportProgress((int)CommWorkerEvent.evSent);
// do we need reply?
if (FQueue.CurrentCmd().NeedsReply())
{
// set receiving phase and deadline time
FQueue.Phase = ExchangePhase.phReceiving;
FDeadLineTime = DateTime.Now.AddMilliseconds(StartParams.TimeoutMs);
}
else
{
// delete command and set idle phase
lock (FQueue)
{
FQueue.Delete(0);
FDeadLineTime = DateTime.MaxValue;
FQueue.Phase = ExchangePhase.phIdle;
}
}
}
}
// are we in receiving phase?
else if (FQueue.Phase == ExchangePhase.phReceiving)
{
// rx chars available? --> read them
int rxcount = COM.BytesToRead;
if (rxcount > 0)
{
byte[] buf = new byte[rxcount];
COM.Read(buf, 0, rxcount);
// initally copy buffer, else resize and append
if (RxBuffer == null)
{
RxBuffer = buf;
}
else
{
int oldlen = RxBuffer.Length;
Array.Resize(ref RxBuffer, RxBuffer.Length + rxcount);
Array.Copy(buf, 0, RxBuffer, oldlen, rxcount);
}
// fire rx event if rx complete (according to RxBlockMode)
bool fire = false;
switch (RxBlockMode)
{
case RxBlockMode.rbBlockSize:
fire = RxBuffer.Length >= RxBlockSize;
break;
case RxBlockMode.rbTerminator:
// convert RXBuffer to string
// be sure to use a converter with full 8bit conversion
var MyEncoding = Encoding.GetEncoding("Windows-1252");
string rxstring = MyEncoding.GetString(RxBuffer);
fire = rxstring.EndsWith(RxBlockTerminator);
break;
// rbChar
default:
fire = true;
break;
}
if (fire)
{
Online = true;
// report that we are online
this.ReportProgress((int)CommWorkerEvent.evOnlineStat, true);
// report received
// put the current command in the message, so that it won't get lost during processing
this.ReportProgress((int)CommWorkerEvent.evRcvd, new CommReceived(FQueue.CurrentCmd(), RxBuffer));
RxBuffer = null;
// still might be a wrong answer
// remove the command and set idle state anyway
lock (FQueue)
{
FQueue.Delete(0);
FQueue.Phase = ExchangePhase.phIdle;
}
}
}
// check for Timeout
if (DateTime.Now > FDeadLineTime)
{
if (PortOpen)
{
// report timeout
Online = false;
this.ReportProgress((int)CommWorkerEvent.evTimeout, FQueue.CurrentCmd());
this.ReportProgress((int)CommWorkerEvent.evOnlineStat, false);
}
// clear all buffers
PurgeRx();
PurgeTx();
lock(FQueue)
{
FQueue.Clear();
FQueue.Phase = ExchangePhase.phIdle;
}
// queue initial commands
this.ReportProgress((int)CommWorkerEvent.evNotify, "Adding init commands to queue.");
AddCommands(StartParams.RigCommands.InitCmd, CommandKind.ckInit);
AddCommands(StartParams.RigCommands.StatusCmd, CommandKind.ckStatus);
}
}
// status line changed?
bool cts = COM.CtsHolding;
bool dsr = COM.DsrHolding;
bool rts = COM.RtsEnable;
bool dtr = COM.DtrEnable;
if (oldrts != rts)
{
// report status line change
oldrts = rts;
this.ReportProgress((int)CommWorkerEvent.evStatusLine);
}
if (olddtr != dtr)
{
// report status line change
olddtr = dtr;
this.ReportProgress((int)CommWorkerEvent.evStatusLine);
}
if (oldcts != cts)
{
// report status line change
oldcts = cts;
this.ReportProgress((int)CommWorkerEvent.evStatusLine);
}
if (olddsr != dsr)
{
// report status line change
olddsr = dsr;
this.ReportProgress((int)CommWorkerEvent.evStatusLine);
}
Thread.Sleep(StartParams.COMPollMs);
}
catch (Exception ex)
{
// report error
this.ReportProgress((int)CommWorkerEvent.evErr, ex.ToString());
// try to close COM
if ((COM != null) && (COM.IsOpen))
{
try
{
COM.Close();
}
catch
{
// do nothing
}
}
// sleep a while
Thread.Sleep(1000);
}
}
// try to close COM
if ((COM != null) && (COM.IsOpen))
{
try
{
COM.Close();
}
catch
{
// do nothing
}
}
}
}
}