RS232 module drops characters using Raptor board

The RS232 module frequently drops one or more characters in a received packet of data. I tried both 9600 baud and 38400 baud. I am sending a byte string to a Programmable Logic Controller and waiting for the response packet.

Here is an example trace log:

16:13:47.973 PLC SendData(): 01010C0000083E9C : >
16:13:48.040 PLC Data received: 88 :
16:13:58.081 PLC SendData() Error: Timeout waiting for response from PLC
16:13:59.083 PLC SendData(): 01010C0000083E9C : >
16:13:59.145 PLC Data received: 010101 :
16:13:59.149 PLC Data received: 005188 : Q
16:13:59.152 PLC data received: 010101005188 : Q

The first received byte 88 hex, is the last byte of a packet, most of the bytes are missing. The second try shows the full packet expected to be received.

This is the code:




        /// <summary>
        /// Read the input coil status byte from the PLC, and return an error code on failure
        /// </summary>
        /// <param name="CoilStatusBits"></param>
        /// <param name="ErrorCode"></param>
        /// <returns></returns>
        public bool ReadCoilStatus(out byte CoilStatusBits, out byte ErrorCode )
        {
            CoilStatusBits = 0;
            bool rv;
            
            /*
            EXAMPLE - Read Coil Status command
            0   Slave Address 11
            1   Function 01
            2   Starting Address Hi 00
            3   Starting Address Lo 13
            4   No. of Points Hi 00
            5   No. of Points Lo 25
            6-7 Error Check (LRC or CRC) --
            Response:
            0   Slave Address 11
            1   Function 01
            2   Byte Count 05
            3   Data (Coils 27-20) CD
            4   Data (Coils 35-28) 6B
            5   Data (Coils 43-36) B2
            6   Data (Coils 51-44) 0E
            7   Data (Coils 56-52) 1B
             */
            rv = SendData(10000, 3, true, out ErrorCode, MakeReadCoilStatusPacket(PLCAddress, READ_COIL_STATUS, InputCoilsAddress, 8));
            if ( rv == true)
            {
                CoilStatusBits = RxDataBuf[3];
            }
            return rv;
        }



       /// <summary>
        /// Build the Modbus command packet for reading input coil status bytge
        /// </summary>
        /// <param name="SLAVEADDR"></param>
        /// <param name="FUNCTIONCODE"></param>
        /// <param name="STARTINGCOIL"></param>
        /// <param name="NUMCOILS"></param>
        /// <returns></returns>
        private byte[] MakeReadCoilStatusPacket(byte SLAVEADDR, byte FUNCTIONCODE, ushort STARTINGCOIL, ushort NUMCOILS)
        {
            byte[] Packet = new byte[8];
            byte CRC_Hi, CRC_Lo;

            Packet[0] = SLAVEADDR;
            Packet[1] = FUNCTIONCODE;
            Packet[2] = (byte)(STARTINGCOIL >> 8);
            Packet[3] = (byte)(STARTINGCOIL & 0x00FF);
            Packet[4] = (byte)(NUMCOILS >> 8);
            Packet[5] = (byte)(NUMCOILS & 0x00FF);
            CalcCRC(Packet, 6, out  CRC_Hi, out CRC_Lo);
            Packet[6] = CRC_Hi;
            Packet[7] = CRC_Lo;
            return Packet;
        }




        /// <summary>
        ///Send a variable length byte packet to the rs232 port for the PLC
        ///Wait for a byte string response from the PLC
        ///After sending a command to the PLC, the program must wait since the PLC
        ///will not accept any other commands while executing the previous command. 
        /// </summary>
        /// <param name="ms_time">time in milliseconds. Used for the AutoResetEvent object wait</param>
        /// <param name="ErrorCode">Error code received from PLC in case of exception</param>
        /// <param name="sendData">variable length packet of bytes to transmit</param>
        /// <returns>true if packet sent without error</returns>
        private bool SendData(int ms_time, int _RxDataQueueSizeExpected, bool _ExpectByteCount,  out byte ErrorCode, params byte[] sendData)
        {
            bool rv = false;
            ErrorCode = PLCExceptionCodes.RECEIVE_SUCCESS;
            try
            {
                RxDataCount = 0;
                if (rs232PLC.serialPort.IsOpen)
                {
                    //Enter a critical section - only one thread at a time. Don't release until transaction is finished
                    lock (SendDataLock)
                    {
                        lock (RxDataQueue.SyncRoot)
                        {
                            //thread-safe initialize the Queue of data received from the PLC reader
                            RxDataQueue.Clear();
                            RxDataEvent.Reset();
                            RxDataQueueSizeExpected = _RxDataQueueSizeExpected;
                            ExpectByteCount = _ExpectByteCount;
                        }
                        if (LoggingLevel > 0)
                        {
                            Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " SendData(): " 
                                    + KioskUtilities.BytesToHexString(sendData, sendData.Length) 
                                    + " : " + KioskUtilities.BytesToAsciiString(sendData, sendData.Length));
                        }
                        //Send the command to the PLC
                        if (IdleTimer.IsRunning)
                        {
                            /// Wait for the end of the serial port idle time between sending packets required by ModBus
                            IdleTimerEvent.WaitOne(PacketIdleTimeMs + 20, false);
                        }
                        rs232PLC.serialPort.Write(sendData);
                        rs232PLC.serialPort.Flush();
                        IdleTimer.Start();  //Mark the start of the serial port idle time between sending packets required by ModBus

                        //wait for a certain number of bytes to appear in the PLC reader data Queue
                        rv = RxDataEvent.WaitOne(ms_time + 100, false);
                        if (rv)
                        {
                            lock (RxDataQueue.SyncRoot)
                            {
                                //thread-safe capture of the received byte array from the PLC reader receive Queue
                                RxDataQueue.CopyTo(RxDataBuf, 0);
                                RxDataCount = RxDataQueue.Count;
                                ErrorCode = PLCExceptionCodes.RECEIVE_SUCCESS;
                            }
                            if (LoggingLevel > 0)
                            {
                                Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " data received: "
                                    + KioskUtilities.BytesToHexString(RxDataBuf, RxDataCount)
                                    + " : " + KioskUtilities.BytesToAsciiString(RxDataBuf, RxDataCount));
                            }

                            // Test for exception response
                            //Byte Contents Example
                            //0 Slave Address 0A
                            //1 Function Code 81  - Most significant bit (7) of byte is true if error happened
                            //2 Exception Code 02
                            //3 CRC HI
                            //4 CRC LO
                            if ( ( RxDataCount >= 3 ) && ( (RxDataBuf[1] & 0x80) == 0x80) )
                            {
                                rv = false;
                                ErrorCode = RxDataBuf[2];
                                Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code  
                                        + " SendData() Error:  PLC reported exception " + GetExceptionCodeString(ErrorCode));
                            }
                        }
                        else
                        {
                            ErrorCode = PLCExceptionCodes.RECEIVE_TIMEOUT;
                            Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " SendData() Error:  Timeout waiting for response from PLC");
                        }
                    }
                }
                else
                {
                    Debug.Print(  code + " SendData() serial port is not open");
                    rs232PLC.serialPort.Open();
                    rv = false;
                }
            }
            catch (GTI.Serial.PortNotOpenException ex)
            {
                KioskUtilities.LogException(  code + " SendData() serial port not open exception ", ex);
                rs232PLC.serialPort.Open();
                rv = false;
            }
            return rv;
        }



        /// <summary>
        /// This is the function that will be called by the rs232 module when data is available on the serial port from the PLC.
        /// </summary>
        /// <param name="sender">Object that sent called this function</param>
        /// <param name="data">serial data received</param>
        private void PLC_DataReceived(GT.Interfaces.Serial sender, System.IO.Ports.SerialData data)
        {
            try
            {
                lock (RxDataQueue.SyncRoot)
                {
                    // the BytesToRead property tells us how many bytes are waiting in a buffer.
                    rs232Count = rs232PLC.serialPort.BytesToRead;
                    if (rs232Count > MaxRxDataQueueSize)
                    {
                        rs232Count = MaxRxDataQueueSize;    //Don't overflow the allocated rs232Data byte array size
                        if (LoggingLevel > 0)
                        {
                            Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " PLC_DataReceived() error - rs232Data array is full, bytes discarded");
                        }
                    }
                    // Now read the serial port data into the rs232Data buffer, starting at the first byte.
                    rs232PLC.serialPort.Read(rs232Data, 0, rs232Count);
                    if (LoggingLevel > 1)
                    {
                        Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " Data received: "
                            + KioskUtilities.BytesToHexString(rs232Data, rs232Count)
                            + " : " + KioskUtilities.BytesToAsciiString(rs232Data, rs232Count));
                    }
                    OnDL05PLCDataReceived(new DL05PLCEventArgs(rs232Data, rs232Count));
                    // Accumulate received characters in the RxDataQueue, but prevent the Queue size from expanding forever
                    if (RxDataQueue.Count < MaxRxDataQueueSize)
                    {
                        for (int i = 0; i < rs232Count; i++)
                        {
                            if (RxDataQueue.Count < MaxRxDataQueueSize)
                            {
                                RxDataQueue.Enqueue(rs232Data[i]);
                            }
                            else
                            {
                                if (LoggingLevel > 0)
                                {
                                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " RxDataQueue.Count = " + RxDataQueue.Count.ToString() + " RxDataQueue is full");
                                }
                                break;
                            }
                        }
                    }

                    //compute and wait for the expected number of bytes
                    if (RxDataQueue.Count >= 3)
                    {
                        // Test for exception response
                        RxDataQueue.CopyTo(RxQDataBuf, 0);
                        if ((RxQDataBuf[1] & 0x80) == 0x80)
                        {
                            //PLC reports an exception in its response
                            //Byte Contents Example
                            //0 Slave Address 0A
                            //1 Function 81
                            //2 Exception Code 02
                            //3 CRC HI
                            //4 CRC LO
                            RxDataQueueSizeExpected = 5;
                        }
                        else
                        {
                            //PLC reports no error
                            if (ExpectByteCount)
                            {
                                //Expect to receive: header size (3 bytes) plus data length (variable) plus CRC size (2 bytes)
                                RxDataQueueSizeExpected = 3 + RxQDataBuf[2] + 2;
                            }
                        }
                    }
                    if (RxDataQueue.Count >= RxDataQueueSizeExpected)
                    {
                        RxDataEvent.Set();
                    }
                }
            }
            catch (GTI.Serial.PortNotOpenException ex2)
            {
                KioskUtilities.LogException(code + " PLC_DataReceived() port not open exception ", ex2);
            }
            catch (Exception ex1)
            {
                KioskUtilities.LogException(code + " PLC_DataReceived() exception ", ex1);
                KioskUtilities.Reboot();
            }
        }



Hi,
your application is rather complex and it would need some time to analyse it in detail.
Since I used the serial port in many applications and did not see lost data, I would propose that you first try the structure of working examples with serial communication. Some months ago we had some posts concerning similar issues.

https://www.ghielectronics.com/community/forum/topic?id=14927

https://www.ghielectronics.com/community/forum/topic?id=14821&page=2 #17

the important thing is to have serial read and serial write in different threads.

Cheers
Roland

How heavy a load is your Raptor under?

In the recent SDK update there is a reference to lost UART data here in the notes.

https://www.ghielectronics.com/support/netmf/sdk/19/netmf-and-gadgeteer-package-2014-r2

Issues:
-TCP connections can fail after the first time when using MFDeploy.
-UART loses data during heavy loads.

I do believe that the read and write are done on different threads. A thread is calling the SendData() function once per second that writes to the serial port, and waits for a response. The RS232 Gadgeteer module calls my event handler function PLC_DataReceived() when some bytes are received. PLC_DataReceived() puts the received characters into a queue and exits. The SendData() function is waiting for the correct number of bytes to accumulate in the queue.

The other processing going on is every 30 seconds an HTTP request is made on the Ethernet module to a web server, and the KP16 keypad is scanned for a key press every 10 milliseconds and key pad switch de-bouncing is performed. Here is the code for scanning the KP16, called every 10 ms:


       private bool IsKeyPressed(out char key)
        {
            key = (char)0;
            //This is for testing KP16 keypad
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.A))
            {
                key = 'A';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.B))
            {
                key = 'B';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.C))
            {
                key = 'C';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.D))
            {
                key = 'D';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Pound))
            {
                key = '#';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Star))
            {
                key = '*';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Zero))
            {
                key = '0';
                return true;
            } 
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.One))
            {
                key = '1';
                return true;  
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Two))
            {
                key = '2';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Three))
            {
                key = '3';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Four))
            {
                key = '4';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Five))
            {
                key = '5';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Six))
            {
                key = '6';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Seven))
            {
                key = '7';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Eight))
            {
                key  = '8';
                return true;
            }
            if (keypad_KP16.IsKeyPressed(GTM.GHIElectronics.Keypad_KP16.Key.Nine))
            {
                key = '9';
                return true;
            }
            return false;
        }

Hi,
in NETMF it is different to .NET. Your program runs in one thread unless you explicitly open a second thread with.e.g.


        writerThread = new Thread(new ThreadStart(runWriterThread));
        writerThread.Start();
        

        void runWriterThread()
        {
            while (true)
            {
                rs232.serialPort.WriteLine("I am a test string");
                Thread.Sleep(100);
            }
        }

Is your program a gadgeteer application?
It would be helpful to see the program.started method and the declaration of the global variables.

Edit: I did not find a tutorial on this matter at the moment but perhaps the Blogs of Colin Miller can be useful

http://blogs.msdn.com/b/netmfteam/archive/2011/01/17/threads-and-thread-priorities-in-netmf.aspx

Yes, it is a Gadgeteer application.
Here is the Program.generated.cs, Program.cs
The files PrototypeDevice,cs, BusinessLogic.cs, and SmartTruckPLC.cs shows how SendData() and PLC_DataReceived are being called.


//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.18444
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace SmartTruckRaptor {
    using Gadgeteer;
    using GTM = Gadgeteer.Modules;
    
    
    public partial class Program : Gadgeteer.Program {
        
        /// <summary>The RS232 module using socket 12 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.RS232 rs232;
        
        /// <summary>The RS232 module using socket 1 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.RS232 rs2322;
        
        /// <summary>The RS232 module using socket 10 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.RS232 rs2323;
        
        /// <summary>The RS232 module using socket 4 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.RS232 rs2324;
        
        /// <summary>The Ethernet_ENC28  (Premium) module using socket 3 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.Ethernet_ENC28 ethernet_ENC28;
        
        /// <summary>The Display T43 module using sockets 15, 16, 17 and 14 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.Display_T43 display_T43;
        
        /// <summary>The Keypad KP16 module using socket 18 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.Keypad_KP16 keypad_KP16;
        
        /// <summary>The Relay X1 module using socket 13 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.Relay_X1 relay_X1;
        
        /// <summary>The UsbClientDP module using socket 8 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.UsbClientDP usbClientDP;
        
        /// <summary>The TemperatureHumidity module using socket 2 of the mainboard.</summary>
        private Gadgeteer.Modules.Seeed.TemperatureHumidity temperatureHumidity;
        
        /// <summary>The RS485 module using socket 11 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.RS485 rs485;
        
        /// <summary>The Extender module using socket 5 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.Extender extender;
        
        /// <summary>The SDCard module using socket 9 of the mainboard.</summary>
        private Gadgeteer.Modules.GHIElectronics.SDCard sdCard;
        
        /// <summary>This property provides access to the Mainboard API. This is normally not necessary for an end user program.</summary>
        protected new static GHIElectronics.Gadgeteer.FEZRaptor Mainboard {
            get {
                return ((GHIElectronics.Gadgeteer.FEZRaptor)(Gadgeteer.Program.Mainboard));
            }
            set {
                Gadgeteer.Program.Mainboard = value;
            }
        }
        
        /// <summary>This method runs automatically when the device is powered, and calls ProgramStarted.</summary>
        public static void Main() {
            // Important to initialize the Mainboard first
            Program.Mainboard = new GHIElectronics.Gadgeteer.FEZRaptor();
            Program p = new Program();
            p.InitializeModules();
            p.ProgramStarted();
            // Starts Dispatcher
            p.Run();
        }
        
        private void InitializeModules() {
            this.rs232 = new GTM.GHIElectronics.RS232(12);
            this.rs2322 = new GTM.GHIElectronics.RS232(1);
            this.rs2323 = new GTM.GHIElectronics.RS232(10);
            this.rs2324 = new GTM.GHIElectronics.RS232(4);
            this.ethernet_ENC28 = new GTM.GHIElectronics.Ethernet_ENC28(3);
            this.display_T43 = new GTM.GHIElectronics.Display_T43(15, 16, 17, 14);
            this.keypad_KP16 = new GTM.GHIElectronics.Keypad_KP16(18);
            this.relay_X1 = new GTM.GHIElectronics.Relay_X1(13);
            this.usbClientDP = new GTM.GHIElectronics.UsbClientDP(8);
            this.temperatureHumidity = new GTM.Seeed.TemperatureHumidity(2);
            this.rs485 = new GTM.GHIElectronics.RS485(11);
            this.extender = new GTM.GHIElectronics.Extender(5);
            this.sdCard = new GTM.GHIElectronics.SDCard(9);
        }
    }
}



using System;
using System.Collections;
using System.Threading;
using System.IO;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Presentation.Shapes;
using Microsoft.SPOT.Touch;
using Microsoft.SPOT.IO;
using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.Seeed;
using Gadgeteer.Modules.GHIElectronics;
using GHI.Premium.IO;
using Configuration;
using TouchScreenInterface;
using KioskUtility;


namespace SmartTruckRaptor
{
    public partial class Program
    {
        AutoResetEvent StartEvent;
        int StartTimer;
        GT.Timer DelayStartTimer;
        KioskTouchScreen TouchScreenServer;

        /// <summary>
        ///This method is run when the mainboard is powered up or reset.    
        /// </summary>
        void ProgramStarted()
        {
            /*******************************************************************************************
            Modules added in the Program.gadgeteer designer view are used by typing 
            their name followed by a period, e.g.  button.  or  camera.
            
            Many modules generate useful events. Type +=<tab><tab> to add a handler to an event, e.g.:
                button.ButtonPressed +=<tab><tab>
            
            If you want to do something periodically, use a GT.Timer and handle its Tick event, e.g.:
                GT.Timer timer = new GT.Timer(1000); // every second (1000ms)
                timer.Tick +=<tab><tab>
                timer.Start();
            *******************************************************************************************/

            // Use Debug.Print to show messages in Visual Studio's "Output" window during debugging.

            try
            {
                KioskUtilities.StartWatchDogKicker(this, Mainboard);

                StartEvent = new AutoResetEvent(false);
                StartTimer = 3;
                DelayStartTimer = new GT.Timer(1000, GT.Timer.BehaviorType.RunContinuously); // every second (1000ms)
                DelayStartTimer.Tick += new GT.Timer.TickEventHandler(DelayStartTimer_Tick);
                DelayStartTimer.Start();

                ThreadStart starter = new ThreadStart(ProcessCommandsLoop);
                Thread th = new Thread(starter);
                th.Start();

                //Initialize the Glide touch screen user interface object
                TouchScreenServer = new KioskTouchScreen();
            }
            catch (Exception ex)
            {
                KioskUtilities.LogException("ProgramStarted() exception", ex);
                KioskUtilities.Reboot();
            }
        }

        /// <summary>
        /// Count down X seconds, then set the StartEvent to start the program
        /// Needed to give debugger time to attach
        /// </summary>
        /// <param name="timer"></param>
        void DelayStartTimer_Tick(GT.Timer timer)
        {
            if (StartTimer > 0)
            {
                Debug.Print("Waiting for debugger - time = " + StartTimer.ToString());
                --StartTimer;
            }
            else
            {
                StartEvent.Set();
                DelayStartTimer.Stop();
            }
        }

        /// <summary>
        /// Endless loop to poll for commands from the DeviceHive server. This must be in a thread so that
        /// the main thread is not blocked and the GHI dispatcher runs to service Gadgeteer devices.
        /// </summary>
        void ProcessCommandsLoop()
        {
            try
            {
                bool rv = false;
                //wait for Gadgeteer system to start up and debugger to attach if needed
                StartEvent.WaitOne();
                PrototypeDevice Cobra = new PrototypeDevice(ethernet_ENC28, rs232, rs2322, rs2323, rs2324, rs485, relay_X1, keypad_KP16, display_T43, sdCard, temperatureHumidity, TouchScreenServer);
                if (Cobra.Init())
                {
                    //Cobra ethernet TCP/IP is started
                    while (true)
                    {
                        //keep trying to connect until connected
                        while (!rv)
                        {
                            rv = Cobra.Connect();
                        }
                        //keep processing commands until error
                        while (rv)
                        {
                            rv = Cobra.ProcessCommands();
                        }
                    }
                }
                else
                {
                    Debug.Print("ProcessCommandsLoop() error - Cobra Ethernet cannot be started");
                }
            }
            catch (Exception ex)
            {
                //This is the top-level exception catch
                KioskUtilities.LogException("ProcessCommandsLoop() exception", ex);
                KioskUtilities.Reboot();
            }
            finally
            {
                Debug.Print("Command processing has exited");
                KioskUtilities.Reboot();
            }
        }

    }
}

File PrototypeDevice.cs:


using GHI.Premium.Net;
using GHI.Premium.System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Net.NetworkInformation;
using System;
using System.Threading;
using System.Collections;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using GTME = Gadgeteer.Modules.GHIElectronics;
using Gadgeteer.Networking;
using GHINET = GHI.Premium.Net;
//using GHI.Hardware.G120;  //Used by Cobra II built-in ethernet module
using ProgrammableLogicControllers;
using Configuration;
using SocketServer;
using WebInterface;
using SmartViewRaptor;
using RFIDReaders;
using TemperatureSensor;
using DeviceHive;
using Keyboards;
using Displays;
using SDCardReaders;
using TouchScreenInterface;
using KioskUtility;

namespace SmartTruckRaptor
{
    public class NetworkDefaults
    {
        //These settings will be stored in flash memory and accessed by the web config pages
        public const string NetMask = "255.255.255.0";
        public const string GatewayAddress = "0.0.0.0";     //"192.168.8.2";
        public const string PrimaryDNS = "0.0.0.0";         //192.168.8.20
        public const string SecondaryDNS = "0.0.0.0";       //192.168.8.23
        public const string CloudIP = "192.168.8.134";    //for my computer - creates CloudUrl = "http://192.168.8.134/api"
        //public const string CloudIP = "192.168.8.14";       //sbdev computer
        //public const string CloudIP = "192.168.6.50";      //GEAPS 2014 DeviceHive computer
        //public const string CloudIP = "0.0.0.0";            //creates CloudUrl to prevent connecting to DeviceHive
        
        //SmartTruck Kiosk settings
        public const int SvBox1TcpPort = 8001;         //for SmartView box 1 TCP Server
        public const int SvBox2TcpPort = 8002;         //for SmartView box 2 TCP Server
        public const int PlcTcpPort = 8003;         //for PLC TCP Server 
        public const int RfidTcpPort = 8004;         //for RFID TCP Server 

        //IP address of Cobra board
        //public const string StaticIP = "192.168.6.63";   //GEAPS 2014 show address
        public const string StaticIP = "192.168.8.235";   //Dan's development address
        //public const string StaticIP = "192.168.8.189";     //used by iGMS development server
    }

    public class SystemDefaults
    {
        public const string ModelNumber = "STR30003";
        public const string FacilityCode = "01";
        public const int LoggingLevel = 0;
        public const string Password = "5050";
        public const bool EnableHopping = true;
        public const int ChannelNumber = 49;
        public const int NumOfSignsOnCOM2 = 1;
        public const int NumOfSignsOnCOM3 = 1;
    }

    public class PrototypeDevice : DeviceEngine
    {
        private GHINET.EthernetENC28J60 eth;
        private Microsoft.SPOT.Net.NetworkInformation.NetworkInterface ni;
        private bool EthernetStarted;
        private bool WebServerStarted;
        private bool PlcTcpServerStarted;
        private bool RfidTcpServerStarted;
        private bool SvBox1TcpServerStarted;
        private bool SvBox2TcpServerStarted;
        private const int RequestTimeout = 120000;
        private GTME.RS232 rs232SmartView1;
        private GTME.RS232 rs232SmartView2;
        private GTME.RS232 rs232PLC;
        private GTME.RS232 rs232RFID;
        private GTME.RS485 rs485SmartView;
        private GTME.Relay_X1 x1RelayModule;
        private GTME.Keypad_KP16 kp16KeypadModule;
        private GTME.Display_T43 t43DisplayModule;
        private GTME.SDCard sdCardModule;
        private Gadgeteer.Modules.Seeed.TemperatureHumidity TempHumidityModule;
        private NetworkConfigData netData;
        private SystemConfigData systemData;
        private SmartTruckConfigData smartTruckData;
        private AutoResetEvent CableConnectedEvent;
        private DL05PLC stPLC;
        private IntermecIF2 RfidReader;
        private SmartViewBox232 SvBox1;
        private SmartViewBox232 SvBox2;
        private SmartViewBox485 SvBoxRS485;
        private TempSensor TempHumiditySensor;
        private KP16KeyPad  KeyPad;
        private T43Display Display;
        private SDCardReader CardReader;
        private BusinessLogic bizlogic;
        private TCPServer PlcTcpServer; //Programed Logic Controller TCP/IP Server for pass-through mode
        private TCPServer RfidTcpServer; //RF ID reader TCP/IP Server  for pass-through mode
        private TCPServer SvBox1TcpServer; //SmartView box 1 TCP/IP Server  for pass-through mode
        private TCPServer SvBox2TcpServer; //SmartView box 2 TCP/IP Server for pass-through mode
        private HTTPServer webServer;
        private KioskTouchScreen TouchScreenServer;
        private string CloudUrlPrefix;
        private string CloudUrlSuffix;
        private string CloudUrl;
        private byte[] MacAddressPhysical;
        private string SerialNumber;
        private string MacAddressSeed;
        private string MacAddressText;
        private Guid DeviceHiveId;
        private Guid DeviceHiveKey;
        private Hashtable WebPageList;
        private WebPage NetworkPageData;
        private WebPage OptionsPageData;
        private WebPage OptionsAdminPageData;
        private WebPage SecurityPageData;
        private WebPage LoginPageData;
        private WebPage CssPageData;
        private WebPage JavaScriptPageData;
        private WebPage SmartTruckPageData;

        public PrototypeDevice(     Gadgeteer.Modules.GHIElectronics.Ethernet_ENC28 _ethernet_ENC28,
                                    GTME.RS232 _rs232Socket12,      //Socket 12 
                                    GTME.RS232 _rs232Socket01,      //Socket 1
                                    GTME.RS232 _rs232Socket10,      //Socket 10 
                                    GTME.RS232 _rs232Socket04,      //Socket 4
                                    GTME.RS485 _rs485Module,              //Socket 11
                                    GTME.Relay_X1 _x1RelayModule,
                                    GTME.Keypad_KP16 _kp16KeypadModule,
                                    GTME.Display_T43 _t43DisplayModule,
                                    GTME.SDCard _sdCardModule,
                                    Gadgeteer.Modules.Seeed.TemperatureHumidity _TempHumidityModule,
                                    KioskTouchScreen _TouchScreenServer
                              )
        {
            //set the NETMF time from the real time clock chip
            DateTime rtc = KioskUtilities.GetRTCData();
            Utility.SetLocalTime(rtc);
            //Print firmware and time
            FirmwareVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
            Debug.Print("Firmware Version " + FirmwareVersion);
            Debug.Print("Command processing has started at " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") );
            sdCardModule = _sdCardModule;

            //retrieve SmartTruck configuration data from flash memory
            if (SmartTruckFlashMemory.InitializeSmartTruckInfoEWR())
            {
                smartTruckData = SmartTruckFlashMemory.GetSmartTruckConfigData();
                if (smartTruckData == null)
                {
                    //Could not retrieve old data from flash, store default system configuration data into flash memory
                    smartTruckData = new SmartTruckConfigData(SystemDefaults.NumOfSignsOnCOM2, SystemDefaults.NumOfSignsOnCOM3);
                    Debug.Print("PrototypeDevice() will set SmartTruck default configuration data into Flash memory");
                    SmartTruckFlashMemory.SetSmartTruckConfigData(smartTruckData);
                }
                else
                {
                    //success in retreive data from flash memory
                    Debug.Print("Number of SmartView signs on COM2 = " + smartTruckData.NumOfSignsOnCOM2);
                    Debug.Print("Number of SmartView signs on COM3 = " + smartTruckData.NumOfSignsOnCOM3);
                }
            }
            else
            {
                //could not initialize extended weak reference for SmartTruck flash memory
                Debug.Print("Error - PrototypeDevice() could not Initialize SmarTruckInfo EWR for flash memory, will use newly generated items");
                smartTruckData = new SmartTruckConfigData(SystemDefaults.NumOfSignsOnCOM2, SystemDefaults.NumOfSignsOnCOM3);
            }
            
            //retrieve system configuration data from flash memory
            systemData = null;
            string Message;
            if (FlashMemory.InitializeSystemInfoEWR())
            {
                systemData = FlashMemory.GetSystemConfigData();
                if (systemData == null)
                {
                    systemData = SystemConfigFile.Load(_sdCardModule, out Message);
                    if (systemData == null)
                    {
                        //Could not retrieve system data from storage, store default system configuration data into flash memory
                        Debug.Print(Message);
                        SerialNumber = KioskUtilities.GenerateUniqueSerialNumber(SystemDefaults.ModelNumber, SystemDefaults.FacilityCode);
                        DeviceHiveId = Guid.NewGuid();
                        DeviceHiveKey = Guid.NewGuid();
                        Debug.Print("Serial Number generated = " + SerialNumber);
                        Debug.Print("DeviceHiveId generated= " + DeviceHiveId.ToString());
                        systemData = new SystemConfigData(SystemDefaults.ModelNumber,
                                                            SerialNumber,
                                                            DeviceHiveId,
                                                            DeviceHiveKey,
                                                            SystemDefaults.LoggingLevel,
                                                            SystemDefaults.Password
                                                            );
                        Debug.Print( "PrototypeDevice() will set system default configuration data into Flash memory");
                    }
                    else
                    {
                        Debug.Print("PrototypeDevice() will set system configuration data from SD card into Flash memory");
                    }
                    FlashMemory.SetSystemConfigData(systemData);
                }
            }
            else
            {
                //could not initialize extended weak reference for system config data flash memory
                systemData = SystemConfigFile.Load(_sdCardModule, out Message);
                if (systemData == null)
                {
                    Debug.Print(Message);
                    Debug.Print("Error - PrototypeDevice() could not Initialize SystemInfo EWR for flash memory, will use default values");
                    SerialNumber = KioskUtilities.GenerateUniqueSerialNumber(SystemDefaults.ModelNumber, SystemDefaults.FacilityCode);
                    DeviceHiveId = Guid.NewGuid();
                    DeviceHiveKey = Guid.NewGuid();
                    Debug.Print("Serial Number generated = " + SerialNumber);
                    Debug.Print("DeviceHiveId generated= " + DeviceHiveId.ToString());
                    systemData = new SystemConfigData(SystemDefaults.ModelNumber,
                                                        SerialNumber,
                                                        DeviceHiveId,
                                                        DeviceHiveKey,
                                                        SystemDefaults.LoggingLevel,
                                                        SystemDefaults.Password
                                                        );
                }
                else
                {
                    Debug.Print("Error - PrototypeDevice() could not Initialize SystemInfo EWR for flash memory, will use data from SD card file");
                }
            }

            Debug.Print("System data in use:");
            Debug.Print("Model Number = " + systemData.ModelNumber);
            Debug.Print("Serial Number = " + systemData.SerialNumber);
            Debug.Print("DeviceHive ID = " + systemData.DeviceHiveId.ToString());
            Debug.Print("DeviceHive Key = " + systemData.DeviceHiveKey.ToString()); 
            Debug.Print("LoggingLevel = " + systemData.LoggingLevel);
            Debug.Print("Password = " + systemData.Password);

            //retrieve network configuration data from flash memory

            netData = null;
            if (FlashMemory.InitializeNetworkInfoEWR())
            {
                netData = FlashMemory.GetNetworkConfigData();
                if (netData == null)
                {
                    netData = NetworkConfigFile.Load(_sdCardModule, out Message);
                    if (netData == null)
                    {
                        Debug.Print(Message);
                        //Could not retrieve network data, store default network configuration data into flash memory
                        MacAddressSeed = SystemDefaults.ModelNumber + systemData.SerialNumber;
                        MacAddressPhysical = KioskUtilities.GenerateUniqueMacAddr(MacAddressSeed);
                        MacAddressText = KioskUtilities.BytesToHexString(MacAddressPhysical, MacAddressPhysical.Length);
                        Debug.Print("MAC address generated = " + MacAddressText);
                        netData = new NetworkConfigData(MacAddressPhysical,
                                                            NetworkDefaults.StaticIP,
                                                            NetworkDefaults.NetMask,
                                                            NetworkDefaults.GatewayAddress,
                                                            NetworkDefaults.PrimaryDNS,
                                                            NetworkDefaults.SecondaryDNS,
                                                            NetworkDefaults.CloudIP
                                                        );
                        Debug.Print("PrototypeDevice() will set default network configuration data into Flash memory");
                    }
                    else
                    {
                        Debug.Print("PrototypeDevice() will set network configuration data from SD card into Flash memory");
                    }
                    FlashMemory.SetNetworkConfigData(netData);
                }
            }
            else
            {
                netData = NetworkConfigFile.Load(_sdCardModule, out Message);
                if (netData == null)
                {
                    Debug.Print(Message);
                    //could not initialize extended weak reference for flash memory
                    MacAddressSeed = SystemDefaults.ModelNumber + systemData.SerialNumber;
                    MacAddressPhysical = KioskUtilities.GenerateUniqueMacAddr(MacAddressSeed);
                    MacAddressText = KioskUtilities.BytesToHexString(MacAddressPhysical, MacAddressPhysical.Length);
                    Debug.Print("MAC address generated = " + MacAddressText);
                    Debug.Print("Error - PrototypeDevice() could not Initialize NetworkInfo EWR, will use default network data");
                    netData = new NetworkConfigData(
                                                    MacAddressPhysical,
                                                    NetworkDefaults.StaticIP,
                                                    NetworkDefaults.NetMask,
                                                    NetworkDefaults.GatewayAddress,
                                                    NetworkDefaults.PrimaryDNS,
                                                    NetworkDefaults.SecondaryDNS,
                                                    NetworkDefaults.CloudIP
                                                    );
                }
                else
                {
                    Debug.Print("Error - PrototypeDevice() could not Initialize NetworkInfo EWR, will use network data from SD card");
                }
            }

            Debug.Print("Network data in use:");
            Debug.Print("MAC address = " + KioskUtilities.BytesToHexString( netData.MacAddress, netData.MacAddress.Length));
            Debug.Print("Kiosk IP address = " + netData.StaticIP);
            Debug.Print("Subnet Mask = " + netData.NetMask);
            Debug.Print("Gateway address = " + netData.GatewayAddress);
            Debug.Print("Primary DNS = " + netData.PrimaryDNS);
            Debug.Print("Secondary DNS = " + netData.SecondaryDNS);
            Debug.Print("DeviceHive IP address = " + netData.CloudIP);

            isPassThroughModeActive = (netData.CloudIP == "0.0.0.0");
            if (isPassThroughModeActive)
                Debug.Print("Pass-Through Mode is active");
            else
                Debug.Print("DeviceHive Mode is active");
            //string CloudUrl = "http://" + netData.CloudIP + "/api";
            CloudUrlPrefix = Resources.GetString(Resources.StringResources.CloudUrlPrefix);
            CloudUrlSuffix = Resources.GetString(Resources.StringResources.CloudUrlSuffix);
            CloudUrl = CloudUrlPrefix + netData.CloudIP + CloudUrlSuffix;
            DcClient = new DeviceHive.HttpClient(CloudUrl, RequestTimeout);
            Initializing += new ConnectEventHandler(PreInit);
            Initialized += new ConnectEventHandler(PostInit);
            Connecting += new ConnectEventHandler(PreConnect);
            Connected += new ConnectEventHandler(PostConnect);
            BeforeCommand += new CommandEventHandler(PreProcessCommand);
            AfterCommand += new CommandEventHandler(PostProcessCommand);
            BeforeNotification += new NotificationEventHandler(PreProcessNotification);
            AfterNotification += new NotificationEventHandler(PostProcessNotification);
            Disconnected += new SimpleEventHandler(OnDisconnect);
            Error += new ErrorEventHandler(OnError);
            
            // Map RS-232 ports to devices, cache device objects
            rs232SmartView1 = _rs232Socket01; 
            rs232SmartView2 = _rs232Socket12; 
            rs232PLC = _rs232Socket10;
            rs232RFID = _rs232Socket04;
            rs485SmartView =  _rs485Module;
            x1RelayModule = _x1RelayModule;
            kp16KeypadModule = _kp16KeypadModule;
            t43DisplayModule = _t43DisplayModule;
            sdCardModule = _sdCardModule;

            TempHumidityModule = _TempHumidityModule;
            eth = _ethernet_ENC28.Interface;
            ni = null;
            TouchScreenServer = _TouchScreenServer;
            
            //initialize events and flags
            DeviceConnectedEvent = new AutoResetEvent(false);
            CableConnectedEvent = new AutoResetEvent(false);
            HiveIPAddrEvent = new AutoResetEvent(false);
            EthernetStarted = false;
            PlcTcpServerStarted=false;
            RfidTcpServerStarted = false;
            SvBox1TcpServerStarted=false;
            SvBox2TcpServerStarted=false;
            WebServerStarted = false;

            //create device objects
            TempHumiditySensor = new TempSensor(  this, TempHumidityModule, "SHT10", "TEMPSENSOR" );
            PlcTcpServer = new TCPServer(this, NetworkDefaults.PlcTcpPort, BusinessConstants.MaxTcpPlcReceiveQueueSize);
            stPLC = new DL05PLC(this, rs232PLC, systemData, "PLC");
            RfidTcpServer = new TCPServer(this, NetworkDefaults.RfidTcpPort, BusinessConstants.MaxTcpRfidReceiveQueueSize);
            RfidReader = new IntermecIF2(this, rs232RFID, systemData, "RFID");
            KeyPad = new KP16KeyPad(this, kp16KeypadModule, systemData, "KEYPAD");
            Display = new T43Display(this, t43DisplayModule, systemData, "DISPLAY");
            CardReader = new SDCardReader(this, sdCardModule, "SDCARDREADER");

            //For SmartViewBox 1, using Cobra II, ComPortName should be "COM2" for Socket 8 
            //For SmartViewBox 2, using CobraII, ComPortName should be "COM3" for split cable-Socket 8 and 10
            //One RS-232 port communicates with two Pro-Lite message bars inside the SmartView box
            SvBox1 = new SmartViewBox232(this, rs232SmartView1, systemData, "SMARTVIEW1");
            SvBox2 = new SmartViewBox232(this, rs232SmartView2, systemData, "SMARTVIEW2");
            SvBoxRS485 = new SmartViewBox485(this, rs485SmartView, systemData, "SMARTVIEW485");
            SvBox1TcpServer = new TCPServer(this, NetworkDefaults.SvBox1TcpPort, BusinessConstants.MaxTcpToSvReceiveQueueSize);
            SvBox2TcpServer = new TCPServer(this, NetworkDefaults.SvBox2TcpPort, BusinessConstants.MaxTcpToSvReceiveQueueSize);

            //Set up virtual web pages for the web server
            WebPageList = new Hashtable();
            LoginPageData = new WebPage(Resources.GetString(Resources.StringResources.login_page), "login.htm", "text/html");
            NetworkPageData = new WebPage(Resources.GetString(Resources.StringResources.network_page), "network.htm", "text/html");
            OptionsPageData = new WebPage(Resources.GetString(Resources.StringResources.options_page), "options.htm", "text/html");
            OptionsAdminPageData = new WebPage(Resources.GetString(Resources.StringResources.optionsadmin_page), "optionsadmin.htm", "text/html");
            SmartTruckPageData = new WebPage(Resources.GetString(Resources.StringResources.SmartTruck_page), "smarttruck.htm", "text/html");
            SecurityPageData = new WebPage(Resources.GetString(Resources.StringResources.security_page), "security.htm", "text/html");
            CssPageData = new WebPage(Resources.GetString(Resources.StringResources.styles_sheet), "styles.css", "text/css");
            JavaScriptPageData = new WebPage(Resources.GetString(Resources.StringResources.utility_script), "utility.js", "text/javascript");

            //Create a hash table with the relative path as key and the web page information object as data
            //Keys for the HashTable should be the URL paths to virtual web pages
            WebPageList.Add("login", LoginPageData);
            WebPageList.Add("login.htm", LoginPageData);
            WebPageList.Add("network", NetworkPageData);
            WebPageList.Add("network.htm", NetworkPageData);
            WebPageList.Add("options", OptionsPageData);
            WebPageList.Add("options.htm", OptionsPageData);
            WebPageList.Add("optionsadmin", OptionsAdminPageData);
            WebPageList.Add("optionsadmin.htm", OptionsAdminPageData);
            WebPageList.Add("smarttruck", SmartTruckPageData);
            WebPageList.Add("smarttruck.htm", SmartTruckPageData);
            WebPageList.Add("security", SecurityPageData);
            WebPageList.Add("security.htm", SecurityPageData);
            WebPageList.Add("styles.css", CssPageData);
            WebPageList.Add("utility.js", JavaScriptPageData);

            //Start the web server
            webServer = new HTTPServer(this, WebPageList);

            //Start the touch screen T43 user interface
            TouchScreenServer.Initialize( this, KeyPad, Display, CardReader);

            //Start the business logic object
            bizlogic = new BusinessLogic(this, 
                stPLC, RfidReader, SvBox1, SvBox2, SvBoxRS485, 
                TempHumiditySensor, PlcTcpServer, RfidTcpServer, SvBox1TcpServer, SvBox2TcpServer, 
                webServer, WebPageList, 
                systemData, netData,
                KeyPad, Display, CardReader, 
                "CONTROLLER");
        }

        /// <summary>
        /// Start the TCP and HTTP servers
        /// </summary>
        private void StartTcpServers()
        {
            if (isPassThroughModeActive)
            {
                if (!RfidTcpServerStarted)
                {
                    //Start the RFID TCP Server
                    IPEndPoint RfidSvrEndPoint = RfidTcpServer.Start(eth);
                    if (RfidSvrEndPoint != null)
                    {
                        RfidTcpServerStarted = true;
                        Debug.Print("RfidTcpServer running at TCP port " + RfidSvrEndPoint.Port.ToString());
                    }
                    else
                    {
                        Debug.Print("RfidTcpServer cannot start");
                    }
                }
                else
                {
                    Debug.Print("RfidTcpServer already started");
                }

                if (!PlcTcpServerStarted)
                {
                    //Start the PLC TCP Server
                    IPEndPoint PLCSvrEndPoint = PlcTcpServer.Start(eth);
                    if (PLCSvrEndPoint != null)
                    {
                        PlcTcpServerStarted = true;
                        Debug.Print("PLC TCP Server running at TCP port " + PLCSvrEndPoint.Port.ToString());
                    }
                    else
                    {
                        Debug.Print("PLC TCP Server cannot start ");
                    }
                }
                else
                {
                    Debug.Print("PlcTcpServer already started");
                }

                if (!SvBox1TcpServerStarted)
                {
                    //Start the SmartView Box1 TCP Server
                    IPEndPoint SvBox1EndPoint = SvBox1TcpServer.Start(eth);
                    if (SvBox1EndPoint != null)
                    {
                        SvBox1TcpServerStarted = true;
                        Debug.Print("SmartView Box1 TCP Server running at TCP port " + SvBox1EndPoint.Port.ToString());
                    }
                    else
                    {
                        Debug.Print("SmartView Box1 TCP Server cannot start ");
                    }
                }
                else
                {
                    Debug.Print("SmartView Box1 TcpServer already started");
                }

                if (!SvBox2TcpServerStarted)
                {
                    //Start the SmartView Box2 TCP Server
                    IPEndPoint SvBox2EndPoint = SvBox2TcpServer.Start(eth);
                    if (SvBox2EndPoint != null)
                    {
                        SvBox2TcpServerStarted = true;
                        Debug.Print("SmartView Box2 TCP Server running at TCP port " + SvBox2EndPoint.Port.ToString());
                    }
                    else
                    {
                        Debug.Print("SmartView Box2 TCP Server cannot start ");
                    }
                }
                else
                {
                    Debug.Print("SmartView Box2 TcpServer already started");
                }
            }
        }

        /// <summary>
        /// Start the HTTP Server for the web browser interface
        /// </summary>
        void StartWebServer()
        {
            if (!WebServerStarted)
            {
                if (webServer.StartServer(eth, netData.StaticIP))
                {
                    WebServerStarted = true;
                    Debug.Print("Web Server started");
                }
                else
                {
                    Debug.Print("Web Server cannot start ");
                }
            }
            else
            {
                Debug.Print("Web Server already started");
            }
        }

        /// <summary>
        /// Called by the DeviceHive Init function
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <returns>true</returns>
        private bool PreInit(object sender, EventArgs e)
        {
            return true;
        }

        /// <summary>
        /// Called by the DeviceHive Init function
        /// Start the TCP/IP stack here
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <returns></returns>
        private bool PostInit(object sender, EventArgs e)
        {
            bool rc = true;
            if (!EthernetStarted)
            {
                if (StartClient())
                {
                    EthernetStarted = true;
                    Debug.Print("PostInit() started Ethernet");
                }
                else
                {
                    rc = false;
                    Debug.Print("PostInit() failed to start Ethernet");
                }
            }
            else
            {
                Debug.Print("Ethernet already started");
            }

            if (EthernetStarted)
            {
                StartTcpServers();
                StartWebServer();
            }
            return rc;
        }

        /// <summary>
        /// Set the URL for connecting to the DeviceHive Server
        /// </summary>
        /// <param name="CloudIP"></param>
        public void SetCloudUrl(string CloudIP)
        {
            DeviceHive.HttpClient client;
            client = (DeviceHive.HttpClient)DcClient;
            CloudUrl = CloudUrlPrefix + CloudIP + CloudUrlSuffix;
            client.CloudUrl = CloudUrl;
        }

        /// <summary>
        /// called by the DeviceHive Connect function to connect to the DeviceHive web server
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <returns>true</returns>
        private bool PreConnect(object sender, EventArgs e)
        {
            bool rc = true;
            //Connect to the DeviceHive Server if it's IP address is known
            string CloudIP = netData.CloudIP;
            while (CloudIP == "0.0.0.0")
            {
                //Cannot talk to DeviceHive until valid IP address is set
                Debug.Print("Waiting for valid DeviceHive IP address set in flash memory");
                HiveIPAddrEvent.WaitOne();
                netData = FlashMemory.GetNetworkConfigData();
                if ((netData != null) && (netData.CloudIP != "0.0.0.0"))
                {
                    CloudIP = netData.CloudIP;
                    SetCloudUrl(CloudIP);
                }
            }
            return rc;
        }

        /// <summary>
        /// called by the DeviceHive Connect function to connect to the DeviceHive web server
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <returns>true</returns>
        private bool PostConnect(object sender, EventArgs e)
        {
            return true;
        }

        /// <summary>
        /// Called by the DeviceHive Init function to populate a Device object 
        /// </summary>
        /// <returns>Device object</returns>
        protected override Device CreateDeviceData()
        {
            Device dd=null;
            Debug.Print("Initializing device data");
            Hashtable hdata = new Hashtable();
            hdata.Add( "SerialNumber", systemData.SerialNumber);
                
            dd = new Device()
            {
                deviceClass = new DeviceClass()
                {
                    name = Resources.GetString(Resources.StringResources.DeviceClass),
                    version = systemData.ModelNumber,
                    offlineTimeout = 0
                },
                data = hdata,
                id = systemData.DeviceHiveId,
                key = systemData.DeviceHiveKey.ToString(),
                name = Resources.GetString(Resources.StringResources.DeviceName),
                network = new DeviceNetwork()
                {
                    name = Resources.GetString(Resources.StringResources.NetworkName),
                    description = Resources.GetString(Resources.StringResources.NetworkDesc)
                },
                status = DeviceStatus.OK,
            };
            Debug.Print("Done initializing device data.");
            return dd;
        }

        //public const int TempSensorIndex = 0;
        //public const int KeyBoardIndex = 1;
        //public const int LCDDisplayIndex = 2;
        //public const int ReaderIndex = 0;
        //public const int BusinessLogicIndex = 1;

        /// <summary>
        /// Called by the DeviceHive Init function to populate the equipment table 
        /// </summary>
        protected override void CreateEquipment()
        {
            Debug.Print("Initializing equipment.");
            DeviceData.equipment = new Equipment[]
            {
                stPLC,         
                RfidReader,    
                SvBox1,       
                SvBox2,      
                SvBoxRS485,
                TempHumiditySensor,  
                KeyPad,         
                Display,
                CardReader,
                bizlogic       
            };
        }

        /// <summary>
        /// Get the absolute value of a floating point number
        /// </summary>
        /// <param name="f"></param>
        /// <returns></returns>
        public float Abs(float f)
        {
            return f < 0.0F ? -f : f;
        }

        /// <summary>
        /// Called by the DeviceHive thread to process incoming commands from DeviceHive clients
        /// </summary>
        /// <returns>boolean</returns>
        public override bool ProcessCommands()
        {
            return base.ProcessCommands();
        }

        /// <summary>
        /// Called by the DeviceHive ProcessCommands() function from the DeviceHive thread
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PreProcessCommand(object sender, CommandEventArgs e)
        {
            //Let other threads know that we have a connection to DeviceHive Server
            DeviceConnectedEvent.Set();
        }

        /// <summary>
        /// Called by the DeviceHive ProcessCommands() function from the DeviceHive thread
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PostProcessCommand(object sender, CommandEventArgs e)
        {
        }

        /// <summary>
        /// Called by the DeviceHive SendNotification function before a notification is sent
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PreProcessNotification(object sender, NotificationEventArgs e)
        {
        }

        /// <summary>
        /// Called by the DeviceHive SendNotification function after a notification is sent
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PostProcessNotification(object sender, NotificationEventArgs e)
        {
        }

        /// <summary>
        /// Called by DeviceHive Connect and ProcessCommands in Exception handling
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <returns>true if the exception should disconnect from DeviceHive</returns>
        bool OnError(object sender, ErrorEventArgs e)
        {
            bool rc = true;  
            return rc;
        }

        /// <summary>
        /// Called by DeviceHive Connect and ProcessCommands in Exception handling
        /// Do any clean-up if an exception caused a disconnect from Device Hive
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDisconnect(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// Start the Client TCP/IP network software
        /// </summary>
        /// <returns>true if TCP/IP started ok</returns>
        private bool StartClient()
        {
            bool rc = false;
            //eth = new GHINET.EthernetENC28J60(SPI.SPI_module.SPI2, Pin.P1_10, Pin.P2_11, Pin.P1_9, 4000);  //Used by Cobra II built-in ethernet module
            eth.NetworkAddressChanged += new GHI.Premium.Net.NetworkInterfaceExtension.NetworkAddressChangedEventHandler(eth_NetworkAddressChanged);
            eth.CableConnectivityChanged += new EthernetENC28J60.CableConnectivityChangedEventHandler(eth_CableConnectivityChanged);
            GHI.Premium.System.Util.SetMACAddress(GHI.Premium.System.NetworkInterface.ENC28j60_Ethernet, netData.MacAddress);
            if (!eth.IsOpen) eth.Open();
            if (!eth.IsCableConnected)
            {
                Debug.Print("Ethernet cable is not connected, waiting for connection");
                CableConnectedEvent.WaitOne();
                Thread.Sleep(1000);
            }
            ni = eth.NetworkInterface;
            if ( ni == null )
            {
                rc = false;
                return rc;
            }

            //report MAC address settings
            byte[] PhyAddress = netData.MacAddress;
            string sMAC = KioskUtilities.BytesToHexString(PhyAddress, PhyAddress.Length);
            Debug.Print("MAC address in flash memory = " + sMAC);
            PhyAddress = ni.PhysicalAddress;
            sMAC = KioskUtilities.BytesToHexString(PhyAddress, PhyAddress.Length);
            Debug.Print("MAC address in Network Interface = " + sMAC);
                 
            //set IP, Gateway, DNS IPs
            ni.EnableStaticIP(netData.StaticIP, netData.NetMask, netData.GatewayAddress);
            ni.EnableStaticDns(new string[] { netData.PrimaryDNS, netData.SecondaryDNS });
 
            GHINET.NetworkInterfaceExtension.AssignNetworkingStackTo(eth);

            if (eth.IsActivated)
                Debug.Print("EthernetENC28J60 is activated");
            else
                Debug.Print("EthernetENC28J60 is shut down");

            if (eth.IsOpen)
                Debug.Print("EthernetENC28J60 interface is open");
            else
                Debug.Print("EthernetENC28J60 interface is closed");

            if (eth.IsCableConnected)
                Debug.Print("EthernetENC28J60 cable is connected");
            else
                Debug.Print("EthernetENC28J60 cable is not connected");

            int waitCount = 60;
            bool niSet = false;
            while ( (waitCount > 0) && !niSet )
            {
                Thread.Sleep(1000);
                waitCount--;
                if ((ni.IPAddress == netData.StaticIP) && (ni.SubnetMask == netData.NetMask))
                    niSet = true;
            }

            if (niSet)
            {
                Debug.Print("EthernetENC28J60 IP address has been set: IP = " + ni.IPAddress + " Subnet mask = " + ni.SubnetMask);
                rc = true;
            }
            else
            {
                Debug.Print("Could not set EthernetENC28J60 IP address");
                rc = false;
            }
            return rc;
        }

        void eth_CableConnectivityChanged(object sender, GHINET.EthernetENC28J60.CableConnectivityEventArgs e)
        {
            try
            {
                if (e.IsConnected)
                {
                    Debug.Print("PrototypeDevice.CableConnectivityChanged Event - Ethernet cable is connected");
                    CableConnectedEvent.Set();
                }
                else
                {
                    Debug.Print("PrototypeDevice.CableConnectivityChanged Event - Ethernet cable is not connected");
                }
            }
            catch (Exception ex)
            {
                KioskUtilities.LogException("PrototypeDevice.eth_CableConnectivityChanged() exception ", ex);
                KioskUtilities.Reboot();
            }
        }

        void eth_NetworkAddressChanged(object sender, EventArgs e)
        {
            try
            {
                Microsoft.SPOT.Net.NetworkInformation.NetworkInterface ni  = eth.NetworkInterface;
                Debug.Print("NetworkAddressChanged Event");
                if (ni.IsDhcpEnabled)
                    Debug.Print("DHCP is enabled");
                else
                    Debug.Print("DHCP is disabled");
                Debug.Print("IP address = " + ni.IPAddress);
                Debug.Print("Gateway address = " + ni.GatewayAddress);
                Debug.Print("Subnet mask = " + ni.SubnetMask);

                foreach (string address in ni.DnsAddresses)
                {
                    Debug.Print("DNS address  = " + address);
                }
            }
            catch (Exception ex)
            {
                KioskUtilities.LogException("eth_NetworkAddressChanged() exception ", ex);
                KioskUtilities.Reboot();
            }
        }

    }
}


file BusinessLogic.cs


using System;
using System.Text;
using System.Threading;
using System.Collections;
using System.Reflection;
using System.IO;
using System.Net;
using System.Net.Sockets;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Presentation.Shapes;
using Microsoft.SPOT.Touch;
using Microsoft.SPOT.IO;
using DeviceHive;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using GTME = Gadgeteer.Modules.GHIElectronics;
using Gadgeteer.Networking;
using ProgrammableLogicControllers;
using SocketServer;
using Configuration;
using WebInterface;
using Keyboards;
using Displays;
using SmartViewRaptor;
using RFIDReaders;
using SDCardReaders;
using TemperatureSensor;
using GHI.Premium.System;
using GHI.Premium.USBHost;
using GHI.Premium.IO;
using KioskUtility;

namespace SmartTruckRaptor
{
    public class BusinessConstants
    {
        public const int MaxPlcTcpTransmitQueueSize = 500;
        public const int MaxTcpPlcReceiveQueueSize = 500;
        public const int MaxRfidTcpTransmitQueueSize = 1000;
        public const int MaxTcpRfidReceiveQueueSize = 1000;
        public const int MaxSvToTcpTransmitQueueSize = 500;
        public const int MaxTcpToSvReceiveQueueSize = 500;
    }

    public class BusinessLogic : EquipmentEngine
    {
        const string StateParameter = "state";
        const string DeviceTypeName = "SmartTruck Kiosk Controller";

        private DL05PLC PlcDevice;
        private IntermecIF2 RfidReader;
        private SmartViewBox232 Sv1Box;
        private SmartViewBox232 Sv2Box;
        private  SmartViewBox485 Sv485Box;
        private KP16KeyPad KeyPad;
        private TCPServer PlcTcpServer;
        private TCPServer RfidTcpServer;
        private TCPServer Sv1TcpServer;
        private TCPServer Sv2TcpServer;
        private HTTPServer webServer;
        private Hashtable WebPageList;
        private PrototypeDevice  ThisDevice;
        private SystemConfigData systemData;
        private NetworkConfigData netData;
        private T43Display Display;
        private SDCardReader CardReader;
        private TempSensor TempHumiditySensor;

        /// <summary>
        /// Constructs a BussinessLogic object for the SmartBlock Kiosk
        /// </summary>
        /// <param name="dev">Parent device in DeviceHive classes</param>
        /// <param name="_PlcDevice">PLC05 object</param>
        /// <param name="_ReaderTcpServer">PLC TCPServer object</param>
        /// <param name="_webServer">HTTPServer object</param>
        /// <param name="_WebPageList">List of web page objects in HashTable</param>
        /// <param name="Code">Equipment Code for DeviceHive</param>
        public BusinessLogic(   PrototypeDevice dev, 
                                DL05PLC _PlcDevice,
                                IntermecIF2 _RfidReader,
                                SmartViewBox232 _Sv1Box,
                                SmartViewBox232 _Sv2Box,
                                SmartViewBox485 _Sv485Box,
                                TempSensor _TempHumiditySensor,
                                TCPServer _PlcTcpServer,
                                TCPServer _RfidTcpServer,
                                TCPServer _Sv1TcpServer,
                                TCPServer _Sv2TcpServer, 
                                HTTPServer _webServer, 
                                Hashtable _WebPageList,
                                SystemConfigData _systemData,
                                NetworkConfigData _netData,
                                KP16KeyPad _KeyPad,
                                T43Display _Display,
                                SDCardReader _CardReader,
                                string Code )  : base(dev)
        {
            Debug.Print("Initializing " + Code);
            code = Code;
            name = Code;
            type = DeviceTypeName; // v6
            PlcDevice = _PlcDevice;
            RfidReader = _RfidReader;
            Sv1Box = _Sv1Box;
            Sv2Box = _Sv2Box;
            Sv485Box = _Sv485Box;
            PlcTcpServer = _PlcTcpServer;
            RfidTcpServer = _RfidTcpServer;
            Sv1TcpServer = _Sv1TcpServer;
            Sv2TcpServer = _Sv2TcpServer;
            webServer = _webServer;
            WebPageList = _WebPageList;
            ThisDevice = dev;
            systemData = _systemData;
            netData = _netData;
            KeyPad = _KeyPad;
            Display = _Display;
            CardReader = _CardReader;
            TempHumiditySensor = _TempHumiditySensor;
            //oPort = new OutputPort(Pin.P1_5, false);

            //wire up event handler functions
            webServer.webServer_WebEventHandler += new WebEvent.ReceivedWebEventHandler(BizLogicWebEventHandler);

            if (ThisDevice.isPassThroughModeActive)
            {
                //pass-through mode option has been selected
                PlcTcpServer.OnMessageRx += new ClientMsgRxDelegate(PlcTcpServer_OnMessageRx);
                RfidTcpServer.OnMessageRx += new ClientMsgRxDelegate(RfidTcpServer_OnMessageRx);
                Sv1TcpServer.OnMessageRx += new ClientMsgRxDelegate(Sv1TcpServer_OnMessageRx);
                Sv2TcpServer.OnMessageRx += new ClientMsgRxDelegate(Sv2TcpServer_OnMessageRx);

                //Start the thread that receives characters from the PLC and sends them to the TCP/IP Socket Server
                ThreadStart PlcTcpTransmitStarter = new ThreadStart(PlcToTcpTransmitLoop);
                Thread PlcTcpTransmitThread = new Thread(PlcTcpTransmitStarter);
                PlcTcpTransmitThread.Start();

                //Start the thread that receives characters from the RFID Reader and sends them to the TCP/IP Socket Server
                ThreadStart RfidTcpTransmitStarter = new ThreadStart(RfidToTcpTransmitLoop);
                Thread RfidTcpTransmitThread = new Thread(RfidTcpTransmitStarter);
                RfidTcpTransmitThread.Start();

                //Start the thread that receives characters from the SmartView Box 1 and sends them to the TCP/IP network
                ThreadStart Sv1ToTcpTransmitStarter = new ThreadStart(Sv1ToTcpTransmitLoop);
                Thread Sv1ToTcpTransmitThread = new Thread(Sv1ToTcpTransmitStarter);
                Sv1ToTcpTransmitThread.Start();

                //Start the thread that receives characters from the SmartView Box 2 and sends them to the TCP/IP network
                ThreadStart Sv2ToTcpTransmitStarter = new ThreadStart(Sv2ToTcpTransmitLoop);
                Thread Sv2ToTcpTransmitThread = new Thread(Sv2ToTcpTransmitStarter);
                Sv2ToTcpTransmitThread.Start();


                USBHostModule usbHost = new USBHostModule(systemData);

            }
            else
            {
                //DeviceHive mode option has been selected
                //Start the thread that monitors the status of all equipment in the Kiosk
                ThreadStart SMLStarter = new ThreadStart(StatusMonitorLoop);
                Thread smlThread = new Thread(SMLStarter);
                smlThread.Start();
            }

            Debug.Print("Done initializing " + Code);
        }

        /*
        //Needed for heater module

        //private GTME.Relay_X1 relay;
        //GTME.Relay_X1 _relay,
        /// <summary>
        /// Turn the relay on or off
        /// </summary>
        /// <param name="sw">new state of relay: true=on flase=off</param>
        public void SetRelay(bool sw)
        {
            if (sw)
                relay.TurnOn();
            else
                relay.TurnOff();
        }

        /// <summary>
        /// Gets the state of the relay switch
        /// </summary>
        /// <returns></returns>
        public bool GetRelay()
        {
            return relay.Enabled;
        }
         */ 


        /// <summary>
        /// Poll the SmartView1 for data, send data to TCP Server
        /// Only for Pass-Through mode, not connected to DeviceHive
        /// </summary>
        void Sv1ToTcpTransmitLoop()
        {
            try
            {
                byte[] msgBytes = new byte[BusinessConstants.MaxSvToTcpTransmitQueueSize];
                int msgBytesCount = 0;
                while (true)
                {
                    try
                    {
                        Thread.Sleep(20);
                        if ( Sv1Box.GetSmartViewData( msgBytes, out msgBytesCount ))
                        {
                            //send the Sv1 characters to all connected TCP/IP clients using the client sockets
                            if (Sv1TcpServer != null)
                            {
                                if (systemData.LoggingLevel > 0)
                                {
                                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " SV1ToGMS: "
                                        + KioskUtilities.BytesToHexString(msgBytes, msgBytesCount)
                                        + " : " + KioskUtilities.BytesToAsciiString(msgBytes, msgBytesCount));
                                }
                                for (int index = 0; index < TCPServerConstants.BackLogCount; index++)
                                {
                                    TCPServer.Clienthandler m_handler = Sv1TcpServer.m_handlerArray[index];
                                    if ((m_handler != null) && (m_handler.m_clientConnected))
                                    {
                                        m_handler.m_clientSkt.Send(msgBytes, msgBytesCount, System.Net.Sockets.SocketFlags.None);
                                    }
                                }

                                //This is for testing RS485 Port
                                Sv485Box.SendBytesRaw(msgBytes, msgBytesCount);
                            }
                        }
                    }
                    catch (SocketException ex4)
                    {
                        KioskUtilities.LogException("Sv1ToTcpTransmitLoop() exception 4: ", ex4);
                    }
                    catch (Exception ex1)
                    {
                        KioskUtilities.LogException("Sv1ToTcpTransmitLoop() exception 1: ", ex1);
                        KioskUtilities.Reboot();
                    }
                }
            }
            catch (Exception ex3)
            {
                KioskUtilities.LogException("Sv1ToTcpTransmitLoop() could not start: ", ex3);
                KioskUtilities.Reboot();
            }
        }

        /// <summary>
        /// Handle message from remote TCP/IP client connected to the SmartView 1 Socket Server for Pass-Through Mode
        /// Receive the string of bytes and send to the Sv1 (SmartView) device 
        /// Pass-Through mode should be disabled if Kiosk is connected to a DeviceHive Server
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void Sv1TcpServer_OnMessageRx(object sender, ClientEventArgs e)
        {
            e.Response = null;
            if (e.Size > 0)
            {
                if (systemData.LoggingLevel > 0)
                {
                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " GMSToSV1: "
                        + KioskUtilities.BytesToHexString(e.Message, e.Size)
                        + " : " + KioskUtilities.BytesToAsciiString(e.Message, e.Size));
                }
                Sv1Box.SendBytesRaw(e.Message, e.Size);

                //This is for testing RS485 Port
                Sv485Box.SendBytesRaw(e.Message, e.Size);
            }
        }


        /// <summary>
        /// Poll the SmartView2 box for received data, send the data to the TCP Server
        /// Only for Pass-Through mode, if not connected to DeviceHive
        /// </summary>
        void Sv2ToTcpTransmitLoop()
        {
            try
            {
                byte[] msgBytes = new byte[BusinessConstants.MaxSvToTcpTransmitQueueSize];
                int msgBytesCount = 0;
                while (true)
                {
                    try
                    {
                        Thread.Sleep(20);
                        if ( Sv2Box.GetSmartViewData( msgBytes, out msgBytesCount ))
                        {
                            //send the Sv2 characters to all connected TCP/IP clients using the client sockets
                            if (Sv2TcpServer != null)
                            {
                                if (systemData.LoggingLevel > 0)
                                {
                                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " SV2ToGMS: "
                                        + KioskUtilities.BytesToHexString(msgBytes, msgBytesCount)
                                        + " : " + KioskUtilities.BytesToAsciiString(msgBytes, msgBytesCount));
                                }
                                for (int index = 0; index < TCPServerConstants.BackLogCount; index++)
                                {
                                    TCPServer.Clienthandler m_handler = Sv2TcpServer.m_handlerArray[index];
                                    if ((m_handler != null) && (m_handler.m_clientConnected))
                                    {
                                        m_handler.m_clientSkt.Send(msgBytes, msgBytesCount, System.Net.Sockets.SocketFlags.None);
                                    }
                                }

                                //This is for testing RS485 Port
                                Sv485Box.SendBytesRaw(msgBytes, msgBytesCount);
                            }
                        }
                    }
                    catch (SocketException ex4)
                    {
                        KioskUtilities.LogException("Sv2ToTcpTransmitLoop() exception 4: ", ex4);
                    }
                    catch (Exception ex1)
                    {
                        KioskUtilities.LogException("Sv2ToTcpTransmitLoop() exception 1: ", ex1);
                        KioskUtilities.Reboot();
                    }
                }
            }
            catch (Exception ex3)
            {
                KioskUtilities.LogException("Sv2ToTcpTransmitLoop() could not start: ", ex3);
                KioskUtilities.Reboot();
            }
        }

        /// <summary>
        /// Handle message from remote TCP/IP client connected to the SmartView 2 Socket Server for Pass-Through Mode
        /// Receive the string of bytes and and send to the Sv2 device
        /// Pass-Through mode should be disabled if Kiosk is connected to a DeviceHive Server
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void Sv2TcpServer_OnMessageRx(object sender, ClientEventArgs e)
        {
            e.Response = null;
            if (e.Size > 0)
            {
                if (systemData.LoggingLevel > 0)
                {
                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " GMSToSV2: "
                        + KioskUtilities.BytesToHexString(e.Message, e.Size)
                        + " : " + KioskUtilities.BytesToAsciiString(e.Message, e.Size));
                }
                Sv2Box.SendBytesRaw(e.Message, e.Size);

                //This is for testing RS485 Port
                Sv485Box.SendBytesRaw(e.Message, e.Size);
            }
        }

        /// <summary>
        /// Endless loop thread function to monitor the PLC serial port for received characters
        /// and send the PLC characters to the connected TCP clients when in Pass-Through Mode.
        /// </summary>
        void PlcToTcpTransmitLoop()
        {
            try
            {
                byte[] msgBytes = new byte[BusinessConstants.MaxPlcTcpTransmitQueueSize];
                int msgBytesCount = 0;
                while (true)
                {
                    try
                    {
                        Thread.Sleep(20);
                        if ( PlcDevice.GetPLCData( msgBytes, out msgBytesCount ))
                        {
                            //send the PLC characters to all connected TCP/IP clients using the client sockets
                            if (PlcTcpServer != null)
                            {
                                if (systemData.LoggingLevel > 0)
                                {
                                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " PLCToGMS: "
                                        + KioskUtilities.BytesToHexString(msgBytes, msgBytesCount)
                                        + " : " + KioskUtilities.BytesToAsciiString(msgBytes, msgBytesCount));
                                }
                                for (int index = 0; index < TCPServerConstants.BackLogCount; index++)
                                {
                                    TCPServer.Clienthandler m_handler = PlcTcpServer.m_handlerArray[index];
                                    if ((m_handler != null) && (m_handler.m_clientConnected))
                                    {
                                        m_handler.m_clientSkt.Send(msgBytes, msgBytesCount, System.Net.Sockets.SocketFlags.None);
                                    }
                                }
                            }

                        }
                    }
                    catch (SocketException ex4)
                    {
                        KioskUtilities.LogException("PlcTcpTransmitLoop() exception 4: ", ex4);
                    }
                    catch (Exception ex1)
                    {
                        KioskUtilities.LogException("PlcTcpTransmitLoop() exception 1: ", ex1);
                        KioskUtilities.Reboot();
                    }
                }
            }
            catch (Exception ex3)
            {
                KioskUtilities.LogException("PlcTcpTransmitLoop() could not start: ", ex3);
                KioskUtilities.Reboot();
            }
        }

        /// <summary>
        /// Handle message from remote TCP/IP client connected to the PLC Socket Server for Pass-Through Mode
        /// Receive the string of bytes and send to the PLC device using its serial port
        /// Pass-Through mode should be disabled if Kiosk is connected to a DeviceHive Server
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void PlcTcpServer_OnMessageRx(object sender, ClientEventArgs e)
        {
            e.Response = null;
            if (e.Size > 0)
            {
                if (systemData.LoggingLevel > 0)
                {
                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " GMSToPLC: "
                        + KioskUtilities.BytesToHexString(e.Message, e.Size)
                        + " : " + KioskUtilities.BytesToAsciiString(e.Message, e.Size));
                }
                PlcDevice.SendBytesRaw(e.Message, e.Size);
            }
        }

        /// <summary>
        /// Poll the RFID reader serial port for received data, send data to TCP Server
        /// Only for Pass-Through mode, if not connected to DeviceHive
        /// </summary>
        void RfidToTcpTransmitLoop()
        {
            try
            {
                byte[] msgBytes = new byte[BusinessConstants.MaxRfidTcpTransmitQueueSize];
                int msgBytesCount = 0;
                while (true)
                {
                    try
                    {
                        Thread.Sleep(20);
                        if ( RfidReader.GetIntermecData( msgBytes, out msgBytesCount ))
                        {
                            //send the RFID reader characters to all connected TCP/IP clients using the client sockets
                            if (RfidTcpServer != null)
                            {
                                if (systemData.LoggingLevel > 0)
                                {
                                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " RFIDToGMS: "
                                        + KioskUtilities.BytesToHexString(msgBytes, msgBytesCount)
                                        + " : " + KioskUtilities.BytesToAsciiString(msgBytes, msgBytesCount));
                                }
                                for (int index = 0; index < TCPServerConstants.BackLogCount; index++)
                                {
                                    TCPServer.Clienthandler m_handler = RfidTcpServer.m_handlerArray[index];
                                    if ((m_handler != null) && (m_handler.m_clientConnected))
                                    {
                                        m_handler.m_clientSkt.Send(msgBytes, msgBytesCount, System.Net.Sockets.SocketFlags.None);
                                    }
                                }
                            }

                        }
                    }
                    catch (SocketException ex4)
                    {
                        KioskUtilities.LogException("RfidTcpTransmitLoop() exception 4: ", ex4);
                    }
                    catch (Exception ex1)
                    {
                        KioskUtilities.LogException("RfidTcpTransmitLoop() exception 1: ", ex1);
                        KioskUtilities.Reboot();
                    }
                }
            }
            catch (Exception ex3)
            {
                KioskUtilities.LogException("RfidTcpTransmitLoop() could not start: ", ex3);
                KioskUtilities.Reboot();
            }
        }

        /// <summary>
        /// Handle message from remote TCP/IP client connected to the RFID Reader Socket Server for Pass-Through Mode
        /// Receive the string of bytes and send to the RFID Reader device
        /// Pass-Through mode should be disabled if Kiosk is connected to a DeviceHive Server
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void RfidTcpServer_OnMessageRx(object sender, ClientEventArgs e)
        {
            e.Response = null;
            if (e.Size > 0)
            {
                if (systemData.LoggingLevel > 0)
                {
                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " GMSToRFID: "
                        + KioskUtilities.BytesToHexString(e.Message, e.Size)
                        + " : " + KioskUtilities.BytesToAsciiString(e.Message, e.Size));
                }
                RfidReader.SendBytesRaw(e.Message, e.Size);
            }
        }


        /// <summary>
        /// Handle requests for the SmartBlock web page from the  webServer class WebApp object
        /// </summary>
        /// <param name="path">relative URL path to the page</param>
        /// <param name="method">http method GET or POST</param>
        /// <param name="responder">Responder class object</param>
        public void BizLogicWebEventHandler(string path, WebServer.HttpMethod method, Responder responder)
        {

            try
            {
                WebPage PageData = (WebPage)WebPageList[path];
                string content = PageData.Content;
                try
                {
                    UserAccessLevel userLevel;
                    if (PageData.Url.ToLower() == "smarttruck.htm") 
                    {
                        if (method == WebServer.HttpMethod.POST)
                        {
                            //protect change access to web pages with a password.
                            //Only the login page can be accessed without a password
                            if (!webServer.passwordFound(responder, out userLevel))
                            {
                                content = "<h2>Error - Incorrect password. Please log in</h2>";
                                Debug.Print(content);
                                return;
                            }
                            content = responder.Body.Text;
                            if (content == null)
                            {
                                content = "<h2>responder.Body.Text is null</h2>";
                                Debug.Print(content);
                                return;
                            }

                            //Example of posted data:
                            //NumOfLedBars=2

                            //Split on the & character to get all the name-value pairs
                            int ItemsNeeded =2;
                            string[] Pairs = content.Split(new char[] { '&' });
                            if (Pairs.Length < ItemsNeeded)
                            {
                                content = "<h2>Need " + ItemsNeeded.ToString() + " data items in posted web page</h2>";
                                Debug.Print(content);
                                return;
                            }

                            //Split on the = character to get a hashtable of name-value pairs
                            Hashtable Values = new Hashtable();
                            foreach (string pair in Pairs)
                            {
                                string[] NameValue = pair.Split(new char[] { '=' });
                                if (NameValue.Length == 2)
                                {
                                    Values.Add(NameValue[0], NameValue[1]);
                                }
                            }
                            if (Values.Count < ItemsNeeded)
                            {
                                content = "<h2>Need " + ItemsNeeded.ToString() + " data items in posted web page</h2>";
                                Debug.Print(content);
                                return;
                            }

                            int NumOfSignsOnCOM2 = int.Parse((string)Values["NumOfSignsOnCOM2"]);
                            if (NumOfSignsOnCOM2 < 0) 
                            {
                                content = "<h2>Invalid Number Of NumOfSignsOnCOM2 " + NumOfSignsOnCOM2 + "</h2>";
                                Debug.Print(content);
                                return;
                            }
                            int NumOfSignsOnCOM3 = int.Parse((string)Values["NumOfSignsOnCOM3"]);
                            if (NumOfSignsOnCOM3 < 0)
                            {
                                content = "<h2>Invalid Number Of NumOfSignsOnCOM3 " + NumOfSignsOnCOM3 + "</h2>";
                                Debug.Print(content);
                                return;
                            }


                            //get current system config data from flash memory
                            SmartTruckConfigData SmartTruckData1 = SmartTruckFlashMemory.GetSmartTruckConfigData();
                            if (SmartTruckData1 == null)
                            {
                                content = "<h2>WebEventHandler() could not get SmartTruck configuration data from Flash memory</h2>";
                                Debug.Print(content);
                                return;
                            }

                            //Save new SmartTruck options in flash memory
                            Debug.Print("WebEventHandler() will set new SmartTruck configuration into Flash memory");
                            SmartTruckConfigData SmartTruckData2 = new SmartTruckConfigData(NumOfSignsOnCOM2, NumOfSignsOnCOM3);
                            SmartTruckFlashMemory.SetSmartTruckConfigData(SmartTruckData2);
                            
                            content = "<h2>Kiosk configuration changed </h2>";
                        }
                        else if (method == WebServer.HttpMethod.GET)
                        {
                            //This page shows the SmartTruck specific options
                            PageData = (WebPage)WebPageList["smarttruck.htm"];
                            content = PageData.Content;
                            
                            //assemble a web page from system configuration data in flash memory
                            SmartTruckConfigData SmartTruckData = SmartTruckFlashMemory.GetSmartTruckConfigData();
                            if (SmartTruckData == null)
                            {
                                content = "<h2>Flash memory cannot be read</h2>";
                                return;
                            }
                            //replace placeholder text vertical bar in web page with the run-time text
                            //regular expressions are much too slow, use split function instead
                            string[] parts = content.Split(new char[] { '|' });
                            if (parts.Length != 3)
                            {
                                content = "<h2>Web page must have 2 text boxes</h2>";
                                return;
                            }

                            content = parts[0] + SmartTruckData.NumOfSignsOnCOM2.ToString()
                                    + parts[1] + SmartTruckData.NumOfSignsOnCOM2.ToString()
                                    + parts[2];
                        }
                    }
                }
                finally
                {
                    if (PageData.Url.ToLower() == "smarttruck.htm")
                    {
                        byte[] data = Encoding.UTF8.GetBytes(content);
                        responder.Respond(data, PageData.MimeType);
                    }
                }
            }
            catch (SocketException ex)
            {
                KioskUtilities.LogException("WebEventHandler", ex);
            }
            catch (Exception ex)
            {
                KioskUtilities.LogException("WebEventHandler", ex);
                KioskUtilities.Reboot();
            }
        }

        /// <summary>
        /// Sets SmartBlock business logic according to input command from DeviceHive Server
        /// Analyze the command from DeviceHive ( and GMS ) and execute
        /// </summary>
        /// <param name="cmd">Input command</param>
        /// <returns>True if successful; false - otherwise</returns>
        public override bool OnCommand(DeviceCommand cmd)
        {
            switch (cmd.command.ToUpper())
            {
                case "SETDATETIME":
                    foreach (string key in cmd.parameters.Keys)
                    {
                        switch (key.ToUpper())
                        {
                            case "DATETIME":
                                string dts = (string)cmd.parameters[key];
                                DateTime dt = KioskUtilities.JSONToDateTime(dts);
                                KioskUtilities.SetRTCData(dt);
                                break;
                        }
                    }
                    break;

                case "GETSTATUS":
                    // Query for initial status and report to DeviceHive
                    NotifyCurrentStatus();
                    break;

                case "REBOOT":
                    KioskUtilities.Reboot();
                    break;
            }
            return true;
        }

        /*
        private bool CardDetected;
        private bool PrevCardDetected;
        private bool RFIDReaderReady;
        private int CurrentDistance;
        private int PrevDistance;
        private string CurrentTagID;
        private string PrevTagID;
        private string CurrentTagChannel;
        private string PrevTagChannel;
        private string FirmwareVersion;
         */

        private bool PLCReady;
        private bool PrevPLCReady;
        private byte ErrorCode;
        private byte PrevErrorCode;
        private byte InputCoilStatusBits = 0;
        private byte PrevInputCoilStatusBits = 0;
        private bool TrafficEye0;
        private bool TrafficEye1;
        private bool TrafficEye2;
        private bool TrafficEye3;
        //private bool[] ProLiteBarsStatus;

        /// <summary>
        /// Collect status of equipment and notify DeviceHive of current status
        /// </summary>
        void NotifyCurrentStatus()
        {
            Hashtable StatusParameters;

            StatusParameters = new Hashtable();
            StatusParameters.Clear();

            StatusParameters.Add("PLC Status", (PLCReady ? "OnLine" : "OffLine"));
            StatusParameters.Add("PLC Error Code", PlcDevice.GetExceptionCodeString(ErrorCode));
            StatusParameters.Add("Front Traffic Eye X0", (TrafficEye0 ? "Blocked" : "Unblocked"));
            StatusParameters.Add("Back Traffic Eye X1", (TrafficEye1 ? "Blocked" : "Unblocked"));
            StatusParameters.Add("Left Traffic Eye X2", (TrafficEye2 ? "Blocked" : "Unblocked"));
            StatusParameters.Add("Right Traffic Eye X3", (TrafficEye3 ? "Blocked" : "Unblocked"));

            /*
            StatusParameters.Add("Reader Status", (RFIDReaderReady ? "OnLine" : "OffLine"));
            StatusParameters.Add("Tag Detected", (CardDetected ? "Yes" : "No"));
            StatusParameters.Add("Current Tag ID", CurrentTagID);
            StatusParameters.Add("Current Tag Channel", CurrentTagChannel);
            StatusParameters.Add("Tag Distance", CurrentDistance);
            StatusParameters.Add("Previous Tag ID", PrevTagID);
            StatusParameters.Add("Previous Tag Channel", PrevTagChannel);
            StatusParameters.Add("Firmware Version", FirmwareVersion);
             */ 

            if (StatusParameters.Count > 0)
                SendNotification(StatusParameters);
        }

        /*
        /// <summary>
        /// Set the red error led on or off according to the status value
        /// </summary>
        /// <param name="status">if status is true (good), turn led off; is status is false (bad), turn led on</param>
        void SetStatusLed(bool status)
        {
            oPort.Write(!status);
        }

        /// <summary>
        /// Initialize the RFID reader to operating status
        /// </summary>
        bool InitializeRFIDReader()
        {
            bool rc = false;
            //RFID reader starts up in binary communication mode after power-on
            if (PlcDevice.GetCommandPrompt())
            {
                //Turn off Service Port Mode, turn on Binary Mode
                rc = PlcDevice.SetServicePortMode(false);
                if (!rc) return rc;
            }
            rc = PlcDevice.SetCRCFlag(1);   // turn off CRC checking
            if (!rc) return rc;

            //reader.SetDataDirectionRegister(0xFE);  //Port 0 output port, Port 1 input port
            //reader.SetIOPortValue(1);   // turn off red status/error LED (Moved to the Cobra board)
            rc = PlcDevice.SetDataDirectionRegister(0xFF);  //Port 0 input port, Port 1 input port
            if (!rc) return rc;
            rc = PlcDevice.GetFirmwareVersion(out FirmwareVersion);
            if (!rc) return rc;
            Debug.Print("RFID2400 firmware version number = " + FirmwareVersion);

            rc = PlcDevice.SetServicePortMode(true);  // put reader into Service Port mode, turn off Binary Mode
            if (!rc) return rc;
            rc = PlcDevice.SetPassword(3456);   //Enable Service Port protected commands
            if (!rc) return rc;
            rc = PlcDevice.SetBeepType(0);  //turn off automatic beeping on tag read activity
            if (!rc) return rc;
            rc = PlcDevice.SetTagClass(2);  //tag class must be Generation 2
            if (!rc) return rc;

            //Set frequency hopping option from System Configuration database
            SystemConfigData systemData1 = FlashMemory.GetSystemConfigData();
            if (systemData1 == null)
            {
                Debug.Print("StatusMonitorLoop() could not get system configuration data from Flash memory - will turn on frequency hopping");
                //Frequency hopping should be on by default
                rc = PlcDevice.SetFrequencyHopping(true);
                if (!rc) return rc;
            }
            else
            {
                if (systemData1.EnableHopping)
                {
                    //Frequency hopping should be on
                    rc = PlcDevice.SetFrequencyHopping(true);
                    if (!rc) return rc;
                }
                else
                {
                    //Frequency hopping should be off, use a fixed RF channel number
                    rc = PlcDevice.SetFrequencyHopping(false);
                    if (!rc) return rc;
                    rc = PlcDevice.SetActiveChannel(systemData1.ChannelNumber);  //Call  this if Frequence Hopping is turned off
                    if (!rc) return rc;
                }
            }
            rc = PlcDevice.ReadOpticalSensor(out CardDetected, out CurrentDistance);
            if (!rc) return rc;
            return rc;
        }
         */ 

        /// <summary>
        /// Test status of equipment in the Kiosk continually, report to DeviceHive if there is a malfunction
        /// Must be done from a thread, not from a NETMF or Gadgeteer timer event, to prevent blocking the rs232 data received event.
        /// </summary>
        void StatusMonitorLoop()
        {
            try
            {
                bool NotifyNeeded = false;
                /*
                string ErrorMsg;
                CardDetected = false;
                PrevCardDetected = false;
                CurrentDistance = 0;
                PrevDistance = 0;
                CurrentTagID = "Unknown";
                PrevTagID = "Unknown";
                CurrentTagChannel = "Unknown";
                PrevTagChannel = "Unknown";
                 FirmwareVersion = "Unknown";
                 */ 

                //SetStatusLed(false);    //turn red status LED on to show it works ok
                Thread.Sleep(3000);

                //SetStatusLed(true);     //turn red status LED off
                ThisDevice.DeviceConnectedEvent.WaitOne();  //Wait until connected to a DeviceHive server
                Thread.Sleep(100);
                //RFIDReaderReady = InitializeRFIDReader();
                PLCReady = false;
                PrevPLCReady = false;
                ErrorCode =  PLCExceptionCodes.RECEIVE_SUCCESS;
                PrevErrorCode = PLCExceptionCodes.RECEIVE_SUCCESS;
                InputCoilStatusBits = 0;
                PrevInputCoilStatusBits = 0;
                TrafficEye0 =  false;
                TrafficEye1 = false;
                TrafficEye2 = false;
                TrafficEye3 = false;

                //get curent system config data from flash memory
                //SystemConfigData systemData1 = FlashMemory.GetSystemConfigData();
                //if (systemData1 == null)
                //{
                    //Debug.Print("StatusMonitorLoop could not get system configuration data from flash memory");
                    //return;
                //}

                //ProLiteBarsStatus = new bool[BusinessConstants.MaxNumProLiteBars];

                //if (RFIDReaderReady)
                //    Debug.Print("StatusMonitorLoop initialized RFID reader ok");
                //else
                //    Debug.Print("StatusMonitorLoop cannot initialize RFID reader");
                //SetStatusLed(RFIDReaderReady);
                NotifyCurrentStatus(); //Notify GMS of current pass/fail status
                ThisDevice.NotifyDeviceReady(); //Notify GMS that device is ready for commands
               
                //continually query all installed equipment for status on a time interval

                while (true)
                {
                    try
                    {
                        Thread.Sleep(1000);
                        if (ThisDevice.IsConnected)  //If connected to a DeviceHive Server
                        {

                            NotifyNeeded = false;

                            PLCReady = PlcDevice.ReadCoilStatus(out InputCoilStatusBits, out ErrorCode);
                            if (PLCReady)
                            {
                                InputCoilStatusBits &= 0x0F;
                                if (InputCoilStatusBits != PrevInputCoilStatusBits)
                                {
                                    Debug.Print("StatusMonitorLoop() - InputCoilStatus changed");
                                    PrevInputCoilStatusBits = InputCoilStatusBits;
                                    TrafficEye0 = ((InputCoilStatusBits & 0x01) == 0x01) ? true : false;
                                    TrafficEye1 = ((InputCoilStatusBits & 0x02) == 0x02) ? true : false;
                                    TrafficEye2 = ((InputCoilStatusBits & 0x04) == 0x04) ? true : false;
                                    TrafficEye3 = ((InputCoilStatusBits & 0x08) == 0x08) ? true : false;

                                    //the Show text calls are for debugging only

                                    /*
                                    Sv1Box.ShowText(1, "FA", "Box=1 Bar=1 Eyes=" + InputCoilStatusBits.ToHex());
                                    Sv1Box.ShowText(2, "FA", "Box=1 Bar=2 Eyes=" + InputCoilStatusBits.ToHex());
                                    Sv2Box.ShowText(1, "FA", "Box=2 Bar=1 Eyes=" + InputCoilStatusBits.ToHex());
                                    Sv2Box.ShowText(2, "FA", "Box=2 Bar=2 Eyes=" + InputCoilStatusBits.ToHex());
                                    PlcDevice.ForceSingleCoil(3, true, out ErrorCode);
                                    PlcDevice.ForceSingleCoil(4, true, out ErrorCode);
                                    PlcDevice.ForceSingleCoil(8, true, out ErrorCode);
                                    PlcDevice.ForceSingleCoil(9, true, out ErrorCode);
                                    Thread.Sleep(3000);
                                    Sv1Box.ShowText(1, "FH", "");
                                    Sv1Box.ShowText(2, "FH", "");
                                    Sv2Box.ShowText(1, "FH", "");
                                    Sv2Box.ShowText(2, "FH", "");
                                    PlcDevice.ForceSingleCoil(3, false, out ErrorCode);
                                    PlcDevice.ForceSingleCoil(4, false, out ErrorCode);
                                    PlcDevice.ForceSingleCoil(8, false, out ErrorCode);
                                    PlcDevice.ForceSingleCoil(9, false, out ErrorCode);
                                    */

                                    //

                                    NotifyNeeded = true;
                                }
                            }

                            if (PLCReady != PrevPLCReady)
                            {
                                Debug.Print("StatusMonitorLoop() - PLCReady Status changed");
                                PrevPLCReady = PLCReady;
                                NotifyNeeded = true;
                            }
                            if (ErrorCode != PrevErrorCode)
                            {
                                Debug.Print("StatusMonitorLoop() - ErrorCode Status changed");
                                PrevErrorCode = ErrorCode;
                                NotifyNeeded = true;
                            }

                            if (NotifyNeeded)
                                NotifyCurrentStatus();

                            /*
                            switch (RFIDReaderReady)
                            {
                                case true:
                                    StatusParameters.Clear();
                                    if (RFIDReaderReady = PlcDevice.ReadOpticalSensor(out CardDetected, out CurrentDistance))
                                    {
                                        if (CardDetected != PrevCardDetected)
                                        {
                                            Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " CardDetected = " + CardDetected.ToString() + " Distance = " + CurrentDistance.ToString());
                                            PrevCardDetected = CardDetected;
                                            StatusParameters.Add("Tag Detected", (CardDetected ? "Yes" : "No"));
                                            StatusParameters.Add("Tag Distance", CurrentDistance.ToString());
                                        }
                                        if (((CurrentDistance - PrevDistance) > 1) || ((CurrentDistance - PrevDistance) < -1 ))
                                        {
                                            Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " CardDetected = " + CardDetected.ToString() + " Distance = " + CurrentDistance.ToString());
                                            PrevDistance = CurrentDistance;
                                            if (!StatusParameters.Contains("Tag Detected"))
                                                StatusParameters.Add("Tag Detected", (CardDetected ? "Yes" : "No"));
                                            if (!StatusParameters.Contains("Tag Distance"))
                                                StatusParameters.Add("Tag Distance", CurrentDistance.ToString());
                                        }

                                        if (CardDetected)
                                        {
                                            for (int Try = 0; Try < 10; Try++)
                                            {
                                                if (PlcDevice.ReadTag(out CurrentTagID, out CurrentTagChannel, out ErrorMsg))
                                                {
                                                    PrevTagID = CurrentTagID;
                                                    PrevTagChannel = CurrentTagChannel;
                                                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " Current  TagID = " + CurrentTagID + " Current  RF Channel = " + CurrentTagChannel);
                                                    PlcDevice.SpeakerBeep();
                                                    if (!StatusParameters.Contains("Tag Detected"))
                                                        StatusParameters.Add("Tag Detected", (CardDetected ? "Yes" : "No"));
                                                    if (!StatusParameters.Contains("Tag Distance"))
                                                        StatusParameters.Add("Tag Distance", CurrentDistance);
                                                    StatusParameters.Add("Current Tag ID", CurrentTagID);
                                                    StatusParameters.Add("Current Tag Channel", CurrentTagChannel);
                                                    break;
                                                }
                                                else
                                                {
                                                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " ReadTag(): " + ErrorMsg);
                                                }
                                            }
                                        }
                                        else
                                        {
                                            CurrentTagID = "Unknown";
                                            CurrentTagChannel = "Unknown";
                                        }
                                    }
                                    if (!RFIDReaderReady )
                                    {
                                        SetStatusLed(false);
                                        StatusParameters.Add("Reader Status", (RFIDReaderReady ? "OnLine" : "OffLine"));
                                        Debug.Print("StatusMonitorLoop cannot communicate with RFID reader");
                                    }
                                    if (StatusParameters.Count > 0)
                                        SendNotification(StatusParameters);
                                    break;

                                case false:
                                    StatusParameters.Clear();
                                    RFIDReaderReady = InitializeRFIDReader();
                                    if (RFIDReaderReady)
                                    {
                                        SetStatusLed(true);
                                        StatusParameters.Add("Reader Status", (RFIDReaderReady ? "OnLine" : "OffLine"));
                                        Debug.Print("StatusMonitorLoop can communicate with RFID reader ok");
                                    }
                                    if (StatusParameters.Count > 0)
                                        SendNotification(StatusParameters);
                                    break;
                            }
                             */
                        }

                    }
                    catch (ThreadAbortException ex3)
                    {
                        KioskUtilities.LogException("Exception in StatusMonitorLoop(): ", ex3);
                    }
                    catch (WebException ex4)
                    {
                        KioskUtilities.LogException("Exception in StatusMonitorLoop(): ", ex4);
                    }
                    catch (SocketException ex5)
                    {
                        KioskUtilities.LogException("Exception in StatusMonitorLoop(): ", ex5);
                        Debug.GC(true);
                        Thread.Sleep(10); // need for GC();
                    }
                    catch (Exception ex1)
                    {
                        KioskUtilities.LogException("Exception in StatusMonitorLoop(): ", ex1);
                        KioskUtilities.Reboot();
                    }
                }   //End of while (true)
                 
            }
            catch (Exception ex2)
            {
                KioskUtilities.LogException("Could not start StatusMonitorLoop(): ", ex2 );
                KioskUtilities.Reboot();
            }
        }

        /// <summary>
        /// Registers the SmartBlock in DeviceHive Server
        /// </summary>
        /// <returns>True if successfull; false - otherwise</returns>
        public override bool Register()
        {
            bool result = false;
            string value;
            if (FlashMemory.NetworkConfigDataReadOk)
                value = "SmartBlock Kiosk up - flash memory read ok";
            else
                value = "SmartBlock - problem reading flash memory";
            result = SendNotification(value);
            return result;
        }

        /// <summary>
        /// Sends equipment state change notification
        /// </summary>
        /// <param name="value">Key value</param>
        /// <returns>True if successfull; false - otherwise</returns>
        public virtual bool SendNotification(string value)
        {
            return base.SendNotification(StateParameter, value);
        }

        /// <summary>
        /// Sends equipment name and change notification
        /// </summary>
        /// <param name="name">parameter name</param>
        /// <param name="value">parameter value</param>
        /// <returns></returns>
        public virtual bool SendNotification(string name, string value)
        {
            return base.SendNotification(name, value);
        }

        /// <summary>
        /// Sends equipment name and notification from a table of name/value pairs
        /// </summary>
        /// <param name="_parameters">name/value pair table</param>
        /// <returns></returns>
        public virtual bool SendNotification(Hashtable _parameters)
        {
            return base.SendNotificationList(_parameters);
        }

    }
}


File SmartTruckPLC.cs
This is where the RS232 write and read are performed


using System;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GT = Gadgeteer;
using GTI = Gadgeteer.Interfaces;
using GTME = Gadgeteer.Modules.GHIElectronics;
using GHI.Hardware.G120;
using DeviceHive;
using Configuration;
using System.Collections;
using KioskUtility;

namespace ProgrammableLogicControllers
{

    public class PLCExceptionCodes
    {
        //PLC Exception Codes
        //                  Name = Code                 Meaning

        public const byte RECEIVE_SUCCESS = 0x00;      //Data received ok
        public const byte ILLEGAL_FUNCTION = 0x01;      //The function code received in the query is not an allowable action for the slave.
        // If a Poll Program Complete command was issued, this code indicates that no program function preceded it.
        public const byte ILLEGAL_DATA_ADDRESS = 0x02;   //The data address received in the query is not an allowable address for the slave.
        public const byte ILLEGAL_DATA_VALUE = 0x03;     //A value contained in the query data field is not an allowable value for the slave.
        public const byte SLAVE_DEVICE_FAILURE = 0x04;  //An unrecoverable error occurred while the slave was attempting to perform the requested action.
        public const byte ACKNOWLEDGE = 0x05;            //The slave has accepted the request and is processing it, but a long duration of time will be required to do so. 
        //This response is returned to prevent a timeout error from occurring in the master. The master can next issue a
        //Poll Program Complete message to determine if processing is completed.
        public const byte SLAVE_DEVICE_BUSY = 0x06;      //The slave is engaged in processing a long-duration program command. 
        //The master should retransmit the message later when the slave is free.

        public const byte NEGATIVE_ACKNOWLEDGE = 0x07;   //The slave cannot perform the program function received in the query. 
        //This code is returned for an unsuccessful programming request using function code 13 or 14 decimal. 
        //The master should request diagnostic or error information from the slave.
        public const byte MEMORY_PARITY_ERROR = 0x08;    //The slave attempted to read extended memory, but detected a parity error in the memory. 
        //The master can retry the request, but service may be required on the slave device.
        public const byte RECEIVE_TIMEOUT = 0xFF;                 //No response from PLC in allotted time
    }


    /// <summary>
    /// The DL05PLC received bytes are stored in the DL05PLCEventArgs class
    /// </summary>
    public class DL05PLCEventArgs : EventArgs
    {
        private byte[]  _PLCBytes;
        private int _PLCCount;
        public DL05PLCEventArgs(byte[] _Data, int _Count)
        {
            _PLCBytes = _Data;
            _PLCCount = _Count;
        }
        public byte[] PLCBytes
        {
            get { return _PLCBytes; }
        }
        public int PLCCount
        {
            get { return _PLCCount; }
        }
    }

    /// <summary>
    /// The delegate for the PLC event handler
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void DL05PLCEventHandler(object sender, DL05PLCEventArgs e);


    /// <summary>
    /// The Kiosk SmartTruck DL05 PLC module
    /// </summary>
    public class DL05PLC : EquipmentEngine
    {

        private const string StateParameter = "state";
        private const string DeviceTypeName = "DirectLogic DL05 Programmable Logic Controller";

        /// <summary>The RS232 module using socket 8 of the mainboard.</summary>
        private GTME.RS232 rs232PLC;
        public event DL05PLCEventHandler PLCEvent;

        private const int MaxRxDataQueueSize = 500;   //limit on qty of chars to put on rs232 received data Queue 
        private const int IdleTimeCharacterCount = 4;
        private const int BitsPerCharacter = 11;
        private const int BaudRate = 38400;
        private const int PacketIdleTimeMs = ((1000 * IdleTimeCharacterCount * BitsPerCharacter) / BaudRate) + 1;
        private const byte PLCAddress = 1;
        private const byte READ_COIL_STATUS = 1;    // Function code to read coil status
        private const byte FORCE_COIL_STATE = 5;    // Function code to force single coil state
        private const ushort InputCoilsAddress = 3072;    //Start address of the input coils
        private const ushort OutputCoilsAddress = 5120;    //Start address of the output coils

        private ByteQueue RxDataQueue;
        private AutoResetEvent RxDataEvent;
        private int RxDataQueueSizeExpected;
        private bool ExpectByteCount;
        private byte[] RxDataBuf;
        private byte[] RxQDataBuf;
        private int RxDataCount;
        private byte[] rs232Data;
        private int rs232Count;
        //private Regex oRegex;
        private Object SendDataLock;
        private int LoggingLevel;
        private SystemConfigData systemData;
        private GT.Timer IdleTimer;
        private AutoResetEvent IdleTimerEvent;
        private DeviceEngine ThisDevice;
        //private string toFindPattern;

        /// <summary>
        /// Constructs a DL05PLC class by given parameters
        /// </summary>
        /// <param name="dev">Parent device</param>
        /// <param name="_rs232PLC">Gadgeteer RS232 port object</param>
        /// <param name="Code">Equipment code</param>
        public DL05PLC(DeviceEngine dev, GTME.RS232 _rs232PLC, SystemConfigData _systemData, string Code)
            : base(dev)
        {
            Debug.Print("Constructing " + Code);
            rs232PLC = _rs232PLC;
            code = Code;
            name = Code;
            type = DeviceTypeName; // v6
            systemData = _systemData;
            LoggingLevel = _systemData.LoggingLevel;
            ThisDevice = dev;
            RxDataQueueSizeExpected = 0;
            RxDataQueue = new ByteQueue(MaxRxDataQueueSize);
            RxDataEvent = new AutoResetEvent(false);
            SendDataLock = new Object();
            RxDataBuf = new byte[MaxRxDataQueueSize];
            RxDataCount = 0;
            rs232Data = new byte[MaxRxDataQueueSize];
            rs232Count = 0;
            RxQDataBuf = new byte[MaxRxDataQueueSize];
            rs232PLC.Initialize(BaudRate, GTI.Serial.SerialParity.None,
                                            GTI.Serial.SerialStopBits.One, 8,
                                            GTI.Serial.HardwareFlowControl.NotRequired);
            if (!ThisDevice.isPassThroughModeActive)
                rs232PLC.serialPort.DataReceived += new GTI.Serial.DataReceivedEventHandler(PLC_DataReceived);

            IdleTimer = new GT.Timer(PacketIdleTimeMs, GT.Timer.BehaviorType.RunOnce);
            IdleTimer.Tick += new GT.Timer.TickEventHandler(IdleTimer_Tick);
            IdleTimerEvent = new AutoResetEvent(false);
            Debug.Print("Done Constructing " + Code);
        }

        /// <summary>
        /// Set Event object marking the end of the serialport idle time between sending packets required by ModBus
        /// </summary>
        /// <param name="timer"></param>
        void IdleTimer_Tick(GT.Timer timer)
        {
            IdleTimerEvent.Set();
        }

        /// <summary>
        ///Send a variable length byte packet to the rs232 port for the PLC
        ///Wait for a byte string response from the PLC
        ///After sending a command to the PLC, the program must wait since the PLC
        ///will not accept any other commands while executing the previous command. 
        /// </summary>
        /// <param name="ms_time">time in milliseconds. Used for the AutoResetEvent object wait</param>
        /// <param name="ErrorCode">Error code received from PLC in case of exception</param>
        /// <param name="sendData">variable length packet of bytes to transmit</param>
        /// <returns>true if packet sent without error</returns>
        private bool SendData(int ms_time, int _RxDataQueueSizeExpected, bool _ExpectByteCount,  out byte ErrorCode, params byte[] sendData)
        {
            bool rv = false;
            ErrorCode = PLCExceptionCodes.RECEIVE_SUCCESS;
            try
            {
                RxDataCount = 0;
                if (rs232PLC.serialPort.IsOpen)
                {
                    //Enter a critical section - only one thread at a time. Don't release until transaction is finished
                    lock (SendDataLock)
                    {
                        lock (RxDataQueue.SyncRoot)
                        {
                            //thread-safe initialize the Queue of data received from the PLC reader
                            RxDataQueue.Clear();
                            RxDataEvent.Reset();
                            RxDataQueueSizeExpected = _RxDataQueueSizeExpected;
                            ExpectByteCount = _ExpectByteCount;
                        }
                        if (LoggingLevel > 0)
                        {
                            Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " SendData(): " 
                                    + KioskUtilities.BytesToHexString(sendData, sendData.Length) 
                                    + " : " + KioskUtilities.BytesToAsciiString(sendData, sendData.Length));
                        }
                        //Send the command to the PLC
                        if (IdleTimer.IsRunning)
                        {
                            /// Wait for the end of the serial port idle time between sending packets required by ModBus
                            IdleTimerEvent.WaitOne(PacketIdleTimeMs + 20, false);
                        }
                        rs232PLC.serialPort.Write(sendData);
                        rs232PLC.serialPort.Flush();
                        IdleTimer.Start();  //Mark the start of the serial port idle time between sending packets required by ModBus

                        //wait for a certain number of bytes to appear in the PLC reader data Queue
                        rv = RxDataEvent.WaitOne(ms_time + 100, false);
                        if (rv)
                        {
                            lock (RxDataQueue.SyncRoot)
                            {
                                //thread-safe capture of the received byte array from the PLC reader receive Queue
                                RxDataQueue.CopyTo(RxDataBuf, 0);
                                RxDataCount = RxDataQueue.Count;
                                ErrorCode = PLCExceptionCodes.RECEIVE_SUCCESS;
                            }
                            if (LoggingLevel > 0)
                            {
                                Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " data received: "
                                    + KioskUtilities.BytesToHexString(RxDataBuf, RxDataCount)
                                    + " : " + KioskUtilities.BytesToAsciiString(RxDataBuf, RxDataCount));
                            }

                            // Test for exception response
                            //Byte Contents Example
                            //0 Slave Address 0A
                            //1 Function Code 81  - Most significant bit (7) of byte is true if error happened
                            //2 Exception Code 02
                            //3 CRC HI
                            //4 CRC LO
                            if ( ( RxDataCount >= 3 ) && ( (RxDataBuf[1] & 0x80) == 0x80) )
                            {
                                rv = false;
                                ErrorCode = RxDataBuf[2];
                                Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code  
                                        + " SendData() Error:  PLC reported exception " + GetExceptionCodeString(ErrorCode));
                            }
                        }
                        else
                        {
                            ErrorCode = PLCExceptionCodes.RECEIVE_TIMEOUT;
                            Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " SendData() Error:  Timeout waiting for response from PLC");
                        }
                    }
                }
                else
                {
                    Debug.Print(  code + " SendData() serial port is not open");
                    rs232PLC.serialPort.Open();
                    rv = false;
                }
            }
            catch (GTI.Serial.PortNotOpenException ex)
            {
                KioskUtilities.LogException(  code + " SendData() serial port not open exception ", ex);
                rs232PLC.serialPort.Open();
                rv = false;
            }
            return rv;
        }



        /// <summary>
        /// This is the function that will be called by the rs232 module when data is available on the serial port from the PLC.
        /// </summary>
        /// <param name="sender">Object that sent called this function</param>
        /// <param name="data">serial data received</param>
        private void PLC_DataReceived(GT.Interfaces.Serial sender, System.IO.Ports.SerialData data)
        {
            try
            {
                lock (RxDataQueue.SyncRoot)
                {
                    // the BytesToRead property tells us how many bytes are waiting in a buffer.
                    rs232Count = rs232PLC.serialPort.BytesToRead;
                    if (rs232Count > MaxRxDataQueueSize)
                    {
                        rs232Count = MaxRxDataQueueSize;    //Don't overflow the allocated rs232Data byte array size
                        if (LoggingLevel > 0)
                        {
                            Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " PLC_DataReceived() error - rs232Data array is full, bytes discarded");
                        }
                    }
                    // Now read the serial port data into the rs232Data buffer, starting at the first byte.
                    rs232PLC.serialPort.Read(rs232Data, 0, rs232Count);
                    if (LoggingLevel > 1)
                    {
                        Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " Data received: "
                            + KioskUtilities.BytesToHexString(rs232Data, rs232Count)
                            + " : " + KioskUtilities.BytesToAsciiString(rs232Data, rs232Count));
                    }
                    OnDL05PLCDataReceived(new DL05PLCEventArgs(rs232Data, rs232Count));
                    // Accumulate received characters in the RxDataQueue, but prevent the Queue size from expanding forever
                    if (RxDataQueue.Count < MaxRxDataQueueSize)
                    {
                        for (int i = 0; i < rs232Count; i++)
                        {
                            if (RxDataQueue.Count < MaxRxDataQueueSize)
                            {
                                RxDataQueue.Enqueue(rs232Data[i]);
                            }
                            else
                            {
                                if (LoggingLevel > 0)
                                {
                                    Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " RxDataQueue.Count = " + RxDataQueue.Count.ToString() + " RxDataQueue is full");
                                }
                                break;
                            }
                        }
                    }

                    //compute and wait for the expected number of bytes
                    if (RxDataQueue.Count >= 3)
                    {
                        // Test for exception response
                        RxDataQueue.CopyTo(RxQDataBuf, 0);
                        if ((RxQDataBuf[1] & 0x80) == 0x80)
                        {
                            //PLC reports an exception in its response
                            //Byte Contents Example
                            //0 Slave Address 0A
                            //1 Function 81
                            //2 Exception Code 02
                            //3 CRC HI
                            //4 CRC LO
                            RxDataQueueSizeExpected = 5;
                        }
                        else
                        {
                            //PLC reports no error
                            if (ExpectByteCount)
                            {
                                //Expect to receive: header size (3 bytes) plus data length (variable) plus CRC size (2 bytes)
                                RxDataQueueSizeExpected = 3 + RxQDataBuf[2] + 2;
                            }
                        }
                    }
                    if (RxDataQueue.Count >= RxDataQueueSizeExpected)
                    {
                        RxDataEvent.Set();
                    }
                }
            }
            catch (GTI.Serial.PortNotOpenException ex2)
            {
                KioskUtilities.LogException(code + " PLC_DataReceived() port not open exception ", ex2);
            }
            catch (Exception ex1)
            {
                KioskUtilities.LogException(code + " PLC_DataReceived() exception ", ex1);
                KioskUtilities.Reboot();
            }
        }


        /// <summary>
        /// Convert the PLC exception code to a string mnemonic
        /// </summary>
        /// <param name="Code"></param>
        /// <returns>Name of exception</returns>
        public string GetExceptionCodeString(byte Code)
        {
            string rc="";
            switch ( Code )
            {
                case PLCExceptionCodes.ILLEGAL_FUNCTION:
                    rc = "ILLEGAL_FUNCTION";
                    break;
                case PLCExceptionCodes.ILLEGAL_DATA_ADDRESS:
                    rc = "ILLEGAL_DATA_ADDRESS";
                    break;
                case PLCExceptionCodes.ILLEGAL_DATA_VALUE:
                    rc = "ILLEGAL_DATA_VALUE";
                    break;
                case PLCExceptionCodes.SLAVE_DEVICE_FAILURE:
                    rc = "SLAVE_DEVICE_FAILURE";
                    break;
                case PLCExceptionCodes.ACKNOWLEDGE:
                    rc = "ACKNOWLEDGE";
                    break;
                case PLCExceptionCodes.SLAVE_DEVICE_BUSY:
                    rc = "SLAVE_DEVICE_BUSY";
                    break;
                case PLCExceptionCodes.NEGATIVE_ACKNOWLEDGE:
                    rc = "NEGATIVE_ACKNOWLEDGE";
                    break;
                case PLCExceptionCodes.MEMORY_PARITY_ERROR:
                    rc = "MEMORY_PARITY_ERROR";
                    break;
                case PLCExceptionCodes.RECEIVE_SUCCESS:
                    rc = "RECEIVE_SUCCESS";
                    break;
                case PLCExceptionCodes.RECEIVE_TIMEOUT:
                    rc = "RECEIVE_TIMEOUT";
                    break;
                default:
                    rc = "UNKNOWN_EXCEPTION_CODE";
                    break;
            }
            return rc;
        }

        /// <summary>
        /// Poll rs232PLC serial port for received data - For use in Pass-Through Mode
        /// </summary>
        /// <param name="RxBuf"></param>
        /// <param name="RxSize"></param>
        /// <returns></returns>
        public bool GetPLCData(byte[] RxBuf, out int RxSize)
        {
            bool rv = false;
            RxSize = 0;
            int RxCurrentTotal = 0;
            int RxCurrentStart = 0;
            int RxCurrentCount = 0;
            lock (RxDataQueue.SyncRoot)
            {
                //GHI serialPort reports data available in chunks, need a loop to read it all
                while ((RxCurrentCount = rs232PLC.serialPort.BytesToRead) > 0)
                {
                    RxCurrentStart = RxCurrentTotal;
                    RxCurrentTotal = RxCurrentTotal + RxCurrentCount;
                    if (RxCurrentTotal > RxBuf.Length)
                    {
                        //Don't overflow the allocated rs232Data byte array size
                        if (LoggingLevel > 0)
                        {
                            Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code
                                + " RS232Total = " + RxCurrentTotal.ToString() 
                                + " Error - RxBuf array is full, bytes will be discarded");
                        }
                        rs232PLC.serialPort.DiscardInBuffer();
                        break;
                    }
                    else
                    {
                        // Now read the serial port data into the Data buffer, starting at current index RS232Start.
                        rs232PLC.serialPort.Read(RxBuf, RxCurrentStart, RxCurrentCount);
                        RxSize = RxCurrentTotal;
                        rv = true;
                        if (LoggingLevel > 1)
                        {
                            Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " Data Received: "
                                + KioskUtilities.BytesToHexString(RxBuf, RxCurrentTotal)
                                + " : " + KioskUtilities.BytesToAsciiString(RxBuf, RxCurrentTotal));
                        }
                        Thread.Sleep(20);
                    }
                }
            }
            return rv;
        }


        /// <summary>
        /// Send byte array to PLC rs232 port
        /// Do not wait for a response packet from rs232 port
        /// Used for pass-through mode for GMS computer
        /// </summary>
        /// <param name="_Data">byte array to send</param>
        /// <param name="Count">number of bytes to send</param>
        public void SendBytesRaw(byte[] Data, int Count)
        {
            try
            {
                if (rs232PLC.serialPort.IsOpen)
                {
                    //Enter a critical section - only one thread at a time
                    lock (SendDataLock)
                    {
                        lock (RxDataQueue.SyncRoot)
                        {
                            //thread-safe initialize the Queue of data received from the RFID2400
                            RxDataQueue.Clear();
                            RxDataEvent.Reset();
                            RxDataQueueSizeExpected = 0;
                        }
                        rs232PLC.serialPort.Write(Data, 0, Count);
                        rs232PLC.serialPort.Flush();
                    }
                }
                else
                {
                    Debug.Print( code + " SendBytesRaw() serial port is not open");
                    rs232PLC.serialPort.Open();
                }
            }
            catch (GTI.Serial.PortNotOpenException ex)
            {
                KioskUtilities.LogException( code + " SendBytesRaw() serial port is not open", ex);
                rs232PLC.serialPort.Open();
            }
        }

        /// <summary>
        /// Fires the PLC event if an event handler method is registered
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnDL05PLCDataReceived(DL05PLCEventArgs e)
        {
            if (PLCEvent != null)
                PLCEvent(this, e);
        }

        
        //This member function performs calculation of CRC value over the internal buffer's
        //content and returns the computed CRC code
        ushort CalcCRC( byte[] buf, ushort Len,  out byte CRC_Hi, out byte CRC_Lo )
        {
          ushort i,j,k;
          ushort rc;

          //Initialize the CRC seeds
          CRC_Hi = 0xFF; CRC_Lo = 0xFF;

          //Calculate combined CRC for all bytes in the message buffer
          j = 0;
          k = Len;
          while( (k--) > 0  )
          {
            i = (byte)(CRC_Hi ^ buf[j++]);
            CRC_Hi = (byte)(CRC_Lo ^ CRC16_TableHi[i]);
            CRC_Lo = CRC16_TableLo[i];
          }
          rc = (ushort)((CRC_Hi << 8) | CRC_Lo);
          return rc;
        }


        //Modbus CRC-16 - High Order Byte Table
        private byte[] CRC16_TableHi = new byte[256] 
        {
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
        0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
        0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
        } ;


        //Modbus CRC-16 - Low Order Byte Table
        private byte[] CRC16_TableLo = new byte[256]
        {
        0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
        0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
        0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
        0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
        0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
        0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
        0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
        0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
        0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
        0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
        0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
        0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
        0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
        0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
        0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
        0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
        0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
        0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
        0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
        0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
        0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
        0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
        0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
        0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
        0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
        0x43, 0x83, 0x41, 0x81, 0x80, 0x40
        } ;

        /// <summary>
        /// Build the Modbus command packet for reading input coil status bytge
        /// </summary>
        /// <param name="SLAVEADDR"></param>
        /// <param name="FUNCTIONCODE"></param>
        /// <param name="STARTINGCOIL"></param>
        /// <param name="NUMCOILS"></param>
        /// <returns></returns>
        private byte[] MakeReadCoilStatusPacket(byte SLAVEADDR, byte FUNCTIONCODE, ushort STARTINGCOIL, ushort NUMCOILS)
        {
            byte[] Packet = new byte[8];
            byte CRC_Hi, CRC_Lo;

            Packet[0] = SLAVEADDR;
            Packet[1] = FUNCTIONCODE;
            Packet[2] = (byte)(STARTINGCOIL >> 8);
            Packet[3] = (byte)(STARTINGCOIL & 0x00FF);
            Packet[4] = (byte)(NUMCOILS >> 8);
            Packet[5] = (byte)(NUMCOILS & 0x00FF);
            CalcCRC(Packet, 6, out  CRC_Hi, out CRC_Lo);
            Packet[6] = CRC_Hi;
            Packet[7] = CRC_Lo;
            return Packet;
        }

        /// <summary>
        /// Read the input coil status byte from the PLC, and return an error code on failure
        /// </summary>
        /// <param name="CoilStatusBits"></param>
        /// <param name="ErrorCode"></param>
        /// <returns></returns>
        public bool ReadCoilStatus(out byte CoilStatusBits, out byte ErrorCode )
        {
            CoilStatusBits = 0;
            bool rv;
            
            /*
            EXAMPLE - Read Coil Status command
            0   Slave Address 11
            1   Function 01
            2   Starting Address Hi 00
            3   Starting Address Lo 13
            4   No. of Points Hi 00
            5   No. of Points Lo 25
            6-7 Error Check (LRC or CRC) --
            Response:
            0   Slave Address 11
            1   Function 01
            2   Byte Count 05
            3   Data (Coils 27-20) CD
            4   Data (Coils 35-28) 6B
            5   Data (Coils 43-36) B2
            6   Data (Coils 51-44) 0E
            7   Data (Coils 56-52) 1B
             */
            rv = SendData(10000, 3, true, out ErrorCode, MakeReadCoilStatusPacket(PLCAddress, READ_COIL_STATUS, InputCoilsAddress, 8));
            if ( rv == true)
            {
                CoilStatusBits = RxDataBuf[3];
            }
            return rv;
        }

        private byte[] MakeForceSingleCoilPacket(byte SLAVEADDR, byte FUNCTIONCODE, ushort COILADDR, bool CoilState)
        {
            byte[] Packet = new byte[8];
            byte CRC_Hi, CRC_Lo;

            /*
            The query message specifies the coil reference to be forced. Coils are addressed
            starting at zero: coil 1 is addressed as 0.
            The reguested ON/OFF state is specified by a constant in the query data field.
            A value of FF 00 hex requests the coil to be ON. A value of 00 00 requests it to be OFF. 
            All other values are illegal and will not affect the coil.

            Here is an example of a request to force coil 173 ON in slave device 17:
            Field Name (Hex)
            0   Slave Address 11
            1   Function 05
            2   Coil Address Hi 00
            3   Coil Address Lo AC
            4   Force Data Hi FF
            5   Force Data Lo 00
            6   Error Check (LRC or CRC)
            */
            Packet[0] = SLAVEADDR;
            Packet[1] = FUNCTIONCODE;
            Packet[2] = (byte)(COILADDR >> 8);
            Packet[3] = (byte)(COILADDR & 0x00FF);
            if (CoilState)
            {
                Packet[4] = 0xFF;
                Packet[5] = 0x00;
            }
            else
            {
                Packet[4] = 0x00;
                Packet[5] = 0x00;
            }
            CalcCRC(Packet, 6, out  CRC_Hi, out CRC_Lo);
            Packet[6] = CRC_Hi;
            Packet[7] = CRC_Lo;
            return Packet;
        }

        /// <summary>
        /// Force a single output coil on or off
        /// </summary>
        /// <param name="CoilNumber"></param>
        /// <param name="CoilState"></param>
        /// <param name="ErrorCode"></param>
        /// <returns></returns>
        public bool ForceSingleCoil(ushort CoilNumber, bool CoilState, out byte ErrorCode)
        {
            bool rv;
            /*
            Here is an example of a response to the query 
            Field Name (Hex)
            0   Slave Address 11
            1   Function 05
            2   Coil Address Hi 00
            3   Coil Address Lo AC
            4   Force Data Hi FF
            5   Force Data Lo 00
            6   Error Check CRC Hi
            7   Error Check CRC Lo
             */
            rv = SendData(10000,  8, false, out ErrorCode, MakeForceSingleCoilPacket(PLCAddress, FORCE_COIL_STATE, (ushort)(OutputCoilsAddress+CoilNumber), CoilState));

            if (rv == true)
            {
            }


            return rv;
        }


        /// <summary>
        /// Sets KeyBoard according to input command from DeviceHive Server
        /// </summary>
        /// <param name="cmd">Input command</param>
        /// <returns>True if successful; false - otherwise</returns>
        public override bool OnCommand(DeviceCommand cmd)
        {
            try
            {
                foreach (string key in cmd.parameters.Keys)
                {
                    switch (key.ToLower())
                    {
                        case "report":
                            string value = "Baud Rate=" + rs232PLC.serialPort.BaudRate.ToString()
                                           + " Parity=" + rs232PLC.serialPort.Parity.ToString()
                                           + " Stop Bits=" + rs232PLC.serialPort.StopBits.ToString()
                                           + " Data Bits=" + rs232PLC.serialPort.DataBits.ToString()
                                           + " Flow Control=" + rs232PLC.serialPort.UsingHardwareFlowControl.ToString();
                            SendNotification("RS-232", value);
                            break;
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                string msg = ex.ToString();
                return false;
            }
        }

        
        /// <summary>
        /// Registers the SmartTruckPLC
        /// </summary>
        /// <returns>True if successfull; false - otherwise</returns>
        public override bool Register()
        {
            string value;
            bool result=false;
            try
            {
                if (rs232PLC.serialPort.IsOpen)
                    value = "SmartTruck PLC started - serial port is open";
                else
                    value = "SmartTruck PLC stopped - serial port is closed";
                result = SendNotification(value);
            }
            catch (Exception ex)
            {
                Debug.Print("SmartTruck PLC Register() exception - " + ex.ToString());
            }
            return result;
        }

        
        /// <summary>
        /// Sends equipment state change notification
        /// </summary>
        /// <param name="value">Key value</param>
        /// <returns>True if successfull; false - otherwise</returns>
        public virtual bool SendNotification(string value)
        {
            return base.SendNotification(StateParameter, value);
        }

        /// <summary>
        /// Sends equipment name and change notification
        /// </summary>
        /// <param name="name">parameter name</param>
        /// <param name="value">parameter value</param>
        /// <returns></returns>
        public virtual bool SendNotification(string name, string value)
        {
            return base.SendNotification(name, value);
        }
       
    }
}


@ dspacek - you really expect someone to go though all this code?

How about a simple program which demonstrates the way you are doing serial comm and exhibits the problem? That is a more reasonable approach to getting assistance.

BTW, if you have a lot of Debug.Print messages and are attached to a debugger, you can have problems with lost data.

Yes, I know it is a lot. I am working toward a deadline, and do not have time right now to make a simple project. Also, a simple project may not be able to demonstrate the problem, because it will lack all the other processing that is going on. But I will try when I get time in a few weeks from now. The code I uploaded shows everything that is going on in other threads. You could just search for the functions I mentioned and see that the RS232 write function is being called from a new thread, once per second, and the RS232 data received event handler is a GHI RS232 event handler.

Will I have problems with lost data if the Debug.Print statements are sending data to a connected MFDeploy?

Uffff…!!
…to much stuff for me.
But anyway, are you really sure that the send method and the PLC_DataReceived eventhandler run in different threads. I’m not convinced, but may be I’m wrong.
I would try to call the send method from a separate thread in which a while(true) loop runs which fetches the send data from a “buffer” and poll a flag whether something is there to be sent.

Sorry for all the code. I think I am doing what you are suggesting.
In businesslogic.cs, there is a thread function

void StatusMonitorLoop()

In this thread function there is a call to ReadCoilStatus()


while (true)
                {
                    try
                    {
                        Thread.Sleep(1000);
                        if (ThisDevice.IsConnected)  //If connected to a DeviceHive Server
                        {

                            NotifyNeeded = false;

                            PLCReady = PlcDevice.ReadCoilStatus(out InputCoilStatusBits, out ErrorCode);
                            if (PLCReady)


In SmartTruckPLC.cs, ReadCoilStatus() calls SendData(), SendData() calls

rs232PLC.serialPort.Write(sendData);
rs232PLC.serialPort.Flush();

Then SendData() waits for received data to appear in the receive queue by waiting on an event object.

The data received event handler is registered with the gadgeteer RS232 module like this:

rs232PLC.serialPort.DataReceived += new GTI.Serial.DataReceivedEventHandler(PLC_DataReceived);

When PLC_DataReceived is called by the framework, it puts the data into the receive queue and sets an event object.

I think, in NETMF a timer.tick is not a new thread and the PLC_DataReceived eventhandler is not a new thread as well. They are not asynchronous eventhandlers.

So what you are saying is that I should not use the Gadgeteer RS232 module event property to register an event handler function? Instead, I am supposed to create my own thread and from that thread poll the serial port for received characters using the rs232.serialPort.BytesToRead() fuction and the rs232.serialPort.Read() function every few milliseconds?

I think that there are two ways one can go.

  1. As you explained in your last post: In a instance of a separate Class starts a new thread with a loop that polls the serial port as you wrote, and if new data are there, transmits the data via a delegate and eventhandler.
    An example for this way can be seen in the driver of the GHI Bluetooth module.

  2. Use the eventhandler of the serial port as before. In a separate class you have a public variable (e.g. string) in which you can write the stuff to be send and a bool as flag which you can set from outside if the stuff to be send is there. Some thread locking should be needed here.
    In this class you start a new thread like


public string stuffToSend;
public bool FlagSomethingToSend;

..
..
writerThread = new Thread(new ThreadStart(runWriterThread));
       writerThread.Start();
        

        void runWriterThread()
        {
            while (true)
            {
                if (FlagSomethingToSend)
                {
                    rs232.serialPort.WriteLine(stuffToSend);
                    stuffToSend = "";
                    FlagSomethingToSend = false;
                }
                Thread.Sleep(100);

            }
        }



The important thing is - as I think - that read and write are done from different threads.

Thanks, I will try number 1, polling the serial port in one thread and sending to the serial port in another thread. I will not be able to try this right now, I have deadlines for the next few weeks.

Does anyone know this - Is there any advantage to registering an event handler in the GHI rs232 module over polling the rs232 module for received characters with my code? Is the GHI event handler driven by a hardware interrupt from a UART chip? Or is GHI software doing polling of the UART? An hardware interrupt handler would not miss characters. If the GHI rs232 module class is doing polling of the UART to call the event handler function I register, then characters can be missed, and there is no advantage, I can poll the UART in my own code.

Hi,
not an answer to your last question, but to links I found to this issue.

Here is an example from msdn how read and write over one serial port can be handled.

Cheers
Roland

Hi,
I think that I found another bug in your code:

 
private void PLC_DataReceived(GT.Interfaces.Serial sender, System.IO.Ports.SerialData data)

// the BytesToRead property tells us how many bytes are waiting in a buffer.
                     //rs232Count = rs232PLC.serialPort.BytesToRead;
                     rs232Count = sender.BytesToRead;
                     if (rs232Count > MaxRxDataQueueSize)
                     {
                         rs232Count = MaxRxDataQueueSize;    //Don't overflow the allocated rs232Data byte array size
                         if (LoggingLevel > 0)
                         {
                             Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " PLC_DataReceived() error - rs232Data array is full, bytes discarded");
                         }
                     }
                     // Now read the serial port data into the rs232Data buffer, starting at the first byte.
                    // rs232PLC.serialPort.Read(rs232Data, 0, rs232Count);
                    sender.Read(rs232Data, 0, rs232Count);