From 2f34f0b8c6b12df19027c2c2b7f7999fd5c5cb90 Mon Sep 17 00:00:00 2001 From: Cory Todd Date: Wed, 26 Feb 2020 07:40:41 -0700 Subject: [PATCH 01/12] Applied code cleanup Leverage better formatting Remove region blocks Use new language features where available --- .gitignore | 1 + Apex7000_BillValidator/ApexValidator.cs | 450 ++++++++---------- Apex7000_BillValidator/Commands.cs | 27 +- Apex7000_BillValidator/DataContracts.cs | 179 +++---- Apex7000_BillValidator/DebugBuffer.cs | 133 +++--- Apex7000_BillValidator/EventArgs.cs | 138 +++--- Apex7000_BillValidator/Events.cs | 108 ++--- Apex7000_BillValidator/Extensions.cs | 44 +- Apex7000_BillValidator/MasterCodex.cs | 79 +-- Apex7000_BillValidator/RS232Config.cs | 88 ++-- Apex7000_BillValidator/Serial/ICommPort.cs | 37 +- .../Serial/PortException.cs | 19 +- .../Serial/SerialPortFixer.cs | 123 ++--- Apex7000_BillValidator/Serial/StrongPort.cs | 271 +++++------ Apex7000_BillValidator/SlaveCodex.cs | 178 ++++--- 15 files changed, 936 insertions(+), 939 deletions(-) diff --git a/.gitignore b/.gitignore index 5794e03..cec3eaa 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ PyramidNETRS232_TestApp_Simple/obj/ *.nupkg PauseResumeCLI/obj/ PauseResumeCLI/bin/ +/.idea/ diff --git a/Apex7000_BillValidator/ApexValidator.cs b/Apex7000_BillValidator/ApexValidator.cs index 29a4408..12a0901 100644 --- a/Apex7000_BillValidator/ApexValidator.cs +++ b/Apex7000_BillValidator/ApexValidator.cs @@ -1,34 +1,46 @@ -using log4net; -using PTI.Serial; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; - -namespace PyramidNETRS232 +namespace PyramidNETRS232 { + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using System.Threading; + using log4net; + using PTI.Serial; + /// - /// The main class that does the actual "talking" the acceptor. In the context of documentation, - /// this object what is referred to as the master and the acceptor is the slave device. + /// The main class that does the actual "talking" the acceptor. In the context of documentation, + /// this object what is referred to as the master and the acceptor is the slave device. /// public partial class PyramidAcceptor : IDisposable { - - private readonly object mutex = new object(); - private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - #region Fields - private StrongPort port = null; - private bool resetRequested = false; + private readonly object _mutex = new object(); /// - /// Stores the prior enable/disable pattern, used with the Enable/Disable calls + /// Stores the prior enable/disable pattern, used with the Enable/Disable calls /// - private byte lastEnablePattern = 0; - #endregion + private byte _lastEnablePattern; + + + private StrongPort _port; + private bool _resetRequested; + + // Time at which escrow starts + private DateTime _escrowStart = DateTime.MinValue; + + /// + /// Creates a new PyramidAcceptor using the specified configuration + /// + /// Operating RS-232 parameters + public PyramidAcceptor(RS232Config config) + { + Config = config; + _lastEnablePattern = config.EnableMask; + } - #region Internal State // State variables for tracking between events and states private byte Ack { get; set; } @@ -48,53 +60,51 @@ public partial class PyramidAcceptor : IDisposable private EscrowCommands EscrowCommand { get; set; } // Used in case we need to retransmit - private byte[] previouslySentMasterMsg { get; set; } - - // Time at which escrow starts - private DateTime escrowStart = DateTime.MinValue; + private byte[] PreviouslySentMasterMsg { get; set; } /// - /// Slave's last state + /// Slave's last state /// public States PreviousState { get; private set; } /// - /// Slave's last events + /// Slave's last events /// public Events PreviousEvents { get; private set; } /// - /// Returns true if the communication thread is running normally + /// Returns true if the communication thread is running normally /// public bool IsRunning { get; private set; } /// - /// Gets or sets the RS232 Configuration + /// Gets or sets the RS232 Configuration /// public RS232Config Config { get; set; } /// - /// Gets the current pause state. If the acceptor - /// is running and at least 1 bill is enabled, the acceptor - /// is not paused. Otherwise, the acceptor is considered - /// paused. + /// Gets the current pause state. If the acceptor + /// is running and at least 1 bill is enabled, the acceptor + /// is not paused. Otherwise, the acceptor is considered + /// paused. /// public bool IsPaused { get; private set; } - #endregion + + #region IDisposable /// - /// Creates a new PyramidAcceptor using the specified configuration + /// Releases comm port and related managed resources. /// - /// Operating RS-232 parameters - public PyramidAcceptor(RS232Config config) + public void Dispose() { - this.Config = config; - lastEnablePattern = config.EnableMask; + _port?.Dispose(); } + #endregion + /// - /// Returns a list of all available ports + /// Returns a list of all available ports /// /// public static List GetAvailablePorts() @@ -104,76 +114,72 @@ public static List GetAvailablePorts() /// - /// Stop talking to the slave and release the underlying commm port. - /// Do not use this to disable the bill acceptor: use PauseAcceptance() + /// Stop talking to the slave and release the underlying comm port. + /// Do not use this to disable the bill acceptor: use PauseAcceptance() /// public void Close() { // This will kill the comms loop IsRunning = false; - if(port != null) - port.Disconnect(); + _port?.Disconnect(); - lock(mutex) + lock (_mutex) { IsPaused = true; } } /// - /// Disables the bill acceptor within the time period defined by the poll rate. - /// The poll rate (RS232Config.PollRate, default 50 ms) is the maximum time - /// between poll packets from master to slave. This command does not disconnect - /// the serial port. Use Close() for that effect. - /// - /// This effectively tells the acceptor to stop accepting bill but keep reporting status. - /// The acceptor's lights will turn off after this call takes effect. - /// + /// Disables the bill acceptor within the time period defined by the poll rate. + /// The poll rate (RS232Config.PollRate, default 50 ms) is the maximum time + /// between poll packets from master to slave. This command does not disconnect + /// the serial port. Use Close() for that effect. + /// This effectively tells the acceptor to stop accepting bill but keep reporting status. + /// The acceptor's lights will turn off after this call takes effect. + /// /// public void PauseAcceptance() { - lastEnablePattern = Config.EnableMask; + _lastEnablePattern = Config.EnableMask; Config.EnableMask = 0; - lock (mutex) + lock (_mutex) { IsPaused = true; } } /// - /// Returns the acceptor to bill accepting mode. This command - /// has no effect if the acceptor is already running and accepting. - /// The acceptor's lights will turn on after this command takes effect. - /// The command will take up to Config.PollRate ms to take effect. - /// + /// Returns the acceptor to bill accepting mode. This command + /// has no effect if the acceptor is already running and accepting. + /// The acceptor's lights will turn on after this command takes effect. + /// The command will take up to Config.PollRate ms to take effect. + /// /// + [Obsolete("Use ResumeAcceptance instead (spelled correctly)")] public void ResmeAcceptance() { - Config.EnableMask = lastEnablePattern; + Config.EnableMask = _lastEnablePattern; - lock (mutex) + lock (_mutex) { IsPaused = false; } - } + } /// - /// Connect to the device and begin speaking rs232 + /// Connect to the device and begin speaking rs232 /// public void Connect() { - - // Lock so we only have one connection attempt at a time. This protects // from client code behaving badly. - lock (mutex) + lock (_mutex) { - try { - port = new StrongPort(Config.CommPortName); + _port = new StrongPort(Config.CommPortName); } catch (IOException) { @@ -181,59 +187,50 @@ public void Connect() return; } - port.ReadTimeout = 500; + _port.ReadTimeout = 500; DebugBufferEntry.SetEpoch(); try { - port.Connect(); + _port.Connect(); // Only start if we connect without error - startRS232Loop(); + StartRS232Loop(); IsPaused = false; - } catch (Exception e) { - - log.ErrorFormat("Exception Connecting to acceptor: {0}", e.Message, e); + Log.ErrorFormat("Exception Connecting to acceptor: {0}", e.Message, e); if (OnError != null) { - NotifyError(Errors.PortError); - } - } - - - } + } } /// - /// Sets flag to reset the acceptor on the next message sent + /// Sets flag to reset the acceptor on the next message sent /// public void RequestReset() { - resetRequested = true; + _resetRequested = true; } - /// - /// Safely reconnect to the slave device + /// Safely reconnect to the slave device /// private void Reconnect() { - // Try to close the port before we re instantiate. If this // explodes there are bigger issues - port.Disconnect(); + _port.Disconnect(); // Let the port cool off (close base stream, etc.) Thread.Sleep(100); @@ -242,62 +239,49 @@ private void Reconnect() } - /// - /// Polls the slave and processes messages accordingly + /// Polls the slave and processes messages accordingly /// - private void startRS232Loop() + private void StartRS232Loop() { - if (IsRunning) { - log.Error("Already running RS-232 Comm loop... Exiting now..."); + Log.Error("Already running RS-232 Comm loop... Exiting now..."); return; } // Polls the slave using the interval defined in config.PollRate (milliseconds) - Thread speakThread = new Thread((fn) => + var speakThread = new Thread(fn => { - IsRunning = true; // Set toggle flag so we can kill this loop while (IsRunning) { - - if (resetRequested) - DoResetAcceptor(); + if (_resetRequested) + { + ResetAcceptor(); + } else - speakToSlave(); + { + ReadAcceptorResp(); + } Thread.Sleep(Config.PollRate); } + }) {IsBackground = true}; - }); - - speakThread.IsBackground = true; speakThread.Start(); } - - #region Implementation /// - /// The main parsing routine + /// The main parsing routine /// - private void speakToSlave() + private void ReadAcceptorResp() { - byte[] data; - if (previouslySentMasterMsg == null) - { - data = GenerateNormalMessage(); - } - else - { - data = previouslySentMasterMsg; + var data = PreviouslySentMasterMsg ?? GenerateNormalMessage(); - } - // Attempt to write data to slave NotifySerialData(DebugBufferEntry.AsMaster(data)); WriteWrapper(data); @@ -309,7 +293,7 @@ private void speakToSlave() // Extract only the states and events NotifySerialData(DebugBufferEntry.AsSlave(resp)); - + // No data was read, return!! if (resp.Length == 0) { @@ -318,26 +302,24 @@ private void speakToSlave() } // Check that we have the same ACK # - else if (IsBadAckNumber(resp, data)) + + if (IsBadAckNumber(resp, data)) { - previouslySentMasterMsg = data; + PreviouslySentMasterMsg = data; return; } - else if(IsBadSlaveChecksumOk(resp)) + if (IsBadSlaveChecksumOk(resp)) { - previouslySentMasterMsg = data; + PreviouslySentMasterMsg = data; return; } - // Otherwise we're all good - toggle ack and clear last sent message - else - { - previouslySentMasterMsg = null; - Ack ^= 1; - } - + + PreviouslySentMasterMsg = null; + Ack ^= 1; + // At this point we have sent our message and received a valid response. // Parse the response and act on any state or events that are reported. @@ -349,80 +331,77 @@ private void speakToSlave() // Raise a state changed notice for clients NotifyStateChange(PreviousState); - + // Multiple event may be reported at once var currentEvents = SlaveCodex.GetEvents(slaveMessage); foreach (Events e in Enum.GetValues(typeof(Events))) { // If flag is set in slave message, report event - if((currentEvents & e) == e) + if ((currentEvents & e) == e) + { NotifyEvent(e); + } } // Check for cassette missing - reports every time cashbox is missing if (!SlaveCodex.IsCashboxPresent(slaveMessage)) { - CashboxPresent = false; NotifyError(Errors.CashboxMissing); - } // Only report the cashbox attached 1 time after it is re-attached else if (!CashboxPresent) { - CashboxPresent = true; SafeEvent(OnCashboxAttached); - } // Mask away rest of message to see if a note is in escrow. If this is the first // escrow message, start the escrow timeout clock - if(!NoteIsEscrowed && PreviousState == States.Escrowed) + if (!NoteIsEscrowed && PreviousState == States.Escrowed) { - escrowStart = DateTime.MinValue; + _escrowStart = DateTime.MinValue; } - NoteIsEscrowed = (PreviousState == States.Escrowed); + + NoteIsEscrowed = PreviousState == States.Escrowed; // Credit bits are 3-5 of data byte 3 var value = SlaveCodex.GetCredit(slaveMessage); if (value > 0) { - Credit = (byte)value; - + Credit = (byte) value; } // Per the spec, credit message is issued by master after stack event is // sent by the slave. If the previous event was stacked or returned, we // must clear the escrow command to completely release the note from // escrow state. - switch(PreviousEvents) + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (PreviousEvents) { case Events.Stacked: NotifyCredit(Credit); // C# does not allow fallthrough so we will goto :) goto case Events.Returned; - - case Events.Returned: + + case Events.Returned: // Clear our the pending escrow command once we've stacked or returned the note EscrowCommand = EscrowCommands.None; Credit = 0; break; - } // Update the events aster the check for check so as to not lose a credit message PreviousEvents = currentEvents; - } - - #region GenerateMsg Write Read Checksum + + /// - /// Generate the next master message using our given state + /// Generate the next master message using our given state /// /// private byte[] GenerateNormalMessage() @@ -432,89 +411,91 @@ private byte[] GenerateNormalMessage() var data = Request.BaseMessage; // Toggle message number (ack #) if last message was okay and not a re-send request. - data[2] = (byte)(0x10 | Ack); + data[2] = (byte) (0x10 | Ack); // Get enable mask from client configuration. On next message, the acceptor // will update itself and not escrow any notes that are disabled in this mask. data[3] = Config.EnableMask; - if(!Config.IsEscrowMode) + if (!Config.IsEscrowMode) { - - // Clear escrow mode bit + // Clear escrow mode bit data[4] = 0x00; if (NoteIsEscrowed) + { data[4] |= 0x20; - - } + } + } else { - // Set escrow mode bit data[4] = 1 << 4; - if(NoteIsEscrowed) + if (!NoteIsEscrowed) { - // Note is in escrow, we have not yet notified the client, and we did not just stack - // a note. Due to the polling loop, we will always send an escrow message immediately - // after a stack because we are really reporting on state during the prior poll. - if (EscrowCommand == EscrowCommands.None) - { - EscrowCommand = EscrowCommands.Notify; - } + return Checksum(data); + } - // Otherwise do what the host tells us to do. - switch (EscrowCommand) - { - case EscrowCommands.Stack: - // set stack bit - data[4] |= 0x20; - EscrowCommand = EscrowCommands.Acknowledged; - break; - - case EscrowCommands.Reject: - // set reject bit - data[4] |= 0x40; - EscrowCommand = EscrowCommands.Acknowledged; - break; - - case EscrowCommands.Notify: - // notify client we have a bill in escrow - NotifyEscrow(Credit); - EscrowCommand = EscrowCommands.Awaiting; - break; - - case EscrowCommands.Awaiting: - // Perform timeout check and set reject flag is timeout exceeded if we are awaiting stack/return command - if (Config.EscrowTimeoutSeconds > 0) + // Note is in escrow, we have not yet notified the client, and we did not just stack + // a note. Due to the polling loop, we will always send an escrow message immediately + // after a stack because we are really reporting on state during the prior poll. + if (EscrowCommand == EscrowCommands.None) + { + EscrowCommand = EscrowCommands.Notify; + } + + // Otherwise do what the host tells us to do. + // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault + switch (EscrowCommand) + { + case EscrowCommands.Stack: + // set stack bit + data[4] |= 0x20; + EscrowCommand = EscrowCommands.Acknowledged; + break; + + case EscrowCommands.Reject: + // set reject bit + data[4] |= 0x40; + EscrowCommand = EscrowCommands.Acknowledged; + break; + + case EscrowCommands.Notify: + // notify client we have a bill in escrow + NotifyEscrow(Credit); + EscrowCommand = EscrowCommands.Awaiting; + break; + + case EscrowCommands.Awaiting: + // Perform timeout check and set reject flag is timeout exceeded if we are awaiting stack/return command + if (Config.EscrowTimeoutSeconds > 0) + { + if (_escrowStart == DateTime.MinValue) { - if (escrowStart == DateTime.MinValue) - { - escrowStart = DateTime.Now; - } - - var delta = DateTime.Now - escrowStart; - if (delta.TotalSeconds > Config.EscrowTimeoutSeconds) - { - EscrowCommand = EscrowCommands.Reject; - - escrowStart = DateTime.MinValue; - } + _escrowStart = DateTime.Now; } - break; - case EscrowCommands.Acknowledged: - EscrowCommand = EscrowCommands.None; - break; + var delta = DateTime.Now - _escrowStart; + if (delta.TotalSeconds > Config.EscrowTimeoutSeconds) + { + EscrowCommand = EscrowCommands.Reject; - case EscrowCommands.None: - // do nothing - break; - } - } + _escrowStart = DateTime.MinValue; + } + } + break; + + case EscrowCommands.Acknowledged: + EscrowCommand = EscrowCommands.None; + break; + + case EscrowCommands.None: + // do nothing + break; + } } @@ -523,15 +504,14 @@ private byte[] GenerateNormalMessage() } /// - /// Write data to port and notify client of any errors they should know about. + /// Write data to port and notify client of any errors they should know about. /// /// byte[] private void WriteWrapper(byte[] data) { try { - port.Write(data); - + _port.Write(data); } catch (PortException pe) { @@ -551,15 +531,14 @@ private void WriteWrapper(byte[] data) } /// - /// Read data from the port and notify client of any errors they should know about. + /// Read data from the port and notify client of any errors they should know about. /// /// private byte[] ReadWrapper() { try { - return port.Read(); - + return _port.Read(); } catch (PortException pe) { @@ -575,36 +554,35 @@ private byte[] ReadWrapper() return new byte[0]; } - } /// - /// Perform a reset on the acceptor. This will not generate a response but the unit - /// may go unresponsive for up to 3 seconds. + /// Perform a reset on the acceptor. This will not generate a response but the unit + /// may go unresponsive for up to 3 seconds. /// - private void DoResetAcceptor() + private void ResetAcceptor() { - byte[] reset = Request.ResetTarget; + var reset = Request.ResetTarget; // Toggle message number (ack #) if last message was okay and not a re-send request. - reset[2] = (byte)((Ack == 0) ? 0x60 : 0x61); + reset[2] = (byte) (Ack == 0 ? 0x60 : 0x61); reset = Checksum(reset); WriteWrapper(reset); // Toggle ACK number Ack ^= 1; - resetRequested = false; + _resetRequested = false; } /// - /// XOR checksum of only the data portion of the message + /// XOR checksum of only the data portion of the message /// /// /// - private byte[] Checksum(byte[] msg) + private static byte[] Checksum(IList msg) { - List tmp = new List(msg); - byte checksum = (byte)(msg[1] ^ msg[2]); - for (int i = 3; i < msg.Length - 1; i++) + var tmp = new List(msg); + var checksum = (byte) (msg[1] ^ msg[2]); + for (var i = 3; i < msg.Count - 1; i++) { checksum ^= msg[i]; } @@ -614,7 +592,7 @@ private byte[] Checksum(byte[] msg) } /// - /// Returns true if the ACK numbers for the given packets do not match + /// Returns true if the ACK numbers for the given packets do not match /// /// byte[] received /// byte[] last message sent @@ -626,32 +604,20 @@ private static bool IsBadAckNumber(byte[] resp, byte[] data) private static bool IsBadSlaveChecksumOk(byte[] msg) { - - // If the length is incorrent, the checksum doesn't matter + // If the length is incorrect, the checksum doesn't matter if (msg.Length != 11) + { return true; + } // msg should be the entire message, including checksum. -2 to skip checksum - byte checksum = (byte)(msg[1] ^ msg[2]); - for (int i = 3; i < msg.Length - 2; i++) + var checksum = (byte) (msg[1] ^ msg[2]); + for (var i = 3; i < msg.Length - 2; i++) { checksum ^= msg[i]; } return checksum != msg[10]; } - #endregion - #endregion - - #region IDisposable - /// - /// Releases comm port and related managed resources. - /// - public void Dispose() - { - if (port != null) - port.Dispose(); - } - #endregion } -} +} \ No newline at end of file diff --git a/Apex7000_BillValidator/Commands.cs b/Apex7000_BillValidator/Commands.cs index 84790a6..0d8cb24 100644 --- a/Apex7000_BillValidator/Commands.cs +++ b/Apex7000_BillValidator/Commands.cs @@ -1,17 +1,18 @@ - -namespace PyramidNETRS232 +namespace PyramidNETRS232 { public partial class PyramidAcceptor { /// - /// Issue a stack command to the acceptor. Note that the acceptor must - /// be configured to escrow mode and the bill must be in escrow. Otherwise, - /// calling this command has no effect other than logging an error message. + /// Issue a stack command to the acceptor. Note that the acceptor must + /// be configured to escrow mode and the bill must be in escrow. Otherwise, + /// calling this command has no effect other than logging an error message. /// public void Stack() { if (!Config.IsEscrowMode) - log.Error("Not in escrow mode, stack command ignored!"); + { + Log.Error("Not in escrow mode, stack command ignored!"); + } // Set flag to accept // We do not directly instruct the unit to stack as this may interfere @@ -20,19 +21,21 @@ public void Stack() } /// - /// Issue a reject command to the acceptor. Note that the acceptor must - /// be configured to escrow mode and the bill must be in escrow. Otherwise, - /// calling this command has no effect other than logging an error message. + /// Issue a reject command to the acceptor. Note that the acceptor must + /// be configured to escrow mode and the bill must be in escrow. Otherwise, + /// calling this command has no effect other than logging an error message. /// public void Reject() { if (!Config.IsEscrowMode) - log.Error("Not in escrow mode, reject command ignored!"); + { + Log.Error("Not in escrow mode, reject command ignored!"); + } // Set flag to reject // We do not directly instruct the unit to reject as this may interfere // with any pending transmissions. EscrowCommand = EscrowCommands.Reject; - } + } } -} +} \ No newline at end of file diff --git a/Apex7000_BillValidator/DataContracts.cs b/Apex7000_BillValidator/DataContracts.cs index 97afe7b..02182d3 100644 --- a/Apex7000_BillValidator/DataContracts.cs +++ b/Apex7000_BillValidator/DataContracts.cs @@ -1,222 +1,223 @@ - -namespace PyramidNETRS232 +namespace PyramidNETRS232 { + using System; + /// - /// The bill acceptor will pass through a series of "States" during bill processing. The acceptor will always - /// be in one single "State". If the acceptor is waiting for a bill insertion, it will report an "Idle" state - /// to the master. If the acceptor is reading a bill, it will report an "Accepting" state to the master. The change - /// from one state to another is called a transition. + /// The bill acceptor will pass through a series of "States" during bill processing. The acceptor will always + /// be in one single "State". If the acceptor is waiting for a bill insertion, it will report an "Idle" state + /// to the master. If the acceptor is reading a bill, it will report an "Accepting" state to the master. The change + /// from one state to another is called a transition. /// public enum States : byte { /// - /// No slave is currently attached to the master. + /// No slave is currently attached to the master. /// Offline, /// - /// Slave reports normal, idle activity. Ready to accept notes. + /// Slave reports normal, idle activity. Ready to accept notes. /// Idling, /// - /// Slave reports that it is currently pulling in a note. + /// Slave reports that it is currently pulling in a note. /// Accepting, /// - /// Slave reports that a note is in the escrow position. This position - /// is a physical location inside the acceptor that is too far for the - /// customer to pull back but not far enough to that it can't be returned - /// if necessary. This state is only report in Escrow Mode. - /// + /// Slave reports that a note is in the escrow position. This position + /// is a physical location inside the acceptor that is too far for the + /// customer to pull back but not far enough to that it can't be returned + /// if necessary. This state is only report in Escrow Mode. + /// /// Escrowed, /// - /// Slave reports that the note is in the process of being stacked. + /// Slave reports that the note is in the process of being stacked. /// Stacking, /// - /// Slave reports that the note is in the process of being returned. + /// Slave reports that the note is in the process of being returned. /// Returning, /// - /// Slave reports that there is a note jam in the feed path that is - /// was not able to clear after a reasonable amount of time. This jam - /// may be located in the cashbox or the main bottom plate. + /// Slave reports that there is a note jam in the feed path that is + /// was not able to clear after a reasonable amount of time. This jam + /// may be located in the cashbox or the main bottom plate. /// BillJammed, /// - /// Slave reports that the cashbox is full and unable to stack anymore notes. - /// When this state is reported, the acceptor is considered out of service. - /// The front LEDs will be off and no notes can be inserted until the cashbox - /// is emptied $$$. + /// Slave reports that the cashbox is full and unable to stack anymore notes. + /// When this state is reported, the acceptor is considered out of service. + /// The front LEDs will be off and no notes can be inserted until the cashbox + /// is emptied $$$. /// StackerFull, /// - /// Slave reports a failure that is not a jam or cashbox related issue. This - /// could include motor failure or EEPROM/Flash memory failure. + /// Slave reports a failure that is not a jam or cashbox related issue. This + /// could include motor failure or EEPROM/Flash memory failure. /// AcceptorFailure } /// - /// Slave acceptor may report a single-shot "Event" taking place. Multiple events - /// may be reported in a single message. Events are only reported one time and will - /// always accompany a state. If a message is retransmitted, the event will be reported - /// a second time but only becuase it was for a retransmission, not because the event - /// occured twice. If the slave has just stacked a bill in the cashbox, the slave will - /// report a "Stacked" event and since it is now waiting for another bill insertion, - /// it will also report an "Idle" state within the same message. + /// Slave acceptor may report a single-shot "Event" taking place. Multiple events + /// may be reported in a single message. Events are only reported one time and will + /// always accompany a state. If a message is retransmitted, the event will be reported + /// a second time but only becuase it was for a retransmission, not because the event + /// occured twice. If the slave has just stacked a bill in the cashbox, the slave will + /// report a "Stacked" event and since it is now waiting for another bill insertion, + /// it will also report an "Idle" state within the same message. /// - [System.Flags] + [Flags] public enum Events : byte { /// - /// No events to report + /// No events to report /// - None = 0, + None = 0, /// - /// Note has successfully been added to the cashbox + /// Note has successfully been added to the cashbox /// - Stacked = 1 << 0, + Stacked = 1 << 0, /// - /// Note has successfully been returned to the patron + /// Note has successfully been returned to the patron /// - Returned = 1 << 1, + Returned = 1 << 1, /// - /// Cheat attempt suspected. The Apex 7000 will return a note - /// to the patron if a cheat is suspected. + /// Cheat attempt suspected. The Apex 7000 will return a note + /// to the patron if a cheat is suspected. /// - Cheated = 1 << 2, + Cheated = 1 << 2, /// - /// Note was not recognized as valid OR was recognized as valid but - /// disabled note. + /// Note was not recognized as valid OR was recognized as valid but + /// disabled note. /// - BillRejected = 1 << 3, + BillRejected = 1 << 3, /// - /// The slave is powering up while this event is being reported. No - /// commands sent by the master to the slave will be acted upon until - /// the power up event is over. + /// The slave is powering up while this event is being reported. No + /// commands sent by the master to the slave will be acted upon until + /// the power up event is over. /// - PowerUp = 1 << 4, + PowerUp = 1 << 4, /// - /// Slave reports that the last message received from the master was invalid. + /// Slave reports that the last message received from the master was invalid. /// - InvalidCommand = 1 << 5 + InvalidCommand = 1 << 5 } /// - /// Errors reported by this library + /// Errors reported by this library /// - [System.Flags] + [Flags] public enum Errors : byte { /// - /// Default error type - nothing is wrong + /// Default error type - nothing is wrong /// - None = 0, + None = 0, /// - /// Timed out reading from slave + /// Timed out reading from slave /// - Timeout = 1 << 0, + Timeout = 1 << 0, /// - /// Error occured while writing to slave. Possible - /// break in serial connection. + /// Error occured while writing to slave. Possible + /// break in serial connection. /// - WriteError = 1 << 1, + WriteError = 1 << 1, /// - /// Unable to open, close, or write to port. May occur - /// if USB VCP is suddenly removed. + /// Unable to open, close, or write to port. May occur + /// if USB VCP is suddenly removed. /// - PortError = 1 << 2, + PortError = 1 << 2, /// - /// Cashbox is not detected by slave + /// Cashbox is not detected by slave /// - CashboxMissing = 1 << 3, + CashboxMissing = 1 << 3, /// - /// Message from slave has an incorrect checksum. If you see this - /// along with InvalidCommands, it is likely that the serial connection - /// is damaged. + /// Message from slave has an incorrect checksum. If you see this + /// along with InvalidCommands, it is likely that the serial connection + /// is damaged. /// - ChecksumError = 1 << 4, + ChecksumError = 1 << 4, /// - /// The last message received from the slave contains 1 or more invalid messages. - /// If you see this along with ChecksumErrors, it is likely that the serial connection - /// is damaged. + /// The last message received from the slave contains 1 or more invalid messages. + /// If you see this along with ChecksumErrors, it is likely that the serial connection + /// is damaged. /// - InvalidCommand = 1 << 5, + InvalidCommand = 1 << 5, /// - /// Usually occurs when the target port is already open. May also occur - /// on some virtual null modems. + /// Usually occurs when the target port is already open. May also occur + /// on some virtual null modems. /// FailedToOpenPort = 1 << 6 } /// - /// Issue these commands while in escrow mode. + /// Issue these commands while in escrow mode. /// internal enum EscrowCommands { /// - /// No commands are active or pending + /// No commands are active or pending /// None, /// - /// Issues the stack command during the next message loop + /// Issues the stack command during the next message loop /// Stack, /// - /// Issues the reject command during the next message loop + /// Issues the reject command during the next message loop /// Reject, /// - /// Escrow message needs to be sent to client + /// Escrow message needs to be sent to client /// Notify, /// - /// Escrow message has been sent to client, awaiting stack or return command + /// Escrow message has been sent to client, awaiting stack or return command /// Awaiting, /// - /// Client has acted on the escrow command, escrow events will be raised on next poll loop + /// Client has acted on the escrow command, escrow events will be raised on next poll loop /// Acknowledged } /// - /// Base message from which all packets are derived + /// Base message from which all packets are derived /// internal struct Request { - // basic message 0 1 2 3 4 5 6 7 - // start, len, ack, bills,escrow,resv'd,end, checksum - internal static readonly byte[] BaseMessage = { 0x02, 0x08, 0x60, 0x7F, 0x10, 0x00, 0x03 }; + // basic message 0 1 2 3 4 5 6 7 + // start, len, ack, bills,escrow,resv'd,end, checksum + internal static readonly byte[] BaseMessage = {0x02, 0x08, 0x60, 0x7F, 0x10, 0x00, 0x03}; - internal static readonly byte[] ResetTarget = { 0x02, 0x08, 0x61, 0x7f, 0x7f, 0x7f, 0x03 }; - } -} + internal static readonly byte[] ResetTarget = {0x02, 0x08, 0x61, 0x7f, 0x7f, 0x7f, 0x03}; + } +} \ No newline at end of file diff --git a/Apex7000_BillValidator/DebugBuffer.cs b/Apex7000_BillValidator/DebugBuffer.cs index 3833a3b..ad2ce29 100644 --- a/Apex7000_BillValidator/DebugBuffer.cs +++ b/Apex7000_BillValidator/DebugBuffer.cs @@ -1,130 +1,125 @@ -using System; -using System.Text; - -namespace PyramidNETRS232 +namespace PyramidNETRS232 { + using System; + using System.Text; + /// - /// Helper entry for describing serial communication transactions + /// Helper entry for describing serial communication transactions /// public class DebugBufferEntry { private static DateTime epoch; - /// - /// Sets the timestamp epoch. All timestamps are relative to this value. - /// - internal static void SetEpoch() + + private DebugBufferEntry(byte[] data, Flows flow) { - epoch = DateTime.Now; + var dt = DateTime.Now - epoch; + Timestamp = $"{dt.Hours}:{dt.Minutes}:{dt.Seconds}::{dt.Milliseconds}"; + + var now = DateTime.Now; + RealTime = $"{dt.Minutes}:{dt.Seconds}:{dt.Milliseconds}"; + Data = data; + Flow = flow; + + DecodedData = Flow == Flows.Master ? MasterCodex.ToMasterMessage(data).ToString() : SlaveCodex.ToSlaveMessage(data).ToString(); } /// - /// Creates a new entry and marks it as being sent master->slave + /// Byte[] data that was transmitted /// - /// byte[] - /// DebugBufferEntry - internal static DebugBufferEntry AsMaster(byte[] data) - { - return new DebugBufferEntry(data, Flows.Master); - } + public byte[] Data { get; } /// - /// Creates a new entry and marks it as being sent slave->master + /// Hex formatted byte[] data as 0xHH format /// - /// byte[] - /// DebugBufferEntry - internal static DebugBufferEntry AsSlave(byte[] data) - { - return new DebugBufferEntry(data, Flows.Slave); - } - - private DebugBufferEntry(byte[] data, Flows flow) - { - var dt = (DateTime.Now - epoch); - Timestamp = String.Format("{0}:{1}:{2}::{3}", dt.Hours, dt.Minutes, dt.Seconds, dt.Milliseconds); - - var now = DateTime.Now; - RealTime = String.Format("{0}:{1}:{2}", dt.Minutes, dt.Seconds, dt.Milliseconds); - Data = data; - Flow = flow; + public string PrintableData => ByteArrayToString(Data); - if (Flow == Flows.Master) - DecodedData = MasterCodex.ToMasterMessage(data).ToString(); - else - DecodedData = SlaveCodex.ToSlaveMessage(data).ToString(); - } + /// + /// Returns Master or Slave + /// + public Flows Flow { get; } /// - /// Byte[] data that was transmitted + /// Retrurns minutes:seconds:milliseconds timestamp relative to epoch /// - public byte[] Data { get; private set; } + public string Timestamp { get; } /// - /// Hex formatted byte[] data as 0xHH format + /// Returns the PC time the packet was collected /// - public string PrintableData - { - get - { - return ByteArrayToString(Data); - } - } + public string RealTime { get; } /// - /// Returns Master or Slave + /// byte[] decoded into known RS-232 messages /// - public Flows Flow { get; private set; } + public string DecodedData { get; } /// - /// Retrurns minutes:seconds:milliseconds timestamp relative to epoch + /// Sets the timestamp epoch. All timestamps are relative to this value. /// - public String Timestamp { get; private set; } + internal static void SetEpoch() + { + epoch = DateTime.Now; + } /// - /// Returns the PC time the packet was collected + /// Creates a new entry and marks it as being sent master->slave /// - public String RealTime { get; private set; } + /// byte[] + /// DebugBufferEntry + internal static DebugBufferEntry AsMaster(byte[] data) + { + return new DebugBufferEntry(data, Flows.Master); + } /// - /// byte[] decoded into known RS-232 messages + /// Creates a new entry and marks it as being sent slave->master /// - public String DecodedData { get; private set; } + /// byte[] + /// DebugBufferEntry + internal static DebugBufferEntry AsSlave(byte[] data) + { + return new DebugBufferEntry(data, Flows.Slave); + } /// - /// Returns Flow :: Data :: Timestamp + /// Returns Flow :: Data :: Timestamp /// /// public override string ToString() { - return String.Format("{0} :: {1} :: {2}", Flow, PrintableData, Timestamp); + return $"{Flow} :: {PrintableData} :: {Timestamp}"; } /// - /// Convert byte[] to a single-byte hex formatted string + /// Convert byte[] to a single-byte hex formatted string /// /// /// public static string ByteArrayToString(byte[] ba) { - StringBuilder hex = new StringBuilder(ba.Length * 2); - foreach (byte b in ba) + var hex = new StringBuilder(ba.Length * 2); + foreach (var b in ba) + { hex.AppendFormat("{0:X2} ", b); + } + return hex.ToString(); - } + } } /// - /// The origin of this debug entry + /// The origin of this debug entry /// - public enum Flows { - + public enum Flows + { /// - /// Sent by master + /// Sent by master /// Master, /// - /// Sent by slave + /// Sent by slave /// Slave } -} +} \ No newline at end of file diff --git a/Apex7000_BillValidator/EventArgs.cs b/Apex7000_BillValidator/EventArgs.cs index 7767dff..955c2a1 100644 --- a/Apex7000_BillValidator/EventArgs.cs +++ b/Apex7000_BillValidator/EventArgs.cs @@ -1,133 +1,149 @@ -using System; - -namespace PyramidNETRS232 +namespace PyramidNETRS232 { + using System; + /// - /// Properties of a state change event + /// Properties of a state change event /// public class StateChangedArgs : EventArgs { /// - /// A state changed argument describes the current, single state. + /// A state changed argument describes the current, single state. /// /// - public StateChangedArgs(States state) { State = state; } + public StateChangedArgs(States state) + { + State = state; + } /// - /// Most recently reported state of slave + /// Most recently reported state of slave /// - public States State { get; private set; } + public States State { get; } } /// - /// Properties of an event change event + /// Properties of an event change event /// public class EventChangedArgs : EventArgs { /// - /// An event change argument describes an event or multiple events - /// simulatenously. + /// An event change argument describes an event or multiple events + /// simultaneously. /// /// - public EventChangedArgs(Events e) { Event = e; } + public EventChangedArgs(Events e) + { + Event = e; + } /// - /// Most recently reported event or events + /// Most recently reported event or events /// - public Events Event { get; private set; } + public Events Event { get; } } /// - /// Properties of an error event + /// Properties of an error event /// public class ErrorArgs : EventArgs { /// - /// An error event argument decribes why an operation failed + /// An error event argument describes why an operation failed /// /// - public ErrorArgs(Errors e) { Error = e; } + public ErrorArgs(Errors e) + { + Error = e; + } /// - /// Error reported by RS-232 library + /// Error reported by RS-232 library /// - public Errors Error { get; private set; } + public Errors Error { get; } } /// - /// Properties of a credit event + /// Properties of a credit event /// public class CreditArgs : EventArgs { /// - /// A credit event argument describes a credit event. + /// A credit event argument describes a credit event. /// /// Index of note to credit - public CreditArgs(int index) { Index = index; } + public CreditArgs(int index) + { + Index = index; + } /// - /// Index of note for which credit should be issues. Index - /// is an integer between 1 and 7 inclusive. - /// Denomination mapping depends on the Apex 7000 firmware but - /// here is the USD map: - /// - /// 1: $1 - /// 2: $2 (not used) - /// 3: $5 - /// 4: $10 - /// 5: $20 - /// 6: $50 - /// 7: $100 + /// Index of note for which credit should be issues. Index + /// is an integer between 1 and 7 inclusive. + /// Denomination mapping depends on the Apex 7000 firmware but + /// here is the USD map: + /// 1: $1 + /// 2: $2 (not used) + /// 3: $5 + /// 4: $10 + /// 5: $20 + /// 6: $50 + /// 7: $100 /// - public int Index { get; private set; } + public int Index { get; } } /// - /// Properties of an escrow event - /// + /// Properties of an escrow event + /// /// public class EscrowArgs : EventArgs { /// - /// Escrow event args describe an escrow event. + /// Escrow event args describe an escrow event. /// - /// + /// /// Index of note in escrow - public EscrowArgs(int index) { Index = index; } + public EscrowArgs(int index) + { + Index = index; + } /// - /// Index of note that is in escrow position. Index - /// is an integer between 1 and 7 inclusive. - /// Denomination mapping depends on the Apex 7000 firmware but - /// here is the USD map: - /// - /// 1: $1 - /// 2: $2 (not used) - /// 3: $5 - /// 4: $10 - /// 5: $20 - /// 6: $50 - /// 7: $100 - /// + /// Index of note that is in escrow position. Index + /// is an integer between 1 and 7 inclusive. + /// Denomination mapping depends on the Apex 7000 firmware but + /// here is the USD map: + /// 1: $1 + /// 2: $2 (not used) + /// 3: $5 + /// 4: $10 + /// 5: $20 + /// 6: $50 + /// 7: $100 + /// /// - public int Index { get; private set; } + public int Index { get; } } /// - /// Properties of a debug entry event + /// Properties of a debug entry event /// public class DebugEntryArgs : EventArgs { /// - /// DebugEntry argument describes a debug event + /// DebugEntry argument describes a debug event /// /// DebugBufferEntry describing serial data tx/rx - public DebugEntryArgs(DebugBufferEntry entry) { Entry = entry; } + public DebugEntryArgs(DebugBufferEntry entry) + { + Entry = entry; + } /// - /// Describes data being debugged. Includes raw byte[] along with - /// a tanslation and some timing information. + /// Describes data being debugged. Includes raw byte[] along with + /// a translation and some timing information. /// - public DebugBufferEntry Entry { get; private set; } + public DebugBufferEntry Entry { get; } } -} +} \ No newline at end of file diff --git a/Apex7000_BillValidator/Events.cs b/Apex7000_BillValidator/Events.cs index 96b7570..07f02e7 100644 --- a/Apex7000_BillValidator/Events.cs +++ b/Apex7000_BillValidator/Events.cs @@ -1,145 +1,121 @@ -using System; - -namespace PyramidNETRS232 +namespace PyramidNETRS232 { + using System; + public partial class PyramidAcceptor { /// - /// Raised when the acceptor reports any event. Events are transient - /// in that they are only reported once to the master. + /// Raised when the acceptor reports any event. Events are transient + /// in that they are only reported once to the master. /// public event EventHandler OnEvent; /// - /// Notify subscribers of event(s). Events may be one or more Events. + /// Notify subscribers of event(s). Events may be one or more Events. /// /// internal virtual void NotifyEvent(Events events) { - EventHandler handler = OnEvent; - if (handler != null) - { - handler(this, new EventChangedArgs(events)); - } + var handler = OnEvent; + handler?.Invoke(this, new EventChangedArgs(events)); } /// - /// Raised when the acceptor reports a state that is different from the - /// previously recorded state. Note: In escrow mode the Escrowed event - /// will be raised as + /// Raised when the acceptor reports a state that is different from the + /// previously recorded state. Note: In escrow mode the Escrowed event + /// will be raised as /// public event EventHandler OnStateChanged; /// - /// Notify subsribers of state. State may be a transition or the current - /// state, repeated. + /// Notify subsribers of state. State may be a transition or the current + /// state, repeated. /// /// internal virtual void NotifyStateChange(States state) { - EventHandler handler = OnStateChanged; - if (handler != null) - { - handler(this, new StateChangedArgs(state)); - } + var handler = OnStateChanged; + handler?.Invoke(this, new StateChangedArgs(state)); } /// - /// Raised by the master in the event that communication fails + /// Raised by the master in the event that communication fails /// public event EventHandler OnError; /// - /// Report errors to the subsriber. + /// Report errors to the subscriber. /// /// internal virtual void NotifyError(Errors errors) { - EventHandler handler = OnError; - if (handler != null) - { - handler(this, new ErrorArgs(errors)); - } + var handler = OnError; + handler?.Invoke(this, new ErrorArgs(errors)); } /// - /// Raised once a note has been successfully stacked. + /// Raised once a note has been successfully stacked. /// public event EventHandler OnCredit; /// - /// Notify subscriber that credit should be issue. + /// Notify subscriber that credit should be issue. /// /// Integer value 1-7 indicating which bill should be credited internal virtual void NotifyCredit(int index) { - EventHandler handler = OnCredit; - if (handler != null) - { - handler(this, new CreditArgs(index)); - } + var handler = OnCredit; + handler?.Invoke(this, new CreditArgs(index)); } /// - /// subscribe to this event to be notified of when and what denomination is in escrow. - /// If you are running in escrow mode, you may then decide whether to stack or reject - /// the note based upon the denomination. + /// subscribe to this event to be notified of when and what denomination is in escrow. + /// If you are running in escrow mode, you may then decide whether to stack or reject + /// the note based upon the denomination. /// public event EventHandler OnEscrow; /// - /// Notify subsriber that a note is in escrow. + /// Notify subscriber that a note is in escrow. /// - /// + /// /// Integer value 1-7 indicating which bill is in escrow internal virtual void NotifyEscrow(int index) { - EventHandler handler = OnEscrow; - if (handler != null) - { - handler(this, new EscrowArgs(index)); - } + var handler = OnEscrow; + handler?.Invoke(this, new EscrowArgs(index)); } /// - /// Raised when the cashbox is no longer detached. This only be reported if the cashbox is first attached, - /// then missing. + /// Raised when the cashbox is no longer detached. This only be reported if the cashbox is first attached, + /// then missing. /// public event EventHandler OnCashboxAttached; - + /// - /// Subscribe to serial data received and transmission events. Useful for debugging. + /// Subscribe to serial data received and transmission events. Useful for debugging. /// public event EventHandler OnSerialData; /// - /// Notify subsriber that data is available for debugging + /// Notify subsriber that data is available for debugging /// /// internal virtual void NotifySerialData(DebugBufferEntry entry) { - EventHandler handler = OnSerialData; - if (handler != null) - { - handler(this, new DebugEntryArgs(entry)); - } + var handler = OnSerialData; + handler?.Invoke(this, new DebugEntryArgs(entry)); } - - #region Private /// - /// Safely handle event. If handler is null, event is ignored. + /// Safely handle event. If handler is null, event is ignored. /// /// Instance of EventHandler to exectute private void SafeEvent(EventHandler eventInst) { - EventHandler exec = eventInst; - if (exec != null) - { - exec(this, null); - } - } - #endregion + var exec = eventInst; + exec?.Invoke(this, null); + } } -} +} \ No newline at end of file diff --git a/Apex7000_BillValidator/Extensions.cs b/Apex7000_BillValidator/Extensions.cs index 30641a4..0189407 100644 --- a/Apex7000_BillValidator/Extensions.cs +++ b/Apex7000_BillValidator/Extensions.cs @@ -1,24 +1,24 @@ -using System; - -namespace PyramidNETRS232 +namespace PyramidNETRS232 { + using System; + /// - /// Add-ons for what is missing from 3.5 .NET + /// Add-ons for what is missing from 3.5 .NET /// internal static class Extensions { /// - /// Returns true if the provided enum valus has the specified flag set. + /// Returns true if the provided enum values has the specified flag set. /// /// /// /// /// - public static bool Has(this System.Enum type, T value) + public static bool Has(this Enum type, T value) { try { - return (((int)(object)type & (int)(object)value) == (int)(object)value); + return ((int) (object) type & (int) (object) value) == (int) (object) value; } catch { @@ -27,17 +27,17 @@ public static bool Has(this System.Enum type, T value) } /// - /// Returns true if the specified enum is equal to the specified value + /// Returns true if the specified enum is equal to the specified value /// /// /// /// /// - public static bool Is(this System.Enum type, T value) + public static bool Is(this Enum type, T value) { try { - return (int)(object)type == (int)(object)value; + return (int) (object) type == (int) (object) value; } catch { @@ -46,49 +46,43 @@ public static bool Is(this System.Enum type, T value) } /// - /// Sets the specified flag on this enum + /// Sets the specified flag on this enum /// /// /// /// /// - public static T Add(this System.Enum type, T value) + public static T Add(this Enum type, T value) { try { - return (T)(object)(((int)(object)type | (int)(object)value)); + return (T) (object) ((int) (object) type | (int) (object) value); } catch (Exception ex) { throw new ArgumentException( - string.Format( - "Could not append value from enumerated type '{0}'.", - typeof(T).Name - ), ex); + $"Could not append value from enumerated type '{typeof(T).Name}'.", ex); } } /// - /// Clears the specified flag on this enum + /// Clears the specified flag on this enum /// /// /// /// /// - public static T Remove(this System.Enum type, T value) + public static T Remove(this Enum type, T value) { try { - return (T)(object)(((int)(object)type & ~(int)(object)value)); + return (T) (object) ((int) (object) type & ~(int) (object) value); } catch (Exception ex) { throw new ArgumentException( - string.Format( - "Could not remove value from enumerated type '{0}'.", - typeof(T).Name - ), ex); + $"Could not remove value from enumerated type '{typeof(T).Name}'.", ex); } } } -} +} \ No newline at end of file diff --git a/Apex7000_BillValidator/MasterCodex.cs b/Apex7000_BillValidator/MasterCodex.cs index e99a273..84e4d62 100644 --- a/Apex7000_BillValidator/MasterCodex.cs +++ b/Apex7000_BillValidator/MasterCodex.cs @@ -1,57 +1,58 @@ - -namespace PyramidNETRS232 +namespace PyramidNETRS232 { + using System; + /// - /// \internal + /// \internal /// internal class MasterCodex { // Mask out reserved and ignored bits - private static MasterMessage relevanceMask = (MasterMessage)0xFE7F; + private const MasterMessage RelevanceMask = (MasterMessage) 0xFE7F; + + internal static MasterMessage ToMasterMessage(byte[] message) + { + if (message.Length != 8) + { + return MasterMessage.InvalidCommand; + } + + var combined = (message[4] << 8) | + message[3]; + + var result = (MasterMessage) combined & RelevanceMask; + return result; + } - [System.Flags] - internal enum MasterMessage : int + [Flags] + internal enum MasterMessage { // Byte0 - bit 0 - En1 = 1 << 0, - En2 = 1 << 1, - En3 = 1 << 2, - En4 = 1 << 3, - En5 = 1 << 4, - En6 = 1 << 5, - En7 = 1 << 6, - + En1 = 1 << 0, + En2 = 1 << 1, + En3 = 1 << 2, + En4 = 1 << 3, + En5 = 1 << 4, + En6 = 1 << 5, + En7 = 1 << 6, + // Ignore 8th bit - x1 = 1 << 7, + x1 = 1 << 7, // Byte 1 - bit 0 - Reserved0 = 1 << 8, // Set to 0 - Security = 1 << 9, // Set to 0 - Orientation1 = 1 << 10, // Set to 0 - Orientation2 = 1 << 11, // Set to 0 - Escrow = 1 << 12, // Set to 1 to enable - Stack = 1 << 13, // In Escrow mode, set to 1 to stack - Return = 1 << 14, // In Escrow mode, set to 1 to return + Reserved0 = 1 << 8, // Set to 0 + Security = 1 << 9, // Set to 0 + Orientation1 = 1 << 10, // Set to 0 + Orientation2 = 1 << 11, // Set to 0 + Escrow = 1 << 12, // Set to 1 to enable + Stack = 1 << 13, // In Escrow mode, set to 1 to stack + Return = 1 << 14, // In Escrow mode, set to 1 to return // Ignore 8th bit - x2 = 1 << 15, + x2 = 1 << 15, // Not part of spec, just added for decoding - InvalidCommand = 1 << 16 - } - - internal static MasterMessage ToMasterMessage(byte[] message) - { - if (message.Length != 8) - return MasterMessage.InvalidCommand; - - int combined = ( - (message[4] << 8) | - (message[3]) - ); - - var result = (MasterMessage)(combined) & relevanceMask; - return result; + InvalidCommand = 1 << 16 } } -} +} \ No newline at end of file diff --git a/Apex7000_BillValidator/RS232Config.cs b/Apex7000_BillValidator/RS232Config.cs index a1ccc3c..a26db6e 100644 --- a/Apex7000_BillValidator/RS232Config.cs +++ b/Apex7000_BillValidator/RS232Config.cs @@ -1,102 +1,98 @@ -using System; - -namespace PyramidNETRS232 +namespace PyramidNETRS232 { + using System; + /// - /// Define the operating parameters of your bill acceptor + /// Define the operating parameters of your bill acceptor /// public class RS232Config { - // default pollrate in ms - private static readonly int POLL_RATE = 100; + private const int DefaultPollRate = 100; + private byte _enableMask = 0x7F; + - #region Fields // Integer poll rate between 50 and 5000 ms - private int pollRate = POLL_RATE; - private byte enableMask = 0x7F; - #endregion + private int _pollRate = DefaultPollRate; + /// - /// Create a new configuration to use for the Apex7000 validator class. Defaults - /// to non-escrow mode. + /// Create a new configuration to use for the Apex7000 validator class. Defaults + /// to non-escrow mode. /// - /// + /// /// String port name e.g. COM4 - public RS232Config(string commPort) : this(commPort.ToString(), false) - { } + public RS232Config(string commPort) : this(commPort, false) + { + } /// - /// Create a new configuration to use for the Apex7000 validator class. + /// Create a new configuration to use for the Apex7000 validator class. /// - /// + /// /// String port name e.g. COM4 /// bool true to enable escrow mode public RS232Config(string commPort, bool isEscrowMode) { - this.CommPortName = commPort; - this.IsEscrowMode = isEscrowMode; + CommPortName = commPort; + IsEscrowMode = isEscrowMode; } - #region Properties + /// - /// Gets or sets the poll rate in milliseconds. The polled system is designed for the master to request - /// information from the slave at a periodic rate. The rate can be as slow as 5 seconds or as fast as - /// 50 msec between each poll. The popular rate is fast since the overall system performance - /// (bills per minute accepted) will be slower at slower polling rates. While feeding the bill into the - /// acceptor, the acceptor will miss a few polls, because it is reading the bill and not servicing the - /// serial interface (Typical for acceptors using this protocol). + /// Gets or sets the poll rate in milliseconds. The polled system is designed for the master to request + /// information from the slave at a periodic rate. The rate can be as slow as 5 seconds or as fast as + /// 50 msec between each poll. The popular rate is fast since the overall system performance + /// (bills per minute accepted) will be slower at slower polling rates. While feeding the bill into the + /// acceptor, the acceptor will miss a few polls, because it is reading the bill and not servicing the + /// serial interface (Typical for acceptors using this protocol). /// /// Default value is 100 ms /// Min: 50 Max: 5000 public int PollRate { - get - { - return pollRate; - } + get => _pollRate; set { // Allow floor of 50 ms, celing of 5 seconds if (value < 50 || value > 5000) { - throw new ArgumentOutOfRangeException("Minimum value is 50ms, maximum value is 5000 ms"); + throw new ArgumentOutOfRangeException(nameof(PollRate), "Minimum value is 50ms, maximum value is 5000 ms"); } - pollRate = value; + _pollRate = value; } } /// - /// String name of the comm port (What the OS calls it) + /// String name of the comm port (What the OS calls it) /// - public string CommPortName { get; private set; } + public string CommPortName { get; } /// - /// Escrow mode allows you to manually call Stack() or Reject() on each - /// escrowed note. If false, we stack any valid note automatically. + /// Escrow mode allows you to manually call Stack() or Reject() on each + /// escrowed note. If false, we stack any valid note automatically. /// /// Default value is false public bool IsEscrowMode { get; set; } /// - /// Gets or sets the timeout for escrow mode. By default, we wait indefinately but - /// you may configure this to a non-zero value to enable escrow timeouts. This has the effect - /// of sending a reject message to the acceptor once timeout occurs. + /// Gets or sets the timeout for escrow mode. By default, we wait indefinately but + /// you may configure this to a non-zero value to enable escrow timeouts. This has the effect + /// of sending a reject message to the acceptor once timeout occurs. /// /// Default value is 0 (disabled) public int EscrowTimeoutSeconds { get; set; } /// - /// Bitwise enable disbale pattern. Each bit set to 1 corresponds to an enabled bill. - /// e.g. 0x7E (0b01111110) is all bill except the $1 are enabled. This value is limited - /// to 7-bits (0x7F) and any extra bits will be unset. 0xFF -> 0x7F + /// Bitwise enable disbale pattern. Each bit set to 1 corresponds to an enabled bill. + /// e.g. 0x7E (0b01111110) is all bill except the $1 are enabled. This value is limited + /// to 7-bits (0x7F) and any extra bits will be unset. 0xFF -> 0x7F /// /// Default mask is 0x7F (all enabled) public byte EnableMask { - get { return enableMask; } - set { enableMask = (byte)(0x7F & value); } + get => _enableMask; + set => _enableMask = (byte) (0x7F & value); } - #endregion } -} +} \ No newline at end of file diff --git a/Apex7000_BillValidator/Serial/ICommPort.cs b/Apex7000_BillValidator/Serial/ICommPort.cs index e65c190..b2a030d 100644 --- a/Apex7000_BillValidator/Serial/ICommPort.cs +++ b/Apex7000_BillValidator/Serial/ICommPort.cs @@ -8,61 +8,56 @@ // Cory Todd // cory@pyramidacceptors.com -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - namespace PTI.Serial { + using System; + /// - /// Defines the contract new implementations of a serial port must adhere to. + /// Defines the contract new implementations of a serial port must adhere to. /// public interface ICommPort : IDisposable { - /// - /// Return true if the underlying serial port is open + /// Return true if the underlying serial port is open /// bool IsOpen { get; } /// - /// Return the OS name of the underlying port + /// Return the OS name of the underlying port /// string Name { get; set; } /// - /// Gets or Sets the timeout in milliseconds for a read operation. + /// Gets or Sets the timeout in milliseconds for a read operation. /// int ReadTimeout { get; set; } /// - /// Attempts to open the underlying serial port using the currently - /// configured state. Returns true if port is successfully opened. + /// Attempts to open the underlying serial port using the currently + /// configured state. Returns true if port is successfully opened. /// /// bool bool Connect(); /// - /// Attempts to safely close the underling serial port and base stream. - /// If the close operation completes without issue, bool true is returned. + /// Attempts to safely close the underling serial port and base stream. + /// If the close operation completes without issue, bool true is returned. /// /// bool bool Disconnect(); /// - /// Writes the byte[] data to this port. If the port is not open, misconfigured, - /// or if there is a physical connection issue, exceptions may arise. + /// Writes the byte[] data to this port. If the port is not open, misconfigured, + /// or if there is a physical connection issue, exceptions may arise. /// /// byte[] void Write(byte[] data); /// - /// Reads all available data from this port. If no data is received withint - /// ReadTimeout milliseconds, a timeout exception will be raised. + /// Reads all available data from this port. If no data is received withint + /// ReadTimeout milliseconds, a timeout exception will be raised. /// /// byte[] - byte[] Read(); + byte[] Read(); } - -} +} \ No newline at end of file diff --git a/Apex7000_BillValidator/Serial/PortException.cs b/Apex7000_BillValidator/Serial/PortException.cs index 8c49463..66f94d2 100644 --- a/Apex7000_BillValidator/Serial/PortException.cs +++ b/Apex7000_BillValidator/Serial/PortException.cs @@ -1,8 +1,8 @@ -using System; -using System.Runtime.Serialization; - -namespace PTI.Serial +namespace PTI.Serial { + using System; + using System.Runtime.Serialization; + /// \internal internal enum ExceptionTypes { @@ -16,10 +16,7 @@ internal enum ExceptionTypes [SerializableAttribute] internal class PortException : Exception, ISerializable { - public ExceptionTypes ErrorType { get; private set; } - public PortException(ExceptionTypes type) - : base() { ErrorType = type; } @@ -38,6 +35,10 @@ public PortException(ExceptionTypes type, string message, Exception inner) // This constructor is needed for serialization. protected PortException(SerializationInfo info, StreamingContext context) - : base(info, context) { } + : base(info, context) + { + } + + public ExceptionTypes ErrorType { get; private set; } } -} +} \ No newline at end of file diff --git a/Apex7000_BillValidator/Serial/SerialPortFixer.cs b/Apex7000_BillValidator/Serial/SerialPortFixer.cs index d4e9741..7a821e8 100644 --- a/Apex7000_BillValidator/Serial/SerialPortFixer.cs +++ b/Apex7000_BillValidator/Serial/SerialPortFixer.cs @@ -12,26 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.IO; -using System.IO.Ports; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.Win32.SafeHandles; -using System.Security.Permissions; - namespace PTI.Serial { + using System; + using System.IO; + using System.Runtime.InteropServices; + using System.Security.Permissions; + using System.Text; + using Microsoft.Win32.SafeHandles; + /// \internal internal class SerialPortFixer : IDisposable { - [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)] - internal static void Execute(string portName) - { - using (new SerialPortFixer(portName)) - { - } - } #region IDisposable Members public void Dispose() @@ -45,6 +37,38 @@ public void Dispose() #endregion + [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)] + internal static void Execute(string portName) + { + using (new SerialPortFixer(portName)) + { + } + } + + internal static class NativeMethods + { + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int FormatMessage(int dwFlags, HandleRef lpSource, int dwMessageId, int dwLanguageId, + StringBuilder lpBuffer, int nSize, IntPtr arguments); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern bool GetCommState(SafeFileHandle hFile, ref Dcb lpDcb); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern bool SetCommState(SafeFileHandle hFile, ref Dcb lpDcb); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern bool ClearCommError(SafeFileHandle hFile, ref int lpErrors, ref Comstat lpStat); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, + IntPtr securityAttrs, int dwCreationDisposition, + int dwFlagsAndAttributes, IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern int GetFileType(SafeFileHandle hFile); + } + #region Implementation private const int DcbFlagAbortOnError = 14; @@ -54,25 +78,28 @@ public void Dispose() private SerialPortFixer(string portName) { const int dwFlagsAndAttributes = 0x40000000; - const int dwAccess = unchecked((int)0xC0000000); + const int dwAccess = unchecked((int) 0xC0000000); if (string.IsNullOrEmpty(portName)) { throw new ArgumentException("Invalid Serial Port", "portName"); } - SafeFileHandle hFile = NativeMethods.CreateFile(@"\\.\" + portName, dwAccess, 0, IntPtr.Zero, 3, dwFlagsAndAttributes, - IntPtr.Zero); + + var hFile = NativeMethods.CreateFile(@"\\.\" + portName, dwAccess, 0, IntPtr.Zero, 3, dwFlagsAndAttributes, + IntPtr.Zero); if (hFile.IsInvalid) { WinIoError(); } + try { - int fileType = NativeMethods.GetFileType(hFile); - if ((fileType != 2) && (fileType != 0)) + var fileType = NativeMethods.GetFileType(hFile); + if (fileType != 2 && fileType != 0) { throw new ArgumentException("Invalid Serial Port", "portName"); } + m_Handle = hFile; InitializeDcb(); } @@ -86,7 +113,7 @@ private SerialPortFixer(string portName) private void InitializeDcb() { - Dcb dcb = new Dcb(); + var dcb = new Dcb(); GetCommStateNative(ref dcb); dcb.Flags &= ~(1u << DcbFlagAbortOnError); SetCommStateNative(ref dcb); @@ -94,13 +121,15 @@ private void InitializeDcb() private static string GetMessage(int errorCode) { - StringBuilder lpBuffer = new StringBuilder(0x200); + var lpBuffer = new StringBuilder(0x200); if ( - NativeMethods.FormatMessage(0x3200, new HandleRef(null, IntPtr.Zero), errorCode, 0, lpBuffer, lpBuffer.Capacity, - IntPtr.Zero) != 0) + NativeMethods.FormatMessage(0x3200, new HandleRef(null, IntPtr.Zero), errorCode, 0, lpBuffer, + lpBuffer.Capacity, + IntPtr.Zero) != 0) { return lpBuffer.ToString(); } + return "Unknown Error"; } @@ -108,9 +137,9 @@ private static int MakeHrFromErrorCode(int errorCode) { try { - return (int)(0x80070000 | (uint)errorCode); + return (int) (0x80070000 | (uint) errorCode); } - catch(OverflowException) + catch (OverflowException) { // In some unusual cases, the above can overflow. Catch this simply return -1 return -1; @@ -119,25 +148,27 @@ private static int MakeHrFromErrorCode(int errorCode) private static void WinIoError() { - int errorCode = Marshal.GetLastWin32Error(); + var errorCode = Marshal.GetLastWin32Error(); throw new IOException(GetMessage(errorCode), MakeHrFromErrorCode(errorCode)); } private void GetCommStateNative(ref Dcb lpDcb) { - int commErrors = 0; - Comstat comStat = new Comstat(); + var commErrors = 0; + var comStat = new Comstat(); - for (int i = 0; i < CommStateRetries; i++) + for (var i = 0; i < CommStateRetries; i++) { if (!NativeMethods.ClearCommError(m_Handle, ref commErrors, ref comStat)) { WinIoError(); } + if (NativeMethods.GetCommState(m_Handle, ref lpDcb)) { break; } + if (i == CommStateRetries - 1) { WinIoError(); @@ -147,19 +178,21 @@ private void GetCommStateNative(ref Dcb lpDcb) private void SetCommStateNative(ref Dcb lpDcb) { - int commErrors = 0; - Comstat comStat = new Comstat(); + var commErrors = 0; + var comStat = new Comstat(); - for (int i = 0; i < CommStateRetries; i++) + for (var i = 0; i < CommStateRetries; i++) { if (!NativeMethods.ClearCommError(m_Handle, ref commErrors, ref comStat)) { WinIoError(); } + if (NativeMethods.SetCommState(m_Handle, ref lpDcb)) { break; } + if (i == CommStateRetries - 1) { WinIoError(); @@ -204,29 +237,5 @@ internal struct Dcb #endregion #endregion - - internal static class NativeMethods - { - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern int FormatMessage(int dwFlags, HandleRef lpSource, int dwMessageId, int dwLanguageId, - StringBuilder lpBuffer, int nSize, IntPtr arguments); - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - internal static extern bool GetCommState(SafeFileHandle hFile, ref Dcb lpDcb); - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - internal static extern bool SetCommState(SafeFileHandle hFile, ref Dcb lpDcb); - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - internal static extern bool ClearCommError(SafeFileHandle hFile, ref int lpErrors, ref Comstat lpStat); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, - IntPtr securityAttrs, int dwCreationDisposition, - int dwFlagsAndAttributes, IntPtr hTemplateFile); - - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern int GetFileType(SafeFileHandle hFile); - } } } \ No newline at end of file diff --git a/Apex7000_BillValidator/Serial/StrongPort.cs b/Apex7000_BillValidator/Serial/StrongPort.cs index ae99651..a8add3c 100644 --- a/Apex7000_BillValidator/Serial/StrongPort.cs +++ b/Apex7000_BillValidator/Serial/StrongPort.cs @@ -1,74 +1,75 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Ports; -using System.Reflection; -using System.Threading; -using System.Linq; -using log4net; -using System.Security.Permissions; - -namespace PTI.Serial +namespace PTI.Serial { + using System; + using System.Collections.Generic; + using System.IO; + using System.IO.Ports; + using System.Linq; + using System.Reflection; + using System.Security.Permissions; + using System.Text; + using System.Threading; + using log4net; // Wrapper around SerialPort // \internal internal class StrongPort : ICommPort { - private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); - - // My prefered encoding when debugging byte buffers - private readonly System.Text.Encoding W1252 = System.Text.Encoding.GetEncoding("Windows-1252"); + private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); // Lock on reading so we don't disconnect while waiting for data. - private static object readLock = new object(); + private static readonly object ReadLock = new object(); + + // My prefered encoding when debugging byte buffers + private readonly Encoding _w1252 = Encoding.GetEncoding("Windows-1252"); /// - /// Creates a new strong port by correctly configuring the DCB blocks used to configured - /// the comm port in the Win32 API. As such, this call requires unrestricted access to the system - /// e.g. run as admin. If you do not run this application as admin, this call will fail with a - /// security excetion + /// Creates a new strong port by correctly configuring the DCB blocks used to configured + /// the comm port in the Win32 API. As such, this call requires unrestricted access to the system + /// e.g. run as admin. If you do not run this application as admin, this call will fail with a + /// security excetion /// /// OS name of port to open. e.g. COM4 /// Thrown if executing user does not have unrestricted access [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)] internal StrongPort(string portName) { - // http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html SerialPortFixer.Execute(portName); - var port = new SerialPort(); - port.BaudRate = 9600; - port.Parity = System.IO.Ports.Parity.Even; - port.DataBits = 7; - port.StopBits = StopBits.One; - port.Handshake = Handshake.None; - port.ReadTimeout = 500; - port.WriteTimeout = 500; - port.WriteBufferSize = 1000; - port.ReadBufferSize = 1000; - port.Encoding = W1252; - port.DtrEnable = true; - port.RtsEnable = true; - port.DiscardNull = false; - port.PortName = portName; + var port = new SerialPort + { + BaudRate = 9600, + Parity = Parity.Even, + DataBits = 7, + StopBits = StopBits.One, + Handshake = Handshake.None, + ReadTimeout = 500, + WriteTimeout = 500, + WriteBufferSize = 1000, + ReadBufferSize = 1000, + Encoding = _w1252, + DtrEnable = true, + RtsEnable = true, + DiscardNull = false, + PortName = portName + }; port.Open(); try { - this._internalSerialStream = port.BaseStream; - this._serialPort = port; - this._serialPort.DiscardInBuffer(); - this._serialPort.DiscardOutBuffer(); + _internalSerialStream = port.BaseStream; + _serialPort = port; + _serialPort.DiscardInBuffer(); + _serialPort.DiscardOutBuffer(); } catch (Exception ex) { - Stream internalStream = this._internalSerialStream; + var internalStream = _internalSerialStream; if (internalStream == null) { - FieldInfo field = typeof(SerialPort).GetField( + var field = typeof(SerialPort).GetField( "internalSerialStream", BindingFlags.Instance | BindingFlags.NonPublic); @@ -76,20 +77,20 @@ internal StrongPort(string portName) // in future versions of the .NET Framework if (field == null) { - log.WarnFormat( - "An exception occured while creating the serial port adaptor, " - + "the internal stream reference was not acquired and we were unable " - + "to get it using reflection. The serial port may not be accessible " - + "any further until the serial port object finalizer has been run: {0}", - ex); + Log.WarnFormat( + "An exception occured while creating the serial port adaptor, " + + "the internal stream reference was not acquired and we were unable " + + "to get it using reflection. The serial port may not be accessible " + + "any further until the serial port object finalizer has been run: {0}", + ex); throw; } - internalStream = (Stream)field.GetValue(port); + internalStream = (Stream) field.GetValue(port); } - log.DebugFormat( + Log.DebugFormat( "An error occurred while constructing the serial port adaptor: {0}", ex); SafeDisconnect(port, internalStream); @@ -97,71 +98,70 @@ internal StrongPort(string portName) } } - #region Fields + #region Fields + // Serial port objects - readonly SerialPort _serialPort; - readonly Stream _internalSerialStream; + private readonly SerialPort _serialPort; + private readonly Stream _internalSerialStream; + #endregion #region Disposal + /// - /// Releases comm port and related managed resources. + /// Releases comm port and related managed resources. /// public void Dispose() { - this.Dispose(true); + Dispose(true); } /// - /// Releases comm port and related managed resources. + /// Releases comm port and related managed resources. /// protected virtual void Dispose(bool disposing) { - SafeDisconnect(this._serialPort, this._internalSerialStream); + SafeDisconnect(_serialPort, _internalSerialStream); if (disposing) { GC.SuppressFinalize(this); } } + #endregion #region Static + /// - /// Safely closes a serial port and its internal stream even if - /// a USB serial interface was physically removed from the system - /// in a reliable manner. + /// Safely closes a serial port and its internal stream even if + /// a USB serial interface was physically removed from the system + /// in a reliable manner. /// /// /// /// - /// The class has 3 different problems in disposal - /// in case of a USB serial device that is physically removed: - /// - /// 1. The eventLoopRunner is asked to stop and - /// returns false. Upon disposal this property is checked and closing - /// the internal serial stream is skipped, thus keeping the original - /// handle open indefinitely (until the finalizer runs which leads to the next problem) - /// - /// The solution for this one is to manually close the internal serial stream. - /// We can get its reference by - /// before the exception has happened or by reflection and getting the - /// "internalSerialStream" field. - /// - /// 2. Closing the internal serial stream throws an exception and closes - /// the internal handle without waiting for its eventLoopRunner thread to finish, - /// causing an uncatchable ObjectDisposedException from it later on when the finalizer - /// runs (which oddly avoids throwing the exception but still fails to wait for - /// the eventLoopRunner). - /// - /// The solution is to manually ask the event loop runner thread to shutdown - /// (via reflection) and waiting for it before closing the internal serial stream. - /// - /// 3. Since Dispose throws exceptions, the finalizer is not suppressed. - /// - /// The solution is to suppress their finalizers at the beginning. + /// The class has 3 different problems in disposal + /// in case of a USB serial device that is physically removed: + /// 1. The eventLoopRunner is asked to stop and + /// returns false. Upon disposal this property is checked and closing + /// the internal serial stream is skipped, thus keeping the original + /// handle open indefinitely (until the finalizer runs which leads to the next problem) + /// The solution for this one is to manually close the internal serial stream. + /// We can get its reference by + /// before the exception has happened or by reflection and getting the + /// "internalSerialStream" field. + /// 2. Closing the internal serial stream throws an exception and closes + /// the internal handle without waiting for its eventLoopRunner thread to finish, + /// causing an uncatchable ObjectDisposedException from it later on when the finalizer + /// runs (which oddly avoids throwing the exception but still fails to wait for + /// the eventLoopRunner). + /// The solution is to manually ask the event loop runner thread to shutdown + /// (via reflection) and waiting for it before closing the internal serial stream. + /// 3. Since Dispose throws exceptions, the finalizer is not suppressed. + /// The solution is to suppress their finalizers at the beginning. /// - static void SafeDisconnect(SerialPort port, Stream internalSerialStream) + private static void SafeDisconnect(SerialPort port, Stream internalSerialStream) { GC.SuppressFinalize(port); GC.SuppressFinalize(internalSerialStream); @@ -170,69 +170,69 @@ static void SafeDisconnect(SerialPort port, Stream internalSerialStream) try { - log.Debug("Disposing internal serial stream"); + Log.Debug("Disposing internal serial stream"); internalSerialStream.Close(); } catch (Exception ex) { - log.DebugFormat( + Log.DebugFormat( "Exception in serial stream shutdown of port {0}: {1}", port.PortName, ex); } try { - log.Debug("Disposing serial port"); + Log.Debug("Disposing serial port"); port.Close(); } catch (Exception ex) { - log.DebugFormat("Exception in port {0} shutdown: {1}", port.PortName, ex); + Log.DebugFormat("Exception in port {0} shutdown: {1}", port.PortName, ex); } } - static void ShutdownEventLoopHandler(Stream internalSerialStream) + private static void ShutdownEventLoopHandler(Stream internalSerialStream) { try { - log.Debug("Working around .NET SerialPort class Dispose bug"); + Log.Debug("Working around .NET SerialPort class Dispose bug"); - FieldInfo eventRunnerField = internalSerialStream.GetType() + var eventRunnerField = internalSerialStream.GetType() .GetField("eventRunner", BindingFlags.NonPublic | BindingFlags.Instance); if (eventRunnerField == null) { - log.Warn( + Log.Warn( "Unable to find EventLoopRunner field. "); } else { - object eventRunner = eventRunnerField.GetValue(internalSerialStream); - Type eventRunnerType = eventRunner.GetType(); + var eventRunner = eventRunnerField.GetValue(internalSerialStream); + var eventRunnerType = eventRunner.GetType(); - FieldInfo endEventLoopFieldInfo = eventRunnerType.GetField( + var endEventLoopFieldInfo = eventRunnerType.GetField( "endEventLoop", BindingFlags.Instance | BindingFlags.NonPublic); - FieldInfo eventLoopEndedSignalFieldInfo = eventRunnerType.GetField( + var eventLoopEndedSignalFieldInfo = eventRunnerType.GetField( "eventLoopEndedSignal", BindingFlags.Instance | BindingFlags.NonPublic); - FieldInfo waitCommEventWaitHandleFieldInfo = eventRunnerType.GetField( + var waitCommEventWaitHandleFieldInfo = eventRunnerType.GetField( "waitCommEventWaitHandle", BindingFlags.Instance | BindingFlags.NonPublic); if (endEventLoopFieldInfo == null || eventLoopEndedSignalFieldInfo == null || waitCommEventWaitHandleFieldInfo == null) { - log.Warn( + Log.Warn( "Unable to find the EventLoopRunner internal wait handle or loop signal fields."); } else { - log.Debug("Waiting for the SerialPort internal EventLoopRunner thread to finish..."); + Log.Debug("Waiting for the SerialPort internal EventLoopRunner thread to finish..."); var eventLoopEndedWaitHandle = - (WaitHandle)eventLoopEndedSignalFieldInfo.GetValue(eventRunner); + (WaitHandle) eventLoopEndedSignalFieldInfo.GetValue(eventRunner); var waitCommEventWaitHandle = - (ManualResetEvent)waitCommEventWaitHandleFieldInfo.GetValue(eventRunner); + (ManualResetEvent) waitCommEventWaitHandleFieldInfo.GetValue(eventRunner); endEventLoopFieldInfo.SetValue(eventRunner, true); @@ -245,57 +245,59 @@ static void ShutdownEventLoopHandler(Stream internalSerialStream) waitCommEventWaitHandle.Set(); } while (!eventLoopEndedWaitHandle.WaitOne(2000)); - log.Debug("Wait completed. Now it is safe to continue disposal."); + Log.Debug("Wait completed. Now it is safe to continue disposal."); } } } catch (Exception) { - log.Warn("SerialPort workaround failure."); + Log.Warn("SerialPort workaround failure."); } } /// - /// Return a list of all available serial ports that the OS can connect upon + /// Return a list of all available serial ports that the OS can connect upon /// /// public static List GetAvailablePorts() { return SerialPort.GetPortNames().ToList(); } + #endregion #region Properties + /// - /// Returns true if this port is open + /// Returns true if this port is open /// - public bool IsOpen { get { return _serialPort != null && _serialPort.IsOpen; } } + public bool IsOpen => _serialPort != null && _serialPort.IsOpen; /// - /// Returns the OS name for the serial port + /// Returns the OS name for the serial port /// - public string Name { get { return _serialPort.PortName; } set { _serialPort.PortName = value; } } + public string Name + { + get => _serialPort.PortName; + set => _serialPort.PortName = value; + } /// - /// Set the read timeout for the underlying serial port. Any read executed on this port - /// that does not receive data before this timeout will cause a SerialPortTimeout exception. + /// Set the read timeout for the underlying serial port. Any read executed on this port + /// that does not receive data before this timeout will cause a SerialPortTimeout exception. /// public int ReadTimeout { - get - { - return _serialPort.ReadTimeout; - } - set - { - _serialPort.ReadTimeout = value; - } - } + get => _serialPort.ReadTimeout; + set => _serialPort.ReadTimeout = value; + } + #endregion #region Methods + /// - /// This class of port gets opened on instantiation + /// This class of port gets opened on instantiation /// /// bool public bool Connect() @@ -304,28 +306,29 @@ public bool Connect() } /// - /// Attempts to safely close the underling serial port and base stream. - /// If the close operation completes without issue, bool true is returned. + /// Attempts to safely close the underling serial port and base stream. + /// If the close operation completes without issue, bool true is returned. /// /// bool public bool Disconnect() { // Lock prevents closing in the middle of a read - lock (readLock) + lock (ReadLock) { SafeDisconnect(_serialPort, _internalSerialStream); } + return true; } /// - /// Writes the byte[] data to this port. If the port is not open, misconfigured, - /// or if there is a physical connection issue, exceptions may arise. + /// Writes the byte[] data to this port. If the port is not open, misconfigured, + /// or if there is a physical connection issue, exceptions may arise. /// /// byte[] public void Write(byte[] data) { - int reconnectAttempts = 0; + var reconnectAttempts = 0; if (_serialPort != null) { try @@ -356,20 +359,20 @@ public void Write(byte[] data) } /// - /// Reads all available data from this port. If no data is received withint - /// ReadTimeout milliseconds, a timeout exception will be raised. + /// Reads all available data from this port. If no data is received withint + /// ReadTimeout milliseconds, a timeout exception will be raised. /// /// byte[] public byte[] Read() { // Create empty array to hold incoming data - byte[] buffer = new byte[0]; + var buffer = new byte[0]; - lock (readLock) + lock (ReadLock) { if (IsOpen) { - int waitCount = 0; + var waitCount = 0; while (_serialPort.BytesToRead < 11) { waitCount++; @@ -386,12 +389,12 @@ public byte[] Read() _serialPort.Read(buffer, 0, 11); _serialPort.DiscardInBuffer(); - } } return buffer; } + #endregion } } \ No newline at end of file diff --git a/Apex7000_BillValidator/SlaveCodex.cs b/Apex7000_BillValidator/SlaveCodex.cs index 053004f..0b29f15 100644 --- a/Apex7000_BillValidator/SlaveCodex.cs +++ b/Apex7000_BillValidator/SlaveCodex.cs @@ -1,132 +1,129 @@ -using System; - -namespace PyramidNETRS232 +namespace PyramidNETRS232 { + using System; + /// - /// Used internally to quickly translate between bytes and the string meaning + /// Used internally to quickly translate between bytes and the string meaning /// /// \internal - internal class SlaveCodex + internal static class SlaveCodex { - private static SlaveMessage stateMask = (SlaveMessage)0x40C2F; - private static SlaveMessage eventMask = (SlaveMessage)0x30350; - private static SlaveMessage cbOkayMask = (SlaveMessage)0x001000; - private static SlaveMessage creditMask = (SlaveMessage)0x380000; - - /// - /// RS-232 mixed a couple of events in with state - /// - [System.Flags] - internal enum SlaveMessage : int - { - // Byte 0 - bit 0 - Idling = 1 << 0, // State - Accepting = 1 << 1, // State - Escrowed = 1 << 2, // State - Stacking = 1 << 3, // State - Stacked = 1 << 4, // Event - Returning = 1 << 5, // State - Returned = 1 << 6, // Event - - // Ignore 8th bit in 7-bit RS-232 - x1 = 1 << 7, - - // Byte1 - bit 0 - Cheated = 1 << 8, // Event - BillRejected = 1 << 9, // Event - BillJammed = 1 << 10, // State - StackerFull = 1 << 11, // State - StackerPresent = 1 << 12, // Ephemeral - Reserved1 = 1 << 13, // Set to 0 - Reserved2 = 1 << 14, // Set to 0 - - // Ignore 8th bit in 7-bit RS-232 - x2 = 1 << 15, - - // Byte2 - bit 0 - PowerUp = 1 << 16, // Event - InvalidCommand = 1 << 17, // Event - Failure = 1 << 18, // State - C1 = 1 << 19, // Credit bit 1 - C2 = 1 << 20, // Credit bit 2 - C3 = 1 << 21, // Credit bit 3 - Reserved = 1 << 23, // Set to 0 - - // Ignore 8th bit in 7-bit RS-232 - x3 = 1 << 24 - } + private const SlaveMessage StateMask = (SlaveMessage) 0x40C2F; + private const SlaveMessage EventMask = (SlaveMessage) 0x30350; + private const SlaveMessage CbOkayMask = (SlaveMessage) 0x001000; + private const SlaveMessage CreditMask = (SlaveMessage) 0x380000; internal static SlaveMessage ToSlaveMessage(byte[] message) { if (message.Length != 11) + { return SlaveMessage.InvalidCommand; - - int combined = ( - (message[5] << 16) | - (message[4]) << 8 | - (message[3]) - ); - return (SlaveMessage)combined; + } + + var combined = (message[5] << 16) | + (message[4] << 8) | + message[3]; + return (SlaveMessage) combined; } internal static States GetState(SlaveMessage message) { // Clear non-state bits - message &= stateMask; + message &= StateMask; if ((message & SlaveMessage.Failure) == SlaveMessage.Failure) + { return States.AcceptorFailure; + } + if ((message & SlaveMessage.StackerFull) == SlaveMessage.StackerFull) + { return States.StackerFull; + } + if ((message & SlaveMessage.BillJammed) == SlaveMessage.BillJammed) + { return States.BillJammed; + } + if ((message & SlaveMessage.Returning) == SlaveMessage.Returning) + { return States.Returning; + } + if ((message & SlaveMessage.Stacking) == SlaveMessage.Stacking) + { return States.Stacking; + } + if ((message & SlaveMessage.Escrowed) == SlaveMessage.Escrowed) + { return States.Escrowed; + } + if ((message & SlaveMessage.Accepting) == SlaveMessage.Accepting) + { return States.Accepting; + } + if ((message & SlaveMessage.Idling) == SlaveMessage.Idling) + { return States.Idling; + } return States.Offline; } internal static Events GetEvents(SlaveMessage message) { - message &= eventMask; - Events result = Events.None; + message &= EventMask; + var result = Events.None; if ((message & SlaveMessage.Stacked) == SlaveMessage.Stacked) + { result |= Events.Stacked; + } + if ((message & SlaveMessage.Returned) == SlaveMessage.Returned) + { result |= Events.Returned; + } + if ((message & SlaveMessage.Cheated) == SlaveMessage.Cheated) + { result |= Events.Cheated; + } + if ((message & SlaveMessage.BillRejected) == SlaveMessage.BillRejected) + { result |= Events.BillRejected; + } + if ((message & SlaveMessage.PowerUp) == SlaveMessage.PowerUp) + { result |= Events.PowerUp; + } + if ((message & SlaveMessage.InvalidCommand) == SlaveMessage.InvalidCommand) + { result |= Events.InvalidCommand; + } return result; } internal static bool IsCashboxPresent(SlaveMessage message) { - message &= cbOkayMask; + message &= CbOkayMask; return (message & SlaveMessage.StackerPresent) == SlaveMessage.StackerPresent; } internal static int GetCredit(SlaveMessage message) { - message &= creditMask; + message &= CreditMask; - return (int)message >> 19; + return (int) message >> 19; } // enum Byte 3 Reserved - all bits must be 0 @@ -134,13 +131,56 @@ internal static int GetCredit(SlaveMessage message) // enum Byte 4 Model number - (00-7FH) internal static string GetModelNumber(byte byte4) { - return String.Format("{0}", byte4); + return $"{byte4}"; } - + // enum Byte 5 Firmware Rev - (00-7FH) internal static string GetFirmwareRevision(byte byte5) { - return String.Format("{0}", byte5); + return $"{byte5}"; + } + + /// + /// RS-232 mixed a couple of events in with state + /// + [Flags] + internal enum SlaveMessage + { + // Byte 0 - bit 0 + Idling = 1 << 0, // State + Accepting = 1 << 1, // State + Escrowed = 1 << 2, // State + Stacking = 1 << 3, // State + Stacked = 1 << 4, // Event + Returning = 1 << 5, // State + Returned = 1 << 6, // Event + + // Ignore 8th bit in 7-bit RS-232 + X1 = 1 << 7, + + // Byte1 - bit 0 + Cheated = 1 << 8, // Event + BillRejected = 1 << 9, // Event + BillJammed = 1 << 10, // State + StackerFull = 1 << 11, // State + StackerPresent = 1 << 12, // Ephemeral + Reserved1 = 1 << 13, // Set to 0 + Reserved2 = 1 << 14, // Set to 0 + + // Ignore 8th bit in 7-bit RS-232 + X2 = 1 << 15, + + // Byte2 - bit 0 + PowerUp = 1 << 16, // Event + InvalidCommand = 1 << 17, // Event + Failure = 1 << 18, // State + C1 = 1 << 19, // Credit bit 1 + C2 = 1 << 20, // Credit bit 2 + C3 = 1 << 21, // Credit bit 3 + Reserved = 1 << 23, // Set to 0 + + // Ignore 8th bit in 7-bit RS-232 + X3 = 1 << 24 } } -} +} \ No newline at end of file From 7413ec94f945d6dd0e2d1eb8926ec58cb09ff2ef Mon Sep 17 00:00:00 2001 From: Cory Todd Date: Wed, 26 Feb 2020 07:41:39 -0700 Subject: [PATCH 02/12] Fix misspelled method name --- Apex7000_BillValidator/ApexValidator.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Apex7000_BillValidator/ApexValidator.cs b/Apex7000_BillValidator/ApexValidator.cs index 12a0901..76180ec 100644 --- a/Apex7000_BillValidator/ApexValidator.cs +++ b/Apex7000_BillValidator/ApexValidator.cs @@ -137,7 +137,7 @@ public void Close() /// the serial port. Use Close() for that effect. /// This effectively tells the acceptor to stop accepting bill but keep reporting status. /// The acceptor's lights will turn off after this call takes effect. - /// + /// /// public void PauseAcceptance() { @@ -167,6 +167,23 @@ public void ResmeAcceptance() IsPaused = false; } } + + /// + /// Returns the acceptor to bill accepting mode. This command + /// has no effect if the acceptor is already running and accepting. + /// The acceptor's lights will turn on after this command takes effect. + /// The command will take up to Config.PollRate ms to take effect. + /// + /// + public void ResumeAcceptance() + { + Config.EnableMask = _lastEnablePattern; + + lock (_mutex) + { + IsPaused = false; + } + } /// /// Connect to the device and begin speaking rs232 From b1f91c056113ff4d634a85ac927ee391839b6fe4 Mon Sep 17 00:00:00 2001 From: Cory Todd Date: Wed, 26 Feb 2020 07:42:35 -0700 Subject: [PATCH 03/12] Protect reader thread with try/catch Protect against disappearing comm ports in the read loop --- Apex7000_BillValidator/ApexValidator.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Apex7000_BillValidator/ApexValidator.cs b/Apex7000_BillValidator/ApexValidator.cs index 76180ec..61094c9 100644 --- a/Apex7000_BillValidator/ApexValidator.cs +++ b/Apex7000_BillValidator/ApexValidator.cs @@ -167,7 +167,7 @@ public void ResmeAcceptance() IsPaused = false; } } - + /// /// Returns the acceptor to bill accepting mode. This command /// has no effect if the acceptor is already running and accepting. @@ -276,16 +276,25 @@ private void StartRS232Loop() // Set toggle flag so we can kill this loop while (IsRunning) { - if (_resetRequested) + try { - ResetAcceptor(); + if (_resetRequested) + { + ResetAcceptor(); + } + else + { + ReadAcceptorResp(); + } + + Thread.Sleep(Config.PollRate); } - else + catch (Exception ex) { - ReadAcceptorResp(); + Log.Error("RS232 Loop port error", ex); + NotifyError(Errors.PortError); + IsRunning = false; } - - Thread.Sleep(Config.PollRate); } }) {IsBackground = true}; From a06f2963187a79e2ea006f6816d244aeb01e8283 Mon Sep 17 00:00:00 2001 From: Cory Todd Date: Wed, 26 Feb 2020 08:00:39 -0700 Subject: [PATCH 04/12] Allow custom ICommPort implementation It is a bit round about --- Apex7000_BillValidator/ApexValidator.cs | 14 +++++--------- Apex7000_BillValidator/Events.cs | 1 + Apex7000_BillValidator/RS232Config.cs | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Apex7000_BillValidator/ApexValidator.cs b/Apex7000_BillValidator/ApexValidator.cs index 61094c9..2e2e2c4 100644 --- a/Apex7000_BillValidator/ApexValidator.cs +++ b/Apex7000_BillValidator/ApexValidator.cs @@ -18,18 +18,18 @@ public partial class PyramidAcceptor : IDisposable private readonly object _mutex = new object(); + // Time at which escrow starts + private DateTime _escrowStart = DateTime.MinValue; + /// /// Stores the prior enable/disable pattern, used with the Enable/Disable calls /// private byte _lastEnablePattern; - private StrongPort _port; + private ICommPort _port; private bool _resetRequested; - // Time at which escrow starts - private DateTime _escrowStart = DateTime.MinValue; - /// /// Creates a new PyramidAcceptor using the specified configuration /// @@ -90,8 +90,6 @@ public PyramidAcceptor(RS232Config config) /// public bool IsPaused { get; private set; } - #region IDisposable - /// /// Releases comm port and related managed resources. /// @@ -100,8 +98,6 @@ public void Dispose() _port?.Dispose(); } - #endregion - /// /// Returns a list of all available ports @@ -196,7 +192,7 @@ public void Connect() { try { - _port = new StrongPort(Config.CommPortName); + _port = Config.GetCommPort(); } catch (IOException) { diff --git a/Apex7000_BillValidator/Events.cs b/Apex7000_BillValidator/Events.cs index 07f02e7..660a150 100644 --- a/Apex7000_BillValidator/Events.cs +++ b/Apex7000_BillValidator/Events.cs @@ -1,6 +1,7 @@ namespace PyramidNETRS232 { using System; + using System.Collections.Generic; public partial class PyramidAcceptor { diff --git a/Apex7000_BillValidator/RS232Config.cs b/Apex7000_BillValidator/RS232Config.cs index a26db6e..dfc98d3 100644 --- a/Apex7000_BillValidator/RS232Config.cs +++ b/Apex7000_BillValidator/RS232Config.cs @@ -1,6 +1,7 @@ namespace PyramidNETRS232 { using System; + using PTI.Serial; /// /// Define the operating parameters of your bill acceptor @@ -94,5 +95,18 @@ public byte EnableMask get => _enableMask; set => _enableMask = (byte) (0x7F & value); } + + /// + /// Create a new comm port using this configuration. + /// By default, this library uses the internal StrongPort type. + /// If you want to use a different implementation, create a + /// class that extends ICommPort and override this class to + /// return your type. + /// + /// ICommPort + public virtual ICommPort GetCommPort() + { + return new StrongPort(CommPortName); + } } } \ No newline at end of file From 502d1da18e69985392df52b99dfaeab442d6a766 Mon Sep 17 00:00:00 2001 From: Cory Todd Date: Wed, 26 Feb 2020 08:03:48 -0700 Subject: [PATCH 05/12] Use fixed-width font on test app Keep the letters from bouncing around with monospace --- Apex7000_BillValidator_Test/MainWindow.xaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Apex7000_BillValidator_Test/MainWindow.xaml b/Apex7000_BillValidator_Test/MainWindow.xaml index 97d13f0..ee3c7f6 100644 --- a/Apex7000_BillValidator_Test/MainWindow.xaml +++ b/Apex7000_BillValidator_Test/MainWindow.xaml @@ -199,7 +199,7 @@ - +