//----------------------------------------------------------------------- // // Copyright © 2012 Nils Hammar. All rights reserved. // //----------------------------------------------------------------------- /* * Software to access vehicle information via the OBD-II connector. * * Copyright © 2012 Nils Hammar * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Alternative licensing is possible, see the licensing document. * * The above text may not be removed or modified. */ namespace ObdSimulator { using System; using System.Collections.Generic; using System.ComponentModel; using System.IO.Ports; using System.Linq; using System.Threading; using System.Windows.Forms; using FtdiApi; using FtdiApi.Constants; using FtdiApi.Exceptions; using SharedObjects; /// /// TODO: Update summary. /// public sealed class SimpleSerialDevice : IDisposable { /// /// Byte pairs that are answers on five baud init request. /// public static readonly byte[][] EXPECT_BYTE_PAIRS = new byte[][] { new byte[] { 0x08, 0x08 }, new byte[] { 0x94, 0x94 }, new byte[] { 0x8f, 0xe9 }, new byte[] { 0x8f, 0x6b }, new byte[] { 0x8f, 0x6d }, new byte[] { 0x8f, 0xef }, }; /// /// Start communication request. /// /// For ISO14230 fast init. /// /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed, intentional")] private static readonly byte[] START_COMM_REQ = new byte[] { 0xc1, 0x33, 0xf1, 0x81, 0x66 }; /// /// Keepalive request. /// /// For ISO 9141. /// /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed, intentional")] private static readonly byte[] KEEPALIVE_REQ_1 = new byte[] { 0x68, 0x6a, 0xf1, 0x01, 0x00, 0xc4 }; /// /// Keepalive request. /// /// For ISO ISO14230 5 baud init. /// /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed, intentional")] private static readonly byte[] KEEPALIVE_REQ_2 = new byte[] { 0xc2, 0x33, 0xf1, 0x01, 0x00, 0xe7 }; /// /// Second set of expected data. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed, intentional")] private static readonly byte[][] EXPECT_BYTE2 = new byte[][] { new byte[] { 0xcc }, }; /// /// Current FTDI API instance. /// private FTDI ftdi; /// /// Current FTDI port. /// private FtdiPort ftdiPort = null; /// /// Current serial port using Windows API. /// private SerialPort serialPort1 = null; /// /// Current serial port using FTDI API. /// private string comPort = null; /// /// Communication speed. /// private int speed = 10400; /// /// Set of expected bytes from protocol. /// private byte[][] expectByteSet = null; /// /// Received data bytes. /// private byte[] rxBytes = null; /// /// Serial data event handler. /// private SerialDataReceivedEventHandler serialDataReceivedEventHandler = null; /// /// Timestamp for last read. /// private long lastReadTs = 0; /// /// Calibrated delay value for 5 baud formatting. /// private int newDelay = 200; /// /// Event logging instance. /// private ILogging iLogging; /// /// Lock object for thread synchronization. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed, intentional")] private object lockObject = new object(); /// /// Delta time between reads from port. /// private long delta = 0; /// /// Received data event. /// private AutoResetEvent receivedDataEvent; /// /// Data receiver handler. /// private BackgroundWorker dataReceivedHandler; /// /// Flag indicating valid data. /// private bool ftdiValidData = true; /// /// Flag indicating that a Break has occurred. /// private bool breakFlag = false; /// /// Flag indicating that RTS is set. /// private bool rtsFlag = false; /// /// Flag indicating that DTR is set. /// private bool dtrFlag = false; /// /// Flag indicating previous modem status. /// private byte oldModemStatus = 0; /// /// Gets or sets a value indicating whether L-line is used or not. /// public bool lLine { get; set; } /// /// Gets or sets a value indicating whether calibration shall occur. /// public bool calibrate { get; set; } /// /// Gets a value indicating whether the Carrier Detect state is set. /// public bool cdState { get { if (this.ftdiPort != null) { byte mdmStat = 0; this.ftdiPort.GetModemStatus(ref mdmStat); return ((mdmStat & FT_MODEM_STATUS.FT_DCD) != 0); } return this.serialPort1.CDHolding; } } /// /// Gets a value indicating whether the Clear to Send state. /// public bool ctsState { get { if (this.ftdiPort != null) { byte mdmStat = 0; this.ftdiPort.GetModemStatus(ref mdmStat); return ((mdmStat & FT_MODEM_STATUS.FT_CTS) != 0); } return this.serialPort1.CtsHolding; } } /// /// Gets a value indicating whether the Data Set Ready state is set. /// public bool dsrState { get { if (this.ftdiPort != null) { byte mdmStat = 0; this.ftdiPort.GetModemStatus(ref mdmStat); return ((mdmStat & FT_MODEM_STATUS.FT_DSR) != 0); } return this.serialPort1.DsrHolding; } } /// /// Gets or sets a value indicating whether the Request to Send state is set. /// public bool rtsState { get { if (this.ftdiPort != null) { return this.rtsFlag; } return this.serialPort1.RtsEnable; } set { this.rtsFlag = value; if (this.ftdiPort != null) { this.ftdiPort.SetRTS(this.rtsFlag); } else { this.serialPort1.RtsEnable = this.rtsFlag; } } } /// /// Gets or sets a value indicating whether the Data Terminal Ready state is set. /// public bool dtrState { get { if (this.ftdiPort != null) { return this.dtrFlag; } return this.serialPort1.DtrEnable; } set { this.dtrFlag = value; if (this.ftdiPort != null) { this.ftdiPort.SetDTR(this.dtrFlag); } else { this.serialPort1.DtrEnable = this.dtrFlag; } } } /// /// Gets or sets a value indicating whether the break state is set. /// public bool breakState { get { if (this.ftdiPort != null) { return this.breakFlag; } return this.serialPort1.BreakState; } set { this.breakFlag = value; if (this.ftdiPort != null) { this.ftdiPort.SetBreak(this.breakFlag); } else { this.serialPort1.BreakState = this.breakFlag; } } } /// /// Gets the detected W1 parameter. /// public int w1 { get; private set; } /// /// Gets the detected W2 parameter. /// public int w2 { get; private set; } /// /// Gets the detected W3 parameter. /// public int w3 { get; private set; } /// /// Gets the W4 parameter. /// public int w4 { get; private set; } /// /// Gets or sets current Baud rate. /// public int baudRate { get { return this.speed; } set { this.speed = value; if (this.ftdiPort != null) { this.ftdiPort.SetBaudRate((uint)this.speed); } else { if (this.serialPort1 != null) { this.serialPort1.BaudRate = this.speed; } } } } /// /// Gets or sets a value indicating whether if line data echo shall occur. /// public bool echoLine { get; set; } /// /// Gets or sets a value indicating whether modem status changes shall be presented. /// public bool echoModem { get; set; } /// /// Current port instane. /// private FT_DEVICE_INFO_NODE selectedFtdiPort; /// /// OBD simulator instance. /// private IObdSimulator iObdSimulator; /// /// Initializes a new instance of the class. /// /// FTDI handler instance. /// COM port name. /// Selected FTDI port instance. /// Communication Speed. /// Event logging instance. /// Simulator instance receiving data. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "comPort", Justification = "Reviewed, intentional")] public SimpleSerialDevice(FTDI ftdi, string comPort, FT_DEVICE_INFO_NODE selectedFtdiPort, int speed, ILogging iLogging, IObdSimulator iObdSimulator) { this.ftdi = ftdi; this.comPort = comPort; this.selectedFtdiPort = selectedFtdiPort; this.speed = speed; this.iLogging = iLogging; this.iObdSimulator = iObdSimulator; this.echoLine = false; this.echoModem = false; this.calibrate = true; this.lLine = false; this.w1 = 0; this.w2 = 0; this.w3 = 0; this.w4 = 25; } /// /// Get array of known FTDI devices. /// /// Array of known FTDI devices. public static FT_DEVICE_INFO_NODE[] ftdiDevices() { FT_DEVICE_INFO_NODE[] newDeviceNames = null; List deviceNameList = new List(); FTDI ftdi = new FTDI(); try { uint devcount = 0; ftdi.GetNumberOfDevices(ref devcount); FT_DEVICE_INFO_NODE[] devicelist = new FT_DEVICE_INFO_NODE[devcount]; ftdi.GetDeviceList(devicelist); foreach (FT_DEVICE_INFO_NODE deviceItem in devicelist) { if (deviceItem != null && !string.IsNullOrEmpty(deviceItem.Description)) { deviceNameList.Add(deviceItem); } } } catch (FT_EXCEPTION ex) { MessageBox.Show(ex.GetType().ToString() + "\r\n" + ex.Message); } finally { newDeviceNames = deviceNameList.ToArray(); } return newDeviceNames; } /// /// Dispose the instance. /// public void Dispose() { this.close(); this.dataReceivedHandler.Dispose(); this.receivedDataEvent.Dispose(); } /// /// Open serial port, handles both Microsoft native and FTDI serial ports. /// public void open() { if (this.selectedFtdiPort != null) { this.ftdiPort = new FtdiPort(this.ftdi); this.ftdiValidData = true; this.ftdiPort.OpenByLocation(this.selectedFtdiPort.LocId); this.ftdiPort.SetBaudRate((uint)this.speed); this.ftdiPort.SetDataCharacteristics( FT_DATA_BITS.FT_BITS_8, FT_STOP_BITS.FT_STOP_BITS_1, FT_PARITY.FT_PARITY_NONE); this.ftdiPort.SetLatency(2); this.ftdiPort.SetFlowControl(FT_FLOW_CONTROL.FT_FLOW_NONE, 0, 0); this.receivedDataEvent = new AutoResetEvent(false); this.ftdiPort.SetEventNotification( FT_EVENTS.FT_EVENT_RXCHAR | FT_EVENTS.FT_EVENT_LINE_STATUS | FT_EVENTS.FT_EVENT_MODEM_STATUS, this.receivedDataEvent); this.dataReceivedHandler = new BackgroundWorker(); this.dataReceivedHandler.DoWork += this.ReadData; if (!this.dataReceivedHandler.IsBusy) { this.dataReceivedHandler.RunWorkerAsync(); } } else { this.serialPort1 = new SerialPort(this.comPort, this.speed, Parity.None, 8, StopBits.One); this.serialPort1.Handshake = Handshake.None; this.serialPort1.Open(); this.serialPort1.ErrorReceived += new System.IO.Ports.SerialErrorReceivedEventHandler(this.serialPort1_ErrorReceived); this.serialPort1.PinChanged += new System.IO.Ports.SerialPinChangedEventHandler(this.serialPort1_PinChanged); this.serialDataReceivedEventHandler = new System.IO.Ports.SerialDataReceivedEventHandler(this.serialPort1_DataReceived); this.serialPort1.DataReceived += this.serialDataReceivedEventHandler; this.iLogging.appendText("Encoding=" + this.serialPort1.Encoding.EncodingName + "\r\n"); } } /// /// Close the currently open serial port. /// public void close() { if (this.ftdiPort != null) { try { this.ftdiPort.Close(); } catch (Exception ex) { this.iLogging.appendText("ftdi; Close failed: " + ex.Message + "\r\n"); } finally { this.ftdiPort = null; } } if (this.serialPort1 != null) { try { this.serialPort1.Close(); } catch (Exception ex) { this.iLogging.appendText("serialPort1; Close failed: " + ex.Message + "\r\n"); } finally { this.serialPort1 = null; } } } /// /// Handle port event. /// public void portEvent() { if (this.ftdiPort != null) { this.handleModemStatus(); this.handleLineStatus(); this.readData(); } } /// /// Write data to port. /// /// Data to send public void write(byte[] ba) { if (ba != null) { if (this.ftdiPort != null) { uint written = 0; this.ftdiPort.Write(ba, ba.Length, ref written); if (written != ba.Length) { this.iLogging.appendText("\r\nWritten less than provided! (written=" + written + ", requested=" + ba.Length + ")\r\n"); } } else { this.serialPort1.Write(ba, 0, ba.Length); } } } /// /// Build initialization packet. /// /// Constructed data packet. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed, intentional")] private static uint buildInitPacket() { byte startByte = 0x0; byte stopByte = 0xff; byte contentByte = 0x33; uint dataPacket = (uint)(stopByte & 0x01); dataPacket = (uint)(dataPacket << 8) | (uint)(contentByte & 0xff); dataPacket = (uint)(dataPacket << 1) | (uint)(startByte & 0x01); return dataPacket; } /// /// Read data from port. /// /// Sending object. /// Event data. private void ReadData(object pSender, DoWorkEventArgs pEventArgs) { try { while (true) { // wait until event is fired this.receivedDataEvent.WaitOne(); this.portEvent(); } } catch (Exception ex) { this.iLogging.appendText("ReadData failed: " + ex.Message + "\r\n" + ex.StackTrace + "\r\n"); } } /// /// Read data from port. /// /// Number of bytes private uint readData() { uint nrOfBytesAvailable = 0; // try to recieve data now this.ftdiPort.GetRxBytesAvailable(ref nrOfBytesAvailable); // If data exists and is valid. if (this.ftdiValidData && nrOfBytesAvailable > 0) { byte[] readData = new byte[nrOfBytesAvailable]; uint numBytesRead = 0; this.ftdiPort.Read(readData, nrOfBytesAvailable, ref numBytesRead); long t1 = DateTime.Now.Ticks; this.delta = (t1 - this.lastReadTs) / (TimeSpan.TicksPerSecond / 1000); if (this.delta > 200) { this.iLogging.appendText("\r\n"); } this.iLogging.appendText(" [" + this.delta + "]"); this.lastReadTs = t1; this.iLogging.appendText(" {" + numBytesRead + "}"); for (int i = 0; i < numBytesRead && i < nrOfBytesAvailable; i++) { this.handleChar(readData[i], this.delta); } } return nrOfBytesAvailable; } /// /// Handle modem signal status change. /// private void handleModemStatus() { if (this.echoLine) { byte modemStatus = 0; this.ftdiPort.GetModemStatus(ref modemStatus); if (modemStatus != this.oldModemStatus) { if ((FT_MODEM_STATUS.FT_CTS & modemStatus) != (FT_MODEM_STATUS.FT_CTS & this.oldModemStatus)) { if ((FT_MODEM_STATUS.FT_CTS & modemStatus) != 0) { this.iLogging.appendText("◅CTS+▻"); } else { this.iLogging.appendText("◅CTS-▻"); } } if ((FT_MODEM_STATUS.FT_DCD & modemStatus) != (FT_MODEM_STATUS.FT_DCD & this.oldModemStatus)) { if ((FT_MODEM_STATUS.FT_DCD & modemStatus) != 0) { this.iLogging.appendText("◅DCD+▻"); } else { this.iLogging.appendText("◅DCD-▻"); } } if ((FT_MODEM_STATUS.FT_DSR & modemStatus) != (FT_MODEM_STATUS.FT_DSR & this.oldModemStatus)) { if ((FT_MODEM_STATUS.FT_DSR & modemStatus) != 0) { this.iLogging.appendText("◅DSR+▻"); } else { this.iLogging.appendText("◅DSR-▻"); } } if ((FT_MODEM_STATUS.FT_RI & modemStatus) != (FT_MODEM_STATUS.FT_RI & this.oldModemStatus)) { if ((FT_MODEM_STATUS.FT_RI & modemStatus) != 0) { this.iLogging.appendText("◅RI+▻"); } else { this.iLogging.appendText("◅RI-▻"); } } this.oldModemStatus = modemStatus; } } } /// /// Handle line status change. /// private void handleLineStatus() { byte lineStatus = 0; this.ftdiPort.GetLineStatus(ref lineStatus); if (this.echoModem) { if (lineStatus > 0) { if ((FT_LINE_STATUS.FT_BI & lineStatus) != 0) { this.iLogging.appendText("⌦BREAK⌫"); } if ((FT_LINE_STATUS.FT_FE & lineStatus) != 0) { this.iLogging.appendText("⌦Framing Error⌫"); } if ((FT_LINE_STATUS.FT_OE & lineStatus) != 0) { this.iLogging.appendText("⌦Overrun Error⌫"); } if ((FT_LINE_STATUS.FT_PE & lineStatus) != 0) { this.iLogging.appendText("⌦Parity Error⌫"); } } } } /// /// Read characters from serial port. /// private void readChars() { while (this.serialPort1.BytesToRead > 0) { this.readChar(); } } /// /// Read single character from serial port. /// private void readChar() { int ch = this.serialPort1.ReadChar(); this.handleChar(ch, this.delta); } /// /// Handle single character received. /// /// Character to handle. /// Delta since last character received, used for detecting initialization. private void handleChar(int ch1, long delta1) { this.iObdSimulator.rxData((byte)ch1, delta1); } /// /// Display protocol information. /// /// Protocol index. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed, intentional")] private void displayInfo(int ix) { switch (ix) { case 0: this.iLogging.appendText("Protocol ISO9141 [0x0808]\r\n"); break; case 1: this.iLogging.appendText("Protocol ISO9141 [0x9494]\r\n"); break; case 2: this.iLogging.appendText("Protocol ISO14230 [0x8fe9]\r\n"); break; case 3: this.iLogging.appendText("Protocol ISO14230 [0x8f6b]\r\n"); break; case 4: this.iLogging.appendText("Protocol ISO14230 [0x8f6d]\r\n"); break; case 5: this.iLogging.appendText("Protocol ISO14230 [0x8fef]\r\n"); break; } this.iLogging.appendText("W1=" + this.w1 + ", W2=" + this.w2 + ", W3=" + this.w3 + ", W4=" + this.w4 + "\r\n"); } /// /// Ack key bytes. /// /// Value of key byte 2. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed, intentional")] private void ackKeyBytes(byte keyByte2) { byte[] buf = new byte[1]; buf[0] = (byte)(~keyByte2 & 0xff); if (this.ftdiPort != null) { uint written = 0; this.ftdiPort.Write(buf, 1, ref written); if (written != 1) { this.iLogging.appendText("\r\nWritten less than provided! (written=" + written + ", requested=" + 1 + ")\r\n"); } } else { this.serialPort1.Write(buf, 0, 1); } } /// /// Verify response data. /// /// Index of data. /// 'true' if match found. /// Key byte 2 value. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed, intentional")] private void verifyResponse(out int ix, out bool match, out byte keyByte2) { ix = 0; match = true; keyByte2 = 0x00; foreach (byte[] expectBytes1 in this.expectByteSet) { for (int i = 0; i < this.rxBytes.Length && i < expectBytes1.Length; i++) { if (expectBytes1[i] != this.rxBytes[i]) { match = false; break; } } if (match) { if (this.rxBytes.Length >= 2) { keyByte2 = this.rxBytes[1]; } break; } ix++; } } /// /// Send initialization packet. /// /// Data to send. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed, intentional")] private void sendInitPacket(uint dataPacket) { if (this.ftdiPort != null) { this.ftdiValidData = false; } else { this.serialPort1.DataReceived -= this.serialDataReceivedEventHandler; } if (this.ftdiPort != null) { this.ftdiPort.Purge(FT_PURGE.FT_PURGE_RX); } else { this.serialPort1.DiscardInBuffer(); } this.txData(10, this.newDelay, dataPacket); // Just in case. this.breakState = false; // To get correct time for W1 this.lastReadTs = DateTime.Now.Ticks; if (this.ftdiPort != null) { this.ftdiValidData = true; } else { this.serialPort1.DataReceived += this.serialDataReceivedEventHandler; } } /// /// Calibrate delay loop for 5 baud. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed, intentional")] private void calibrateDelay() { if (this.calibrate) { this.iLogging.appendText("Calibrating for 5 baud tx.\r\n"); this.newDelay = this.calibrateFiveBaud(); if (this.newDelay < 0) { this.iLogging.appendText("Error! Negative delay calculated!\r\n"); throw new Exception("Negative delay calculated!"); } } this.iLogging.appendText("\r\nnewDelay=" + this.newDelay + "\r\n"); } /// /// Calibrate the delay for correct transmission of five baud data. /// /// Calibrated delay value. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed, intentional")] private int calibrateFiveBaud() { int bits = 32; int delay = 200; long t0 = 0; long t1 = 0; this.txData(bits, delay, 0xff0000ff, out t0, out t1); long ticksPerBit = (t1 - t0) / bits; long timePerBit = (ticksPerBit / 10000); int newDelay1 = (int)(delay - (timePerBit - delay)); this.iLogging.appendText("Calibrated!\r\n"); // Just in case. this.breakState = false; Thread.Sleep(300); return newDelay1; } /// /// Transmit 5 baud data. /// /// Number of bits to transmit. /// Delay value. /// Data to transmit. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed, intentional")] private void txData(int bits, int delay, uint dataPacket) { long t0; long t1; this.txData(bits, delay, dataPacket, out t0, out t1); } /// /// Transmit 5 baud data. /// /// Number of bits to transmit. /// Delay value. /// Data to transmit. /// Timestamp 0 /// Timestamp 1 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed, intentional")] private void txData(int bits, int delay, uint dataPacket, out long t0, out long t1) { ThreadPriority currentPrio = Thread.CurrentThread.Priority; try { Thread.CurrentThread.Priority = ThreadPriority.Highest; t0 = DateTime.Now.Ticks; for (int i = 0; i < bits; i++) { if ((dataPacket & (1 << i)) != 0) { this.setLineState(false); this.iLogging.appendText("1"); } else { this.setLineState(true); this.iLogging.appendText("0"); } Thread.Sleep(delay); } t1 = DateTime.Now.Ticks; } finally { Thread.CurrentThread.Priority = currentPrio; } } /// /// Set the state of the port line. /// /// State to set. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed, intentional")] private void setLineState(bool state) { if (this.lLine) { this.dtrState = state; } this.breakState = state; } /// /// Event for received error. /// /// Sending object. /// Event data. private void serialPort1_ErrorReceived(object sender, SerialErrorReceivedEventArgs e) { if (this.serialPort1 != null) { SerialError err = e.EventType; this.iLogging.appendText("Error Received :" + this.serialPort1.PortName + ", error=" + err + "\r\n"); } } /// /// Event for received data. /// /// Sending object. /// Event data. private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) { if (this.serialPort1 != null) { SerialData serialData = e.EventType; this.readChars(); } } /// /// Event for changed PIN code. /// /// Sending object. /// Event data. private void serialPort1_PinChanged(object sender, SerialPinChangedEventArgs e) { if (this.serialPort1 != null) { SerialPinChange spc = e.EventType; } } } }