//Get Number of Records. -1 If Error
        public int GetNumRecords()
        {
            byte[] recordResponse = SendCommand(WGParams.CommCmd.COMM_GETRECORDNUM, null);
            if (recordResponse == null)
            {
                return(-1);
            }


            return((int)WGTools.NetToUInt32(recordResponse, 0));
        }
        public ControllerRecord[] GetOneRecord(UInt32 recordNum)
        {
            byte[] payload = new byte[34];
            WGTools.UInt32ToNet(recordNum, ref payload, 0);

            byte[] recordResponse = SendCommand(WGParams.CommCmd.COMM_GETONERECORD, payload);

            if (recordResponse != null)
            {
                return(ControllerRecord.ParseRecordFromRecord(recordResponse));
            }

            return(null);
        }
        public void OpenDoor(int doorIndex)
        {
            if (doorIndex < 0 || doorIndex > 3)
            {
                return;
            }

            doorIndex++; //Input Zero Based. Controller Takes 1 Based For This Command.

            byte[] payload = WGTools.strTobyte("0" + doorIndex.ToString() + "01000000000000000000000000000000000000000000000000");


            byte[] response = SendCommand(WGParams.CommCmd.COMM_OPEN_DOOR, payload);
        }
        public void WatchTask(EventHandler <ControllerRecord> watchDelegate)
        {
            //Loop Here and Update
            const int waitMS = 500;
            //Exit Signal


            UInt32 getRecordIndex = 0;

            while (true)
            {
                byte[] sendPayload = new byte[26];

                WGTools.UInt32ToNet(getRecordIndex, ref sendPayload, 0);

                byte[] watchResponse = SendCommand(WGParams.CommCmd.COMM_WATCH, null);

                if (watchResponse == null)
                {
                    if (watchCancelEvent.WaitOne(waitMS))
                    {
                        break;
                    }
                    else
                    {
                        continue;
                    }
                }

                ControllerRecord rec = ControllerRecord.ParseRecordFromWatch(watchResponse);

                //Dont record if parse failed
                if (rec == null)
                {
                    continue;
                }


                getRecordIndex = rec.recordIndex++;

                lock (WatchRecords)
                {
                    if (!WatchRecords.Contains(rec))
                    {
                        WatchRecords.Add(rec);
                        if (watchDelegate != null)
                        {
                            watchDelegate(this, rec);
                        }
                    }
                }



                //Delay Until Next Update Period
                if (watchCancelEvent.WaitOne(waitMS))
                {
                    break;
                }
                else
                {
                    continue;
                }
            }
        }
        //Parse Record From Data String
        public static ControllerRecord ParseRecordFromWatch(byte[] recordPayload)
        {
            String           debugWatch = BitConverter.ToString(recordPayload).Replace("-", "");
            ControllerRecord newRecord  = new ControllerRecord();

            //Response is by byte, 2 digit year, month, weekday, date, hour, min, seconds.
            //Ignore weekday.
            //Format is in BCD
            try
            {
                newRecord.controllerDateTime = new DateTime(2000 + Int32.Parse(recordPayload[0].ToString("X")),
                                                            Int32.Parse(recordPayload[1].ToString("X")),
                                                            Int32.Parse(recordPayload[2].ToString("X")),
                                                            Int32.Parse(recordPayload[4].ToString("X")),
                                                            Int32.Parse(recordPayload[5].ToString("X")),
                                                            Int32.Parse(recordPayload[6].ToString("X")));
            }
            catch (Exception e)   //Catch if Data is out of Range
            {
                return(null);
            }

            //I don't know why the record index is split across bytes but it is. I suspect that the high byte is a memory page number.
            //Look The original code was using 1 Indexed VB Strings and Concatenation To do this.

            UInt32 lng1 = ((UInt32 )WGTools.NetToUInt16(recordPayload, 7) << 8) + (UInt32)(recordPayload[9] & 0x0F);
            UInt32 lng2 = ((UInt32)WGTools.NetToUInt16(recordPayload, 10) << 8) + (UInt32)((recordPayload[9] & 0xF0) >> 4);



            //Check if Card Data Available
            if ((WGTools.NetToUInt32(recordPayload, 12) != 0xFFFFFFFF) && (WGTools.NetToUInt32(recordPayload, 16) == 0xFFFFFFFF))
            {
                //Lost of Door Status Stuff HEre. Ignore it for now
                return(null);
            }

            newRecord.responseType = recordPayload[25];

            newRecord.is10Digit = false;
            //Read In Card Data
            if ((newRecord.responseType & 0x04) > 0)
            {
                newRecord.cardID     = WGTools.NetToUInt32(recordPayload, 12);
                newRecord.statusByte = recordPayload[20];

                newRecord.flagsByte    = (byte)((newRecord.statusByte >= 128) ? 0 : 1); //Scan Success?
                newRecord.readDateTime = WGTools.NetToDateTime32(recordPayload, 16);

                if (newRecord.readDateTime == DateTime.MinValue)
                {
                    return(null);
                }

                if ((newRecord.responseType & 0x08) > 0)
                {
                    newRecord.readDateTime.AddSeconds(1.0);
                }

                if ((newRecord.responseType & 1L) > 0)
                {
                    newRecord.is10Digit = true;
                }

                return(newRecord);
            }
            else
            {
                newRecord.cardID = (UInt32)WGTools.NetToUInt16(recordPayload, 19) + (UInt32)recordPayload[21] * 0x10000;

                newRecord.statusByte = recordPayload[15];
                newRecord.flagsByte  = (byte)((newRecord.statusByte >= 128) ? 0 : 1);  //Scan Success?

                newRecord.readDateTime = WGTools.NetToDateTime32(recordPayload, 16);

                if (newRecord.readDateTime == DateTime.MinValue)
                {
                    return(null);
                }

                return(newRecord);
            }

            return(null);
        }
        //SendCommand Helper
        private byte[] SendCommand(byte[] commCmd, byte[] payload)
        {
            //Standard Command is 34 bytes long. TODO: Add helper for specialized longer commands.
            byte[] dGram = new byte[34];

            //Set Start and End Flags
            dGram[0]  = WGParams.CommFlag.COMM_STARTFLAG;
            dGram[33] = WGParams.CommFlag.COMM_ENDFLAG;

            //Set Controller ID
            WGTools.UInt16ToNet(Connection.ID, ref dGram, 1);

            //Copy Command
            Array.Copy(commCmd, 0, dGram, 3, 2);

            //Copy Payload
            if (payload != null)
            {
                Array.Copy(payload, 0, dGram, 5, 26);
            }

            //Calculate Checksum
            UInt16 checksum = 0;

            for (int i = 1; i <= 30; i++)
            {
                checksum += dGram[i];
            }

            //Copy Checksum
            WGTools.UInt16ToNet(checksum, ref dGram, 31);

            //Send Data And Wait For Response
            UdpClient client = new UdpClient(new IPEndPoint(Connection.HostIP, 0));



            IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, 60000);
            int        snt      = client.Send(dGram, dGram.Length, endPoint);

            byte[] recvBytes;
            try
            {
                //Wait Up to 1 Second for Response
                client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000);
                IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);

                recvBytes = client.Receive(ref remoteEP);
            }
            catch (Exception x)
            {
                //UDP Failed.
                return(null);
            }


            //Verify bytes receive is long enough for our checks
            if (recvBytes.Length <= 8)  //Start/End Flag, ControllerID, Command, CheckSum
            {
                return(null);
            }

            //Verify Magic Bytes
            if (recvBytes[0] != WGParams.CommFlag.COMM_STARTFLAG)
            {
                return(null);
            }
            if (recvBytes[recvBytes.Length - 1] != WGParams.CommFlag.COMM_ENDFLAG)
            {
                return(null);
            }

            //Verify Controller Index
            UInt16 remoteID = WGTools.NetToUInt16(recvBytes, 1);

            if (remoteID != this.Connection.ID)
            {
                return(null);
            }

            //Verify Controller Command Response
            UInt16 remoteCmd = WGTools.NetToUInt16(recvBytes, 3);
            UInt16 localCmd  = WGTools.NetToUInt16(commCmd, 0);

            if (remoteCmd != localCmd)
            {
                return(null);
            }


            //Verify Checksum

            //Calculate Checksum
            UInt16 recvCheckSum = 0;

            for (int i = 1; i <= recvBytes.Length - 4; i++)
            {
                recvCheckSum += recvBytes[i];
            }

            UInt16 recvActCheckSum = WGTools.NetToUInt16(recvBytes, recvBytes.Length - 3);

            if (recvActCheckSum != recvCheckSum)
            {
                return(null);
            }


            //TODO: Verify Payload Length based on command

            byte[] recvPayload = new byte[recvBytes.Length - 8];
            Array.Copy(recvBytes, 5, recvPayload, 0, recvPayload.Length);

            return(recvPayload);
        }
        public static List <WGController> ScanNet(UInt16 udpPort)
        {
            //Get a List of all NICS, and set up a UDPClient ON Each
            NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();

            List <UdpClient> clients = new List <UdpClient>();

            foreach (NetworkInterface n in nics)
            {
                if (n.NetworkInterfaceType == NetworkInterfaceType.Loopback)
                {
                    continue; //Ignore Loopback Interface
                }
                if (n.OperationalStatus != OperationalStatus.Up)
                {
                    continue; //This adapter is off or not connected
                }
                IPInterfaceProperties ipProps = n.GetIPProperties();
                if (ipProps == null)
                {
                    continue; //IP Not Supported
                }
                IPv4InterfaceProperties ip4Props = ipProps.GetIPv4Properties();
                if (ip4Props == null)
                {
                    continue; //IPv4 Not Supported
                }
                //Build UDPClient List

                foreach (UnicastIPAddressInformation uc in ipProps.UnicastAddresses)
                {
                    if (uc.Address.AddressFamily != AddressFamily.InterNetwork)
                    {
                        continue;
                    }


                    IPEndPoint listenEP  = new IPEndPoint(uc.Address, 0);
                    UdpClient  udpClient = new UdpClient(listenEP);

                    clients.Add(udpClient);
                }
            }


            //Broadcast Status Request to All Interfaces

            foreach (UdpClient c in clients)
            {
                byte[] dgram = WGTools.strTobyte("7EFFFF0111000000000000000000000000000000000000000000000000000010020D");

                IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, 60000);
                int        snt      = c.Send(dgram, dgram.Length, endPoint);
            }

            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();



            List <ConnectionInfo.ScanResponse> recvResp = new List <ConnectionInfo.ScanResponse>();

            //Listen for 1 second on each interface

            while (watch.ElapsedMilliseconds < 1500)
            {
                foreach (UdpClient c in clients)
                {
                    try
                    {
                        while (true)
                        {
                            //IPEndPoint object will allow us to read datagrams sent from any source.
                            IPEndPoint remEP = new IPEndPoint(IPAddress.Any, 0);
                            c.Client.ReceiveTimeout = 100;
                            byte[] recvBytes = c.Receive(ref remEP);

                            ConnectionInfo.ScanResponse s = new ConnectionInfo.ScanResponse();
                            s.ep        = (IPEndPoint)c.Client.LocalEndPoint;
                            s.respBytes = recvBytes;
                            lock (recvResp)
                            {
                                recvResp.Add(s);
                            }
                        }
                    }
                    catch //Task Done if Socket is closed, or other exception
                    { }
                }
            }

            //Close All the Clientss
            foreach (UdpClient c in clients)
            {
                c.Close();
            }

            List <WGController> controllers = new List <WGController>();

            //Parse Received Bytes
            foreach (ConnectionInfo.ScanResponse s in recvResp)
            {
                byte[] buf = s.respBytes;

                if (buf.Length != 34)
                {
                    continue;  //Verify Packet Length
                }
                if (buf[33] != (byte)13 | buf[0] != (byte)126)
                {
                    continue; //Verify Start and End Bytes
                }
                long checksum = 0;

                for (int i = 1; i <= 30; i++)
                {
                    checksum += buf[i];
                }

                //CheckSum
                if (checksum != (buf[32] * 256L + buf[31]))
                {
                    continue;  //Verify Checksum
                }
                string byteString = WGTools.byteTostr(buf);

                string tst = byteString.Substring(4, 4);
                if (byteString.Substring(6, 4) != "0111")
                {
                    continue; //Validate Response Type
                }
                WGController controller = new WGController();
                controller.Connection.ID  = WGTools.NetToUInt16(buf, 1);
                controller.Connection.MAC = WGTools.NetToMacString(buf, 5);

                controller.Connection.IP_Address = WGTools.NetToIPString(buf, 11); // $"{buf[12]}.{buf[13]}.{buf[14]}.{buf[15]}";
                controller.Connection.Netmask    = WGTools.NetToIPString(buf, 15); //$"{buf[16]}.{buf[17]}.{buf[18]}.{buf[19]}";
                controller.Connection.Gateway    = WGTools.NetToIPString(buf, 19); //$"{buf[20]}.{buf[21]}.{buf[22]}.{buf[23]}";


                controller.Connection.udpPort  = WGTools.NetToUInt16(buf, 23);
                controller.Connection.Password = WGTools.NetToUInt32(buf, 25);

                controller.Connection.HostIP = s.ep.Address;

                controllers.Add(controller);
            }
            return(controllers);
        }
        //Records have 2x records per transaction
        public static ControllerRecord[] ParseRecordFromRecord(byte[] recordPayload)
        {
            ControllerRecord[] newRecords = new ControllerRecord[2];

            //First Record
            //*******************************************

            newRecords[0]           = new ControllerRecord();
            newRecords[0].is10Digit = false;

            newRecords[0].cardID = (UInt32)WGTools.NetToUInt16(recordPayload, 0) + (UInt32)recordPayload[2] * 0x10000;

            if (newRecords[0].cardID == 22202125)
            {
                return(null);
            }

            newRecords[0].statusByte = recordPayload[3];
            newRecords[0].flagsByte  = (byte)((newRecords[0].statusByte >= 128) ? 0 : 1); //Scan Success? //num5  "Character"

            newRecords[0].readDateTime = WGTools.NetToDateTime32(recordPayload, 4);
            if (newRecords[0].readDateTime == DateTime.MinValue)
            {
                return(null);
            }

            //Check if Card Data Available
            if ((WGTools.NetToUInt64(recordPayload, 8) == 0xFFFFFFFFFFFFFFFF))
            {
                return(null);
            }

            //Get Record ID
            if ((WGTools.NetToUInt32(recordPayload, 8) != 0x00000000) && (WGTools.NetToUInt32(recordPayload, 8) != 0xFFFFFFFF))
            {
                newRecords[0].recordIndex = WGTools.NetToUInt32(recordPayload, 8);
            }

            //Record Local Time
            newRecords[0].localTime = DateTime.Now;

            if (newRecords[0].cardID == 25565535L)  //Special card ID. Not sure what this actually means
            {
                newRecords[0] = null;
            }


            //Second Record
            //*******************************************

            newRecords[1]           = new ControllerRecord();
            newRecords[1].is10Digit = false;

            newRecords[1].cardID = (UInt32)WGTools.NetToUInt16(recordPayload, 12) + (UInt32)recordPayload[14] * 0x10000;

            if (newRecords[1].cardID == 22202125)
            {
                newRecords[1] = null;
                return(newRecords);
            }

            newRecords[1].statusByte = recordPayload[15];
            newRecords[1].flagsByte  = (byte)((newRecords[1].statusByte >= 128) ? 0 : 1); //Scan Success? //num5  "Character"

            newRecords[1].readDateTime = WGTools.NetToDateTime32(recordPayload, 16);
            if (newRecords[1].readDateTime == DateTime.MinValue)
            {
                newRecords[1] = null;
                return(newRecords);
            }

            //Check if Card Data Available
            if (WGTools.NetToUInt64(recordPayload, 20) == 0xFFFFFFFFFFFFFFFF)
            {
                newRecords[1] = null;
                return(newRecords);
            }

            //Get Record ID
            if ((WGTools.NetToUInt32(recordPayload, 20) != 0x00000000) && (WGTools.NetToUInt32(recordPayload, 20) != 0xFFFFFFFF))
            {
                newRecords[1].recordIndex = WGTools.NetToUInt32(recordPayload, 20);
            }

            //Record Local Time
            newRecords[1].localTime = DateTime.Now;

            if (newRecords[1].cardID == 25565535L)  //Special card ID. Not sure what this actually means
            {
                newRecords[1] = null;
                return(newRecords);
            }


            return(newRecords);
        }