#region CmdMessenger - MIT - (c) 2014 Thijs Elenbaas. /* CmdMessenger - library that provides command based messaging Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Copyright 2014 - Thijs Elenbaas */ #endregion using System; using System.Collections.Generic; using System.Threading; using System.Windows.Forms; using CommandMessenger.Queue; using CommandMessenger.Transport; namespace CommandMessenger { public enum SendQueue { Default, InFrontQueue, AtEndQueue, WaitForEmptyQueue, ClearQueue, } public enum ReceiveQueue { Default, WaitForEmptyQueue, ClearQueue, } public enum UseQueue { UseQueue, BypassQueue, } public enum BoardType { Bit16, Bit32, } /// Command messenger main class public class CmdMessenger : IDisposable { private CommunicationManager _communicationManager; // The communication manager private MessengerCallbackFunction _defaultCallback; // The default callback private Dictionary _callbackList; // List of callbacks private SendCommandQueue _sendCommandQueue; // The queue of commands to be sent private ReceiveCommandQueue _receiveCommandQueue; // The queue of commands to be processed /// Definition of the messenger callback function. /// The received command. public delegate void MessengerCallbackFunction(ReceivedCommand receivedCommand); /// /// Event handler for one or more lines received /// public event EventHandler NewLineReceived; /// /// Event handler for a new line sent /// public event EventHandler NewLineSent; /// Gets or sets a whether to print a line feed carriage return after each command. /// true if print line feed carriage return, false if not. public bool PrintLfCr { get { return _communicationManager.PrintLfCr; } set { _communicationManager.PrintLfCr = value; } } /// /// The control to invoke the callback on /// public Control ControlToInvokeOn { get; set; } /// Constructor. /// The transport layer. /// Embedded Processor type. Needed to translate variables between sides. public CmdMessenger(ITransport transport, BoardType boardType = BoardType.Bit16) { Init(transport, boardType, ',', ';', '/', 60); } /// Constructor. /// The transport layer. /// The maximum size of the send buffer /// Embedded Processor type. Needed to translate variables between sides. public CmdMessenger(ITransport transport, int sendBufferMaxLength, BoardType boardType = BoardType.Bit16) { Init(transport, boardType, ',', ';', '/', sendBufferMaxLength); } /// Constructor. /// The transport layer. /// Embedded Processor type. Needed to translate variables between sides. /// The field separator. public CmdMessenger(ITransport transport, BoardType boardType, char fieldSeparator) { Init(transport, boardType, fieldSeparator, ';', '/', 60); } /// Constructor. /// The transport layer. /// Embedded Processor type. Needed to translate variables between sides. /// The field separator. /// The maximum size of the send buffer public CmdMessenger(ITransport transport, BoardType boardType, char fieldSeparator, int sendBufferMaxLength) { Init(transport, boardType, fieldSeparator, ';', '/', sendBufferMaxLength); } /// Constructor. /// The transport layer. /// Embedded Processor type. Needed to translate variables between sides. /// The field separator. /// The command separator. public CmdMessenger(ITransport transport, BoardType boardType, char fieldSeparator, char commandSeparator) { Init(transport, boardType, fieldSeparator, commandSeparator, commandSeparator, 60); } /// Constructor. /// The transport layer. /// Embedded Processor type. Needed to translate variables between sides. /// The field separator. /// The command separator. /// The escape character. /// The maximum size of the send buffer public CmdMessenger(ITransport transport, BoardType boardType, char fieldSeparator, char commandSeparator, char escapeCharacter, int sendBufferMaxLength) { Init(transport, boardType, fieldSeparator, commandSeparator, escapeCharacter, sendBufferMaxLength); } /// Initializes this object. /// The transport layer. /// Embedded Processor type. Needed to translate variables between sides. /// The field separator. /// The command separator. /// The escape character. /// The maximum size of the send buffer private void Init(ITransport transport, BoardType boardType, char fieldSeparator, char commandSeparator, char escapeCharacter, int sendBufferMaxLength) { ControlToInvokeOn = null; //Logger.Open(@"sendCommands.txt"); Logger.DirectFlush = true; _receiveCommandQueue = new ReceiveCommandQueue(HandleMessage); _communicationManager = new CommunicationManager(transport, _receiveCommandQueue, boardType, commandSeparator, fieldSeparator, escapeCharacter); _sendCommandQueue = new SendCommandQueue(_communicationManager, sendBufferMaxLength); PrintLfCr = false; _receiveCommandQueue.NewLineReceived += (o, e) => InvokeNewLineEvent(NewLineReceived, e); _sendCommandQueue.NewLineSent += (o, e) => InvokeNewLineEvent(NewLineSent, e); Escaping.EscapeChars(fieldSeparator, commandSeparator, escapeCharacter); _callbackList = new Dictionary(); _sendCommandQueue.Start(); _receiveCommandQueue.Start(); } /// /// Disposal of CmdMessenger /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// Sets a control to invoke on. /// The control to invoke on. [Obsolete("Use ControlToInvokeOn property instead.")] public void SetControlToInvokeOn(Control controlToInvokeOn) { ControlToInvokeOn = controlToInvokeOn; } /// Stop listening and end serial port connection. /// true if it succeeds, false if it fails. public bool Disconnect() { return _communicationManager.Disconnect(); } /// Starts serial port connection and start listening. /// true if it succeeds, false if it fails. public bool Connect() { return _communicationManager.Connect(); } /// Attaches default callback for unsupported commands. /// The callback function. public void Attach(MessengerCallbackFunction newFunction) { _defaultCallback = newFunction; } /// Attaches default callback for certain Message ID. /// Command ID. /// The callback function. public void Attach(int messageId, MessengerCallbackFunction newFunction) { _callbackList[messageId] = newFunction; } /// Gets or sets the time stamp of the last command line received. /// The last line time stamp. public long LastReceivedCommandTimeStamp { get { return _communicationManager.LastLineTimeStamp; } } /// Handle message. /// The received command. private void HandleMessage(ReceivedCommand receivedCommand) { MessengerCallbackFunction callback = null; if (receivedCommand.Ok) { if (_callbackList.ContainsKey(receivedCommand.CmdId)) { callback = _callbackList[receivedCommand.CmdId]; } else { if (_defaultCallback != null) callback = _defaultCallback; } } else { // Empty command receivedCommand = new ReceivedCommand { CommunicationManager = _communicationManager }; } InvokeCallBack(callback, receivedCommand); } /// Sends a command. /// If no command acknowledge is requested, the command will be send asynchronously: it will be put on the top of the send queue /// If a command acknowledge is requested, the command will be send synchronously: the program will block until the acknowledge command /// has been received or the timeout has expired. /// Based on ClearQueueState, the send- and receive-queues are left intact or are cleared /// The command to sent. /// Property to optionally clear/wait the send queue /// Property to optionally clear/wait the send queue /// A received command. The received command will only be valid if the ReqAc of the command is true. public ReceivedCommand SendCommand(SendCommand sendCommand, SendQueue sendQueueState = SendQueue.InFrontQueue, ReceiveQueue receiveQueueState = ReceiveQueue.Default) { return SendCommand(sendCommand, sendQueueState, receiveQueueState, UseQueue.UseQueue); } /// Sends a command. /// If no command acknowledge is requested, the command will be send asynchronously: it will be put on the top of the send queue /// If a command acknowledge is requested, the command will be send synchronously: the program will block until the acknowledge command /// has been received or the timeout has expired. /// Based on ClearQueueState, the send- and receive-queues are left intact or are cleared /// The command to sent. /// Property to optionally clear/wait the send queue /// Property to optionally clear/wait the send queue /// Property to optionally bypass the queue /// A received command. The received command will only be valid if the ReqAc of the command is true. public ReceivedCommand SendCommand(SendCommand sendCommand, SendQueue sendQueueState, ReceiveQueue receiveQueueState, UseQueue useQueue) { var synchronizedSend = (sendCommand.ReqAc || useQueue == UseQueue.BypassQueue); // When waiting for an acknowledge, it is typically best to wait for the ReceiveQueue to be empty // This is thus the default state if (sendCommand.ReqAc && receiveQueueState == ReceiveQueue.Default) { receiveQueueState = ReceiveQueue.WaitForEmptyQueue; } if (sendQueueState == SendQueue.ClearQueue ) { // Clear receive queue _receiveCommandQueue.Clear(); } if (receiveQueueState == ReceiveQueue.ClearQueue ) { // Clear send queue _sendCommandQueue.Clear(); } // If synchronized sending, the only way to get command at end of queue is by waiting if (sendQueueState == SendQueue.WaitForEmptyQueue || (synchronizedSend && sendQueueState == SendQueue.AtEndQueue) ) { SpinWait.SpinUntil(() => _sendCommandQueue.IsEmpty); } if (receiveQueueState == ReceiveQueue.WaitForEmptyQueue) { SpinWait.SpinUntil(() => _receiveCommandQueue.IsEmpty); } if (synchronizedSend) { return SendCommandSync(sendCommand, sendQueueState); } if (sendQueueState != SendQueue.AtEndQueue) { // Put command at top of command queue _sendCommandQueue.SendCommand(sendCommand); } else { // Put command at bottom of command queue _sendCommandQueue.QueueCommand(sendCommand); } return new ReceivedCommand { CommunicationManager = _communicationManager }; } /// Synchronized send a command. /// The command to sent. /// Property to optionally clear/wait the send queue. /// . public ReceivedCommand SendCommandSync(SendCommand sendCommand, SendQueue sendQueueState) { // Directly call execute command var resultSendCommand = _communicationManager.ExecuteSendCommand(sendCommand, sendQueueState); InvokeNewLineEvent(NewLineSent, new CommandEventArgs(sendCommand)); return resultSendCommand; } /// Put the command at the back of the sent queue. /// The command to sent. public void QueueCommand(SendCommand sendCommand) { _sendCommandQueue.QueueCommand(sendCommand); } /// Put a command wrapped in a strategy at the back of the sent queue. /// The command strategy. public void QueueCommand(CommandStrategy commandStrategy) { _sendCommandQueue.QueueCommand(commandStrategy); } /// Adds a general command strategy to the receive queue. This will be executed on every enqueued and dequeued command. /// The general strategy for the receive queue. public void AddReceiveCommandStrategy(GeneralStrategy generalStrategy) { _receiveCommandQueue.AddGeneralStrategy(generalStrategy); } /// Adds a general command strategy to the send queue. This will be executed on every enqueued and dequeued command. /// The general strategy for the send queue. public void AddSendCommandStrategy(GeneralStrategy generalStrategy) { _sendCommandQueue.AddGeneralStrategy(generalStrategy); } /// Clears the receive queue. public void ClearReceiveQueue() { _receiveCommandQueue.Clear(); } /// Clears the send queue. public void ClearSendQueue() { _sendCommandQueue.Clear(); } /// Helper function to Invoke or directly call event. /// The event handler. /// private void InvokeNewLineEvent(EventHandler newLineHandler, CommandEventArgs newLineArgs) { if (newLineHandler == null || (ControlToInvokeOn != null && ControlToInvokeOn.IsDisposed)) return; if (ControlToInvokeOn != null) { //Asynchronously call on UI thread ControlToInvokeOn.BeginInvoke((MethodInvoker)(() => newLineHandler(this, newLineArgs))); } else { //Directly call newLineHandler(this, newLineArgs); } } /// Helper function to Invoke or directly call callback function. /// The messenger callback function. /// The command. private void InvokeCallBack(MessengerCallbackFunction messengerCallbackFunction, ReceivedCommand command) { if (messengerCallbackFunction == null || (ControlToInvokeOn != null && ControlToInvokeOn.IsDisposed)) return; if (ControlToInvokeOn != null) { //Asynchronously call on UI thread ControlToInvokeOn.BeginInvoke(new MessengerCallbackFunction(messengerCallbackFunction), (object)command); } else { //Directly call messengerCallbackFunction(command); } } protected virtual void Dispose(bool disposing) { if (disposing) { ControlToInvokeOn = null; _communicationManager.Dispose(); _sendCommandQueue.Dispose(); _receiveCommandQueue.Dispose(); } } } }