SoFunction
Updated on 2025-04-06

Implement vending machine interface through C#

The following sections introduce the method of implementing the vending machine interface of C#. The code is written in very detailed. If you don’t understand, please refer to the comments.

MachineJP class:

Part 1: Serial port initialization, serial port data reading and writing

using System;
using ;
using ;
using ;
using ;
using ;
using ;
using ;
using ;
using ;

namespace MachineJPDll
{
  /// <summary>
  /// Vending machine interface (interface)  /// </summary>
  public partial class MachineJP
  {
    #region variable    /// <summary>
    /// Serial port resources    /// </summary>
    private SerialPort m_SerialPort = null;
    /// <summary>
    /// List of commands to be sent to the serial port    /// </summary>
    private List<Cmd> m_CommandList = new List<Cmd>();
    /// <summary>
    /// List of messages sent by the PC side of ACK_RPT or NAK_RPT to the VMC side    /// </summary>
    private List<MT> m_WaitResultMTList = new List<MT>();
    /// <summary>
    /// Collection of data received from the serial port (data has been verified)    /// </summary>
    private ReceiveDataCollection m_ReceiveDataCollection = new ReceiveDataCollection();
    #endregion

    #region constructor and destructor    /// <summary>
    /// Vending machine interface (interface)    /// </summary>
    public MachineJP()
    {

    }

    ~MachineJP()
    {
      if (m_SerialPort != null)
      {
        m_SerialPort.Close();
        m_SerialPort.Dispose();
        m_SerialPort = null;
      }
    }
    #endregion

    #region Read serial port data    /// <summary>
    /// Read serial port data    /// </summary>
    /// <returns>Data read from serial port</returns>    private byte[] ReadPort()
    {
      //Read serial port data      DateTime dt = ;
      while (m_SerialPort.BytesToRead &lt; 2)
      {
        (1);

        if ((dt).TotalMilliseconds &gt; 1500) //time out        {
          return new byte[0];
        }
      }
      List&lt;byte&gt; recList = new List&lt;byte&gt;();
      byte[] recData = new byte[m_SerialPort.BytesToRead];
      m_SerialPort.Read(recData, 0, );
      (recData);
      int length = recData[1] + 2; //Total length of packet data      while ( &lt; length)
      {
        if (m_SerialPort.BytesToRead &gt; 0)
        {
          recData = new byte[m_SerialPort.BytesToRead];
          m_SerialPort.Read(recData, 0, );
          (recData);
        }
        (1);
      }

      return ();
    }
    #endregion

    #region Send data to the serial port    /// &lt;summary&gt;
    /// Send data to the serial port    /// &lt;/summary&gt;
    /// <param name="cmd">Command to be sent</param>    /// <param name="SN">Serial number</param>    private void WritePort(Cmd cmd, byte SN)
    {
      //Send data      List&lt;byte&gt; sendData = ;
      sendData[1] = (byte);
      sendData[2] = SN;
      byte[] checkCode = (sendData, );
      (checkCode);
      if ( != null)
      {
        m_WaitResultMTList.Add();
      }
      m_SerialPort.Write((), 0, );
      (, true, ());
    }
    #endregion

    #region Send ACK message    /// &lt;summary&gt;
    /// Send ACK message    /// &lt;/summary&gt;
    /// <param name="SN">Serial number</param>    private void SendACK(byte SN)
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x40, 0x80 };
      WritePort(new Cmd(sendData), SN);
    }
    #endregion

    #region Init Init    /// &lt;summary&gt;
    /// Initialization    /// &lt;/summary&gt;
    /// <param name="com">Serial port number (example: COM1)</param>    public void Init(string com)
    {
      if (m_SerialPort == null)
      {
        m_SerialPort = new SerialPort(com, 9600, , 8, );
        m_SerialPort.ReadBufferSize = 1024;
        m_SerialPort.WriteBufferSize = 1024;
        m_SerialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);
      }

      if (!m_SerialPort.IsOpen)
      {
        m_SerialPort.Open();
      }

      GET_SETUP();
      CONTROL_IND(0x13, new byte[] { 0x00 }); //Initialization completion flag      GET_STATUS();

      SetDecimalPlaces(2); //Set the number of decimal places    }
    #endregion

    #region Close Connection    /// &lt;summary&gt;
    /// Close the connection    /// &lt;/summary&gt;
    public void Close()
    {
      m_SerialPort.Close();
    }
    #endregion

    #region Receive serial port data    /// &lt;summary&gt;
    /// Receive serial port data    /// &lt;/summary&gt;
    /// <param name="type">Message Type</param>    /// <param name="subtype">Message subtype</param>    public byte[] Receive(byte type, byte subtype)
    {
      return m_ReceiveDataCollection.Get(type, subtype);
    }

    /// &lt;summary&gt;
    /// Receive serial port data    /// &lt;/summary&gt;
    /// <param name="type">Message Type</param>    /// <param name="subtype">Message subtype</param>    public byte[] WaitReceive(byte type, byte subtype)
    {
      DateTime time = ;
      while (true)
      {
        byte[] receiveData = m_ReceiveDataCollection.Get(type, subtype);
        if (receiveData != null) return receiveData;

        if ((time).TotalMinutes &gt; 3) return null;

        (50);
      }
    }

    /// &lt;summary&gt;
    /// Receive serial port data    /// &lt;/summary&gt;
    /// <param name="type">Message Type</param>    public byte[] WaitReceive(byte type)
    {
      DateTime time = ;
      while (true)
      {
        byte[] receiveData = m_ReceiveDataCollection.Get(type);
        if (receiveData != null) return receiveData;

        if ((time).TotalMinutes &gt; 3) return null;

        (50);
      }
    }
    #endregion

    #region Determine whether the message is sent successfully    /// &lt;summary&gt;
    /// Determine whether the message is sent successfully    /// &lt;/summary&gt;
    public bool SendSuccess(byte type, byte subtype)
    {
      DateTime time = ;
      while (true)
      {
        if ((time).TotalMinutes &gt; 3)
        {
          return false;
        }
        byte[] ack = m_ReceiveDataCollection.Get(type, subtype);
        byte[] nak = m_ReceiveDataCollection.Get(type, subtype);
        if (ack != null) return true;
        if (nak != null) return false;

        (1);
      }
    }
    #endregion

  }
}

Part 2: Receive serial port data and respond to the freighter and send data to the freighter.

 

using System;
using ;
using ;
using ;
using ;
using ;
using ;

/*
  * VMC-> PC data reception, freighter event distribution
  */

namespace MachineJPDll
{
  partial class MachineJP
  {
    #region serialPort_DataReceived
    /// &lt;summary&gt;
    /// Method of receiving data events    /// &lt;/summary&gt;
    public void serialPort_DataReceived(object obj, SerialDataReceivedEventArgs args)
    {
      byte[] receiveData = ReadPort();

      if ((receiveData)) //Only process the correct data, discard the incorrect data and not process it      {
        (, false, receiveData);
        byte SN = (receiveData);
        MT mt = new MT(receiveData);

        #region polling (POLL)        if ( == 0x03)
        {
          if (m_CommandList.Count &gt; 0)
          {
            WritePort(m_CommandList[0], SN);
            m_CommandList.RemoveAt(0);
          }
          else
          {
            //Send ACK message            SendACK(SN);
          }
        }
        #endregion

        #region Send ACK message        if ((receiveData))
        {
          SendACK(SN); //Send ACK message        }
        #endregion

        #region VMC system parameters        if ( == 0x05)
        {
          m_ReceiveDataCollection.Add(, , receiveData);
        }
        #endregion

        #region ACK_RPT or NAK_RPT        if ( == 0x01 //ACK_RPT
          ||  == 0x02) //NAK_RPT
        {
          if (m_WaitResultMTList.Count &gt; 0)
          {
            m_ReceiveDataCollection.Add(m_WaitResultMTList[0].Type, m_WaitResultMTList[0].Subtype, receiveData);
            m_WaitResultMTList.RemoveAt(0);
          }
        }
        #endregion

        #region INFO_RPT Data Report        if ( == 0x11)
        {
          #region banknote information          if ( == 16)
          {
            m_ReceiveDataCollection.Add(, , receiveData);
          }
          #endregion

          #region Coin Message          if ( == 17)
          {
            m_ReceiveDataCollection.Add(, , receiveData);
          }
          #endregion

          #region User coin balance          if ( == 3)
          {
            m_ReceiveDataCollection.Add(, , receiveData);
          }
          #endregion
        }
        #endregion

        #region VENDOUT_RPT Shipping Report        if ( == 0x08)
        {
          m_ReceiveDataCollection.Add(, , receiveData);
        }
        #endregion

        #region STATUS_RPT VMC whole machine status report        if ( == 0x0D)
        {
          m_ReceiveDataCollection.Add(, , receiveData);
        }
        #endregion

        #region SALEPRICE_IND Set the current product price        if ( == 0x8E)
        {
          m_ReceiveDataCollection.Add(, , receiveData);
        }
        #endregion

        #region PAYIN_RPT VMC sends cash coin report to PC        if ( == 0x06)
        {
          m_ReceiveDataCollection.Add(, , receiveData);
        }
        #endregion

        #region PAYOUT_RPT Coin Output Report        if ( == 0x07)
        {
          m_ReceiveDataCollection.Add(, , receiveData);
        }
        #endregion

        #region COST_RPT VMC Deduction Report        if ( == 0x10)
        {
          m_ReceiveDataCollection.Add(, , receiveData);
        }
        #endregion

        #region ACTION_RPT Vending Machine Behavior Report        if ( == 0x0B)
        {
          m_ReceiveDataCollection.Add(, , receiveData);
        }
        #endregion

        #region HUODAO_RPT VMC cargo lane report        if ( == 0x0E)
        {
          m_ReceiveDataCollection.Add(, , receiveData);
        }
        #endregion
      }
      else //The received data has not been verified      {
        (, false, "Data exception", receiveData);
      }
    }
    #endregion

  }
}

Part 3: Freighter status, coin input, shipment and other interfaces

using System;
using ;
using ;
using ;
using ;
using ;
using ;
using ;

/*
  * PC->VMC data sending (not directly sending, just adding it to the sending list)
  */

namespace MachineJPDll
{
  partial class MachineJP
  {
    #region GET_SETUP
    /// &lt;summary&gt;
    /// PC notifies VMC to send VMC_SETUP    /// &lt;/summary&gt;
    public VmcSetup GET_SETUP()
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x40, 0x90 };
      m_CommandList.Add(new Cmd(sendData));

      byte[] receiveData = WaitReceive(0x05);
      if (receiveData != null)
      {
        return new VmcSetup(receiveData);
      }
      return null;
    }
    #endregion

    #region CONTROL_IND PC controls the vending machine to complete the corresponding actions    /// &lt;summary&gt;
    /// PC control VMC    /// &lt;/summary&gt;
    public bool CONTROL_IND(byte subtype, byte[] value)
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x41, 0x85 };
      (subtype);
      if (value != null &amp;&amp;  &gt; 0)
      {
        (value);
      }
      m_CommandList.Add(new Cmd(sendData, new MT(sendData)));

      return SendSuccess(0x85, subtype);
    }
    #endregion

    #region Set the number of decimal places    /// &lt;summary&gt;
    /// Set the number of decimal points    /// Used to notify VMC of the VMC, the ratio coefficient relationship between the amount data of both parties, and the PC will give each time it is started.    /// VMC issues a message of type=18. VMC needs to save the data permanently until it is then passed by the PC.    /// Updated.    /// Value range: 0, 1, 2 respectively representing units of numerals, angles, and divisions respectively    /// &lt;/summary&gt;
    public bool SetDecimalPlaces(int data)
    {
      return CONTROL_IND(18, new byte[] { (byte)data });
    }
    #endregion

    #region GET_STATUS PC notifies VMC to send STATUS_RPT    /// &lt;summary&gt;
    /// PC notifies VMC to send STATUS_RPT    /// &lt;/summary&gt;
    public StatusRpt GET_STATUS()
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x40, 0x86 };
      m_CommandList.Add(new Cmd(sendData));

      byte[] receiveData = WaitReceive(0x0D);
      if (receiveData != null)
      {
        return new StatusRpt(receiveData);
      }
      return null;
    }
    #endregion

    #region GET_INFO PC notifies VMC to send INFO_RPT    /// &lt;summary&gt;
    /// PC notifies VMC to send INFO_RPT    /// &lt;/summary&gt;
    public byte[] GET_INFO(byte subtype)
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x40, 0x8C };
      (subtype);
      m_CommandList.Add(new Cmd(sendData));

      return WaitReceive(0x11);
    }
    #endregion

    #region VENDOUT_IND Shipping    /// &lt;summary&gt;
    /// PC shipment instructions    /// &lt;/summary&gt;
    /// <param name="device">The box number of the vending machine, for example, cabinet 1 is 0x01, and so on</param>    /// <param name="method">method =1: VMC indicates shipment through the product ID. If the product ID does not exist, reply NAK_RPT method =2: VMC indicates shipment through the cargo lane ID. If the cargo lane ID does not exist, reply NAK_RPT</param>    /// <param name="sp_id_hd_id">sp_id: Indicate VMC shipment through product ID hd_id: Indicate VMC shipment through cargo lane ID</param>    /// <param name="type">If type=0, cost represents the amount of deduction for this shipment. If TYPE is not 0, COST must be 0</param>    /// <param name="cost">cost represents the amount of deduction for this shipment</param>    public VendoutRpt VENDOUT_IND(byte device, byte method, byte sp_id_hd_id, byte type, int cost)
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x41, 0x83 };
      (new byte[] { device, method, sp_id_hd_id, type });
      (CommonUtil.Int2ByteArray(cost, 2));
      m_CommandList.Add(new Cmd(sendData, new MT(sendData)));

      if (SendSuccess(0x83, 0x00))
      {
        byte[] receiveData = WaitReceive(0x08);
        if (receiveData != null)
        {
          return new VendoutRpt(receiveData);
        }
      }
      return null;
    }
    #endregion

    #region HUODAO_SET_IND Set the quantity of goods on the cargo route    /// &lt;summary&gt;
    /// PC notifies VMC, the quantity of goods corresponding to the current cargo lane, etc.    /// &lt;/summary&gt;
    /// <param name="device">represents the cabinet number</param>    /// <param name="huodao">zyxxxxxx "z" Fixed 0 "y" Fixed 0 "xxxxxx", indicating the product margin. If the product margin is greater than 63, it will be unified to 63</param>    public bool HUODAO_SET_IND(byte device, List&lt;int&gt; huodao)
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x41, 0x8F };
      (device);
      for (int i = 0; i &lt; ; i++)
      {
        if (huodao[i] &gt; 63)
        {
          huodao[i] = 63;
        }
      }
      (&lt;byte&gt;(a =&gt; (byte)a));
      m_CommandList.Add(new Cmd(sendData, new MT(sendData)));

      return SendSuccess(0x8F, 0x00);
    }
    #endregion

    #region SALEPRICE_IND Set the current product price    /// &lt;summary&gt;
    /// PC notifies VMC, current product price    /// &lt;/summary&gt;
    /// <param name="device">represents the cabinet number</param>    /// <param name="type"> means the way to set the unit price; Type = 0: To send the unit price according to the product ID, it can be sent in a longer range, with the maximum product types not exceeding 80; Type = 1: To send the unit price information of 80 cargo channels, fixedly send the unit price information of 80 cargo channels</param>    /// <param name="sp_price">Product price</param>    public bool SALEPRICE_IND(byte device, byte type, List&lt;int&gt; sp_price)
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x41, 0x8E };
      (device);
      (type);
      (sp_price.ConvertAll&lt;byte&gt;(a =&gt; (byte)a));
      m_CommandList.Add(new Cmd(sendData, new MT(sendData)));

      return SendSuccess(0x8E, 0x00);
    }
    #endregion

    #region PAYOUT_IND PC instructs VMC to issue coins    /// &lt;summary&gt;
    /// PC instructs VMC to issue coins    /// &lt;/summary&gt;
    /// <param name="device">Coin export device</param>    /// <param name="value">Total amount of coins issued this time</param>    /// <param name="type">Coin release type There is no need to understand the meaning of type, you only need to pass the type value back to the PC in PAYOUT_RPT after the coin release is completed</param>    public PayoutRpt PAYOUT_IND(PayoutType device, int value, byte type)
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x41, 0x89 };
      ((byte)device);
      (CommonUtil.Int2ByteArray(value, 2));
      (type);
      m_CommandList.Add(new Cmd(sendData, new MT(sendData)));

      if (SendSuccess(0x89, 0x00))
      {
        byte[] receiveData = WaitReceive(0x07);
        if (receiveData != null)
        {
          return new PayoutRpt(receiveData);
        }
      }
      return null;
    }
    #endregion

    #region COST_IND PC deduction instructions    /// &lt;summary&gt;
    /// PC deduction instructions    /// &lt;/summary&gt;
    /// <param name="device">device=0, deduct the amount from the total amount of coin input by the user; deduct the amount from the user's non-stage deposit amount (the banknotes should be lagged behind the banknotes as much as possible), see "Cash Deduction Order"</param>    /// <param name="value">Deduction amount</param>    /// <param name="type">VMC does not need to understand the meaning of type, just pass it back when reporting the corresponding COST_RPT</param>    public CostRpt COST_IND(byte device, int value, byte type)
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x41, 0x8B };
      (device);
      (CommonUtil.Int2ByteArray(value, 2));
      (type);
      m_CommandList.Add(new Cmd(sendData, new MT(sendData)));

      if (SendSuccess(0x8B, 0x00))
      {
        byte[] receiveData = WaitReceive(0x10);
        if (receiveData != null)
        {
          return new CostRpt(receiveData);
        }
      }
      return null;
    }
    #endregion

    #region GET_HUODAO PC notifies VMC to report HUODAO_RPT    /// &lt;summary&gt;
    /// PC notifies VMC to report HUODAO_RPT    /// &lt;/summary&gt;
    /// <param name="device">Case number</param>    public HuoDaoRpt GET_HUODAO(byte device)
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x40, 0x8A };
      (device);
      m_CommandList.Add(new Cmd(sendData));

      byte[] receiveData = WaitReceive(0x0E);
      if (receiveData != null)
      {
        return new HuoDaoRpt(receiveData);
      }
      return null;
    }
    #endregion

    #region SET_HUODAO PC sends configuration cargo route information    /// &lt;summary&gt;
    /// PC sends configuration cargo route information    /// &lt;/summary&gt;
    /// <param name="Cabinet">Cabinet number</param>    /// <param name="hd_no">cargo road logic number, decimal</param>    /// <param name="Hd_id">Product ID number</param>    /// <param name="Hd_count">Remaining volume of cargo lane</param>    /// <param name="Hd_price">Unit price of goods route</param>    /// <param name="Reserved">Reserved field VMC ignores this field, it is best for the PC to set this field to 0</param>    public bool SET_HUODAO(byte Cabinet, int hd_no, int Hd_id, int Hd_count, int Hd_price, int Reserved = 0)
    {
      List&lt;byte&gt; sendData = new List&lt;byte&gt;() { 0xE5, 0x00, 0x00, 0x41, 0x93 };
      (Cabinet);
      ((byte)hd_no);
      ((byte)Hd_id);
      ((byte)Hd_count);
      (CommonUtil.Int2ByteArray(Hd_price, 2));
      (CommonUtil.Int2ByteArray(Reserved, 2));
      m_CommandList.Add(new Cmd(sendData, new MT(sendData)));

      return SendSuccess(0x93, 0x00);
    }
    #endregion

    #region Turn on the paper coin device    /// &lt;summary&gt;
    /// Cash cash register module (bill device, coin device) switch    /// &lt;/summary&gt;
    /// <param name="open">true:Open, false:Off</param>    public bool CtrlCoinPaper(bool open)
    {
      if (open)
      {
        return CONTROL_IND(2, new byte[] { 1 });
      }
      else
      {
        return CONTROL_IND(2, new byte[] { 0 });
      }
    }
    #endregion

    #region Find change    /// &lt;summary&gt;
    /// Find change    /// Same as manually fiddling with physical change switch    /// &lt;/summary&gt;
    public bool MakeChange()
    {
      return CONTROL_IND(6, new byte[] { 0 });
    }
    #endregion

    #region Get coin information    /// &lt;summary&gt;
    /// Get coin information    /// &lt;/summary&gt;
    public InfoRpt_17 GetCoinInfo()
    {
      return new InfoRpt_17(GET_INFO(17));
    }
    #endregion

    #region Get banknote information    /// &lt;summary&gt;
    /// Get banknote information    /// &lt;/summary&gt;
    public InfoRpt_16 GetPaperInfo()
    {
      return new InfoRpt_16(GET_INFO(16));
    }
    #endregion

    #region Get cash coin report    /// &lt;summary&gt;
    /// Get cash coin report    /// &lt;/summary&gt;
    public PayinRpt GetPayinRpt()
    {
      byte[] receiveData = Receive(0x06, 0x00);
      if (receiveData != null)
      {
        return new PayinRpt(receiveData);
      }
      return null;
    }
    #endregion

    #region Get the user's coin balance    /// &lt;summary&gt;
    /// Obtain the user's coin balance    /// &lt;/summary&gt;
    public InfoRpt_3 GetRemaiderAmount()
    {
      byte[] receiveData = WaitReceive(0x11, 0x003);
      if (receiveData != null)
      {
        return new InfoRpt_3(receiveData);
      }
      return null;
    }
    #endregion

  }
}

ReceiveDataCollection class and ReceiveData class:

 

using System;
using ;
using ;
using ;

namespace 
{
  /// &lt;summary&gt;
  /// Data received from the serial port (data has been verified)  /// &lt;/summary&gt;
  public class ReceiveData
  {
    /// &lt;summary&gt;
    /// Data received from the serial port (data has been verified)    /// &lt;/summary&gt;
    public byte[] Data { get; set; }
    /// &lt;summary&gt;
    /// Time added to the collection ReceiveDataCollection    /// &lt;/summary&gt;
    public DateTime AddTime { get; set; }
    /// &lt;summary&gt;
    /// Message type    /// &lt;/summary&gt;
    public byte Type { get; set; }
    /// &lt;summary&gt;
    /// Message subtype    /// &lt;/summary&gt;
    public byte Subtype { get; set; }

    /// &lt;summary&gt;
    /// Data received from the serial port (data has been verified)    /// &lt;/summary&gt;
    /// <param name="type">Message Type</param>    /// <param name="subtype">Message subtype</param>    /// <param name="data">Data received from the serial port (data has been verified)</param>    /// <param name="addTime">Time to add to the collection ReceiveDataCollection</param>    public ReceiveData(byte type, byte subtype, byte[] data, DateTime addTime)
    {
       = type;
       = subtype;
       = data;
       = addTime;
    }
  }
}
using System;
using ;
using ;
using ;

namespace 
{
  /// &lt;summary&gt;
  /// Collection of data received from the serial port (data has been verified)  /// &lt;/summary&gt;
  public class ReceiveDataCollection
  {
    /// &lt;summary&gt;
    /// Collection of data received from the serial port (data has been verified)    /// &lt;/summary&gt;
    private List&lt;ReceiveData&gt; m_ReceiveDataList = new List&lt;ReceiveData&gt;();
    /// &lt;summary&gt;
    /// Data expiration time    /// &lt;/summary&gt;
    private int m_Timeout = 3;
    private static object _lock = new object();

    /// &lt;summary&gt;
    /// Add to collection    /// &lt;/summary&gt;
    /// <param name="type">Message Type</param>    /// <param name="subtype">Message subtype</param>    /// <param name="data">Data received from the serial port (data has been verified)</param>    public void Add(byte type, byte subtype, byte[] data)
    {
      lock (_lock)
      {
        ReceiveData receiveData = new ReceiveData(type, subtype, data, );
        m_ReceiveDataList.Add(receiveData);
        for (int i = m_ReceiveDataList.Count - 1; i &gt;= 0; i--)
        {
          if ((m_ReceiveDataList[i].AddTime).TotalMinutes &gt; m_Timeout)
          {
            m_ReceiveDataList.RemoveAt(i);
          }
        }
      }
    }

    /// &lt;summary&gt;
    /// Get the data received by the serial port from the collection (the data has been verified)    /// &lt;/summary&gt;
    /// <param name="type">Message Type</param>    /// <param name="subtype">Message subtype</param>    /// <returns>Data received from the serial port (data has been verified)</returns>    public byte[] Get(byte type, byte subtype)
    {
      lock (_lock)
      {
        ReceiveData receiveData = null;
        for (int i = 0; i &lt; m_ReceiveDataList.Count; i++)
        {
          if (m_ReceiveDataList[i].Type == type &amp;&amp; m_ReceiveDataList[i].Subtype == subtype)
          {
            receiveData = m_ReceiveDataList[i];
            m_ReceiveDataList.RemoveAt(i);
            return ;
          }
        }
        return null;
      }
    }

    /// &lt;summary&gt;
    /// Get the data received by the serial port from the collection (the data has been verified)    /// &lt;/summary&gt;
    /// <param name="type">Message Type</param>    /// <returns>Data received from the serial port (data has been verified)</returns>    public byte[] Get(byte type)
    {
      lock (_lock)
      {
        ReceiveData receiveData = null;
        for (int i = 0; i &lt; m_ReceiveDataList.Count; i++)
        {
          if (m_ReceiveDataList[i].Type == type)
          {
            receiveData = m_ReceiveDataList[i];
            m_ReceiveDataList.RemoveAt(i);
            return ;
          }
        }
        return null;
      }
    }
  }
}

Models:

StatusRpt class:

using System;
using ;
using ;
using ;
using MachineJPDll;
using ;
using ;

namespace 
{
  /// &lt;summary&gt;
  /// VMC status report  /// &lt;/summary&gt;
  public class StatusRpt
  {
    /// &lt;summary&gt;
    /// Verified data read from the serial port    /// &lt;/summary&gt;
    private byte[] m_data;

    /// &lt;summary&gt;
    /// VMC status report    /// &lt;/summary&gt;
    /// <param name="data">Verified data read from the serial port</param>    public StatusRpt(byte[] data)
    {
      m_data = data;
    }

    public override string ToString()
    {
      StringBuilder sb = new StringBuilder();
      ("Shipping detection equipment status:{0}\r\n", check_st.ToString());
      ("Banknote status:{0}\r\n", bv_st.ToString());
      ("Coin stat:{0}\r\n", cc_st.ToString());
      ("VMCstate:{0}\r\n", vmc_st.ToString());
      ("展示位state:{0} {1} {2} {3}\r\n", pos_st[0].ToString(), pos_st[1].ToString(), pos_st[2].ToString(), pos_st[3].ToString());
      ("Total amount of change available in the machine(Including coins and banknotes):{0}\r\n", ());
      ("Warehouse1Warehouse2Warehouse3Warehouse4temperature:{0} {1} {2} {3}\r\n", (), (), (), ());
      ("Warehousestate设置值:{0} {1} {2} {3}\r\n", tem_st[0].ToString(), tem_st[1].ToString(), tem_st[2].ToString(), tem_st[3].ToString());
      if (this.Automatic coin refund == 255)
      {
        ("Automatic Coin Refund Time: Never Automatic Coin Refund\r\n");
      }
      else
      {
        ("Automatic coin refund时间:{0}\r\n", Automatic coin refund.ToString());
      }
      ("Change margin(1#--6#): {0} {1} {2} {3} {4} {5}\r\n", this. Change margin 1, this. Change margin 2, this. Change margin 3, this. Change margin 4, this. Change margin 5, this. Change margin 6);
      return ();
    }

    /// &lt;summary&gt;
    /// Delivery equipment status    /// &lt;/summary&gt;
    public CheckSt check_st
    {
      get
      {
        byte val = m_data[5];
        return (CheckSt)(val, 0, 2);
      }
    }

    /// &lt;summary&gt;
    /// Status of banknote    /// &lt;/summary&gt;
    public DeviceSt bv_st
    {
      get
      {
        byte val = m_data[5];
        return (DeviceSt)(val, 2, 2);
      }
    }

    /// &lt;summary&gt;
    /// Coin device status    /// &lt;/summary&gt;
    public DeviceSt cc_st
    {
      get
      {
        byte val = m_data[5];
        return (DeviceSt)(val, 4, 2);
      }
    }

    /// &lt;summary&gt;
    /// VMC status    /// &lt;/summary&gt;
    public VmcSt vmc_st
    {
      get
      {
        byte val = m_data[5];
        return (VmcSt)(val, 6, 2);
      }
    }

    /// &lt;summary&gt;
    /// Showcase status    /// &lt;/summary&gt;
    public List&lt;DeviceSt&gt; pos_st
    {
      get
      {
        List&lt;DeviceSt&gt; deviceStList = new List&lt;DeviceSt&gt;();

        byte val = m_data[6];
        for (int i = 0; i &lt; 4; i++)
        {
          DeviceSt deviceSt = (DeviceSt)(val, i * 2, 2);
          (deviceSt);
        }

        return deviceStList;
      }
    }

    /// &lt;summary&gt;
    /// The total amount of change available in the machine (including coins and banknotes)    /// &lt;/summary&gt;
    public int change
    {
      get
      {
        return CommonUtil.ByteArray2Int(m_data, 7, 2);
      }
    }

    /// &lt;summary&gt;
    /// The temperature of the warehouse is 1, 8-digit signed number, this temperature is obtained by the sensor in the warehouse, unit: ℃    /// &lt;/summary&gt;
    public TemSub tem1
    {
      get
      {
        return new TemSub(m_data[9]);
      }
    }

    /// &lt;summary&gt;
    /// The temperature of the warehouse is 2, 8-digit signed number, this temperature is obtained by the sensor in the warehouse, unit: ℃    /// &lt;/summary&gt;
    public TemSub tem2
    {
      get
      {
        return new TemSub(m_data[10]);
      }
    }

    /// &lt;summary&gt;
    /// The temperature of the warehouse is 3, 8 signed numbers, this temperature is obtained by the sensor in the warehouse, unit: ℃    /// &lt;/summary&gt;
    public TemSub tem3
    {
      get
      {
        return new TemSub(m_data[11]);
      }
    }

    /// &lt;summary&gt;
    /// The temperature of the warehouse is 4, 8-digit signed number, this temperature is obtained by the sensor in the warehouse, unit: ℃    /// &lt;/summary&gt;
    public TemSub tem4
    {
      get
      {
        return new TemSub(m_data[12]);
      }
    }

    /// &lt;summary&gt;
    /// The warehouse status setting value, a total of 4 warehouses are supported    /// &lt;/summary&gt;
    public List&lt;TemSt&gt; tem_st
    {
      get
      {
        List&lt;TemSt&gt; temStList = new List&lt;TemSt&gt;();
        for (int i = 0; i &lt; 4; i++)
        {
          TemSt temSt = (TemSt)(m_data[13], i * 2, 2);
          (temSt);
        }
        return temStList;
      }
    }

    /// &lt;summary&gt;
    /// Automatic currency refund time.    /// 0: It means that the coin will be automatically returned immediately after the product is shipped    /// 255: means that the coins will never be automatically returned    /// 1-254: It indicates the automatic refund time after the product is shipped (unit: seconds)    /// &lt;/summary&gt;
    public int Automatic coin refund
    {
      get
      {
        return m_data[14];
      }
    }

    /// &lt;summary&gt;
    /// The change margin "Refund 1#"…"Refund 6#", corresponding to the "Refund INFO_RPT.type=17" respectively /// 1#"…The number of change for each currency in "Refresh 6#"; /// * The maximum change volume is 255, and it is reported as 255 if it exceeds 255;    /// * The unit of change is "piece", which represents the number of change coins.    /// &lt;/summary&gt;
    public int Change margin1
    {
      get
      {
        return m_data[15];
      }
    }

    /// &lt;summary&gt;
    /// The change margin "Refund 1#"…"Refund 6#", corresponding to the "Refund INFO_RPT.type=17" respectively /// 1#"…The number of change for each currency in "Refresh 6#"; /// * The maximum change volume is 255, and it is reported as 255 if it exceeds 255;    /// * The unit of change is "piece", which represents the number of change coins.    /// &lt;/summary&gt;
    public int Change margin2
    {
      get
      {
        return m_data[16];
      }
    }

    /// &lt;summary&gt;
    /// The change margin "Refund 1#"…"Refund 6#", corresponding to the "Refund INFO_RPT.type=17" respectively /// 1#"…The number of change for each currency in "Refresh 6#"; /// * The maximum change volume is 255, and it is reported as 255 if it exceeds 255;    /// * The unit of change is "piece", which represents the number of change coins.    /// &lt;/summary&gt;
    public int Change margin3
    {
      get
      {
        return m_data[17];
      }
    }

    /// &lt;summary&gt;
    /// The change margin "Refund 1#"…"Refund 6#", corresponding to the "Refund INFO_RPT.type=17" respectively /// 1#"…The number of change for each currency in "Refresh 6#"; /// * The maximum change volume is 255, and it is reported as 255 if it exceeds 255;    /// * The unit of change is "piece", which represents the number of change coins.    /// &lt;/summary&gt;
    public int Change margin4
    {
      get
      {
        return m_data[18];
      }
    }

    /// &lt;summary&gt;
    /// The change margin "Refund 1#"…"Refund 6#", corresponding to the "Refund INFO_RPT.type=17" respectively /// 1#"…The number of change for each currency in "Refresh 6#"; /// * The maximum change volume is 255, and it is reported as 255 if it exceeds 255;    /// * The unit of change is "piece", which represents the number of change coins.    /// &lt;/summary&gt;
    public int Change margin5
    {
      get
      {
        return m_data[19];
      }
    }

    /// &lt;summary&gt;
    /// The change margin "Refund 1#"…"Refund 6#", corresponding to the "Refund INFO_RPT.type=17" respectively /// 1#"…The number of change for each currency in "Refresh 6#"; /// * The maximum change volume is 255, and it is reported as 255 if it exceeds 255;    /// * The unit of change is "piece", which represents the number of change coins.    /// &lt;/summary&gt;
    public int Change margin6
    {
      get
      {
        return m_data[20];
      }
    }

  }
}

The above is the code for C# to implement the vending machine interface. Friends who need it can learn it.