// Predict coming time to remaining node on the path (first element is now time of current node)
        private static List <DateTime> PredictTimeOnPath(AGV agv, DateTime timeNow)
        {
            // matrix of distance from node to other node (pixel)
            int[,] D = Node.MatrixNodeDistance;

            List <DateTime> timePath = new List <DateTime>();

            timePath.Add(timeNow);
            foreach (int n in agv.Path)
            {
                // don't predict node which is passed and current node
                if (agv.Path.IndexOf(n) <= agv.Path.IndexOf(agv.ExitNode))
                {
                    continue;
                }

                int   pre_n = agv.Path[agv.Path.IndexOf(n) - 1];
                float disToPreNode;
                // disToPreNode is different in this case (pre_n is current node)
                if (agv.Path.IndexOf(n) == agv.Path.IndexOf(agv.ExitNode) + 1)
                {
                    disToPreNode = D[agv.ExitNode, n] / Display.Scale - agv.DistanceToExitNode;
                }
                else
                {
                    disToPreNode = D[pre_n, n] / Display.Scale;
                }
                float time_Line = disToPreNode / AGV.SimSpeed;

                float  time_Turning;
                int    pre_idx = Array.IndexOf(agv.navigationArr, pre_n.ToString());
                string pre_dir = agv.navigationArr[pre_idx + 1];
                if (pre_dir == "L" || pre_dir == "R")
                {
                    time_Turning = SimTurningTime;
                }
                else if (pre_dir == "B")
                {
                    time_Turning = SimTurningTime;
                }
                else
                {
                    time_Turning = 0;
                }

                timePath.Add(timePath[timePath.Count - 1].AddSeconds(time_Turning + time_Line));
            }
            return(timePath);
        }
        // Update position AGV icon in simulation mode (speed: cm/s)
        public static Point UpdatePositionAGV(int agvID)
        {
            // Find AGV in ListAGV
            var   index    = AGV.ListAGV.FindIndex(a => a.ID == agvID);
            AGV   agv      = AGV.ListAGV[index];
            Point position = new Point();

            // If this node is pick node, remove pallet code that was picked and save this time
            if (agv.Tasks.Count != 0 && agv.ExitNode == agv.Tasks[0].PickNode)
            {
                RackColumn column = RackColumn.ListColumn.Find(c => c.AtNode == agv.ExitNode);

                Pallet.SaveDeliveryTime(column.PalletCodes[agv.Tasks[0].PickLevel - 1], Pallet.ListPallet);
                DBUtility.UpdatePalletDB("PalletInfoTable", column.PalletCodes[agv.Tasks[0].PickLevel - 1], false,
                                         DateTime.Now.ToString("dddd, MMMM dd, yyyy  h:mm:ss tt"), Pallet.ListPallet);

                column.PalletCodes[agv.Tasks[0].PickLevel - 1] = null;
            }

            // Update label position
            List <Node> Nodes         = Node.ListNode;
            int         pixelDistance = (int)Math.Round(agv.DistanceToExitNode * Display.Scale);
            int         x             = Nodes[agv.ExitNode].X - Display.LabelAGV[agvID].Size.Width / 2;
            int         y             = Nodes[agv.ExitNode].Y - Display.LabelAGV[agvID].Size.Height / 2;

            switch (agv.Orientation)
            {
            case 'E':
                position.X = x + pixelDistance; position.Y = y;
                break;

            case 'W':
                position.X = x - pixelDistance; position.Y = y;
                break;

            case 'N':
                position.X = x; position.Y = y - pixelDistance;
                break;

            case 'S':
                position.X = x; position.Y = y + pixelDistance;
                break;
            }
            return(position);
        }
        public static void SendVelocitySetting(uint agvID, float velocity)
        {
            SetVelocityPacket sendFrame = new SetVelocityPacket();

            sendFrame.Header = new byte[2] {
                0xAA, 0xFF
            };
            sendFrame.FunctionCode = (byte)FUNC_CODE.WR_VELOCITY;
            sendFrame.AGVID        = Convert.ToByte(agvID);
            sendFrame.Velocity     = velocity;
            // calculate check sum
            ushort crc = 0;

            crc += sendFrame.Header[0];
            crc += sendFrame.Header[1];
            crc += sendFrame.FunctionCode;
            crc += sendFrame.AGVID;
            crc += BitConverter.GetBytes(sendFrame.Velocity)[0];
            crc += BitConverter.GetBytes(sendFrame.Velocity)[1];
            crc += BitConverter.GetBytes(sendFrame.Velocity)[2];
            crc += BitConverter.GetBytes(sendFrame.Velocity)[3];
            sendFrame.CheckSum   = crc;
            sendFrame.EndOfFrame = new byte[2] {
                0x0A, 0x0D
            };

            // send data via serial port
            AGV agv = AGV.ListAGV.Find(a => a.ID == (int)agvID);

            if (!agv.IsInitialized)
            {
                return;
            }
            try { Communicator.SerialPort.Write(sendFrame.ToArray(), 0, sendFrame.ToArray().Length); }
            catch { };

            // Display ComStatus
            Display.UpdateComStatus("send", sendFrame.AGVID, "Set Velocity", System.Drawing.Color.Blue);

            // wait ack
            System.Timers.Timer timer4 = new System.Timers.Timer(timeout);
            timer4.Elapsed += (sender, e) => timer4_Elapsed(sender, e, sendFrame);
            timer4.Start();
        }
        // Send AGV info request packet (InfoType = 'A' for all except line tracking error, 'L' for line tracking error)
        public static void SendAGVInfoRequest(uint agvID, char InfoType)
        {
            AGVInfoRequestPacket requestFrame = new AGVInfoRequestPacket();

            requestFrame.Header = new byte[2] {
                0xAA, 0xFF
            };
            requestFrame.FunctionCode    = (byte)FUNC_CODE.REQ_AGV_INFO;
            requestFrame.AGVID           = Convert.ToByte(agvID);
            requestFrame.InformationType = (byte)InfoType;
            // calculate check sum
            ushort crc = 0;

            crc += requestFrame.Header[0];
            crc += requestFrame.Header[1];
            crc += requestFrame.FunctionCode;
            crc += requestFrame.AGVID;
            crc += requestFrame.InformationType;
            requestFrame.CheckSum   = crc;
            requestFrame.EndOfFrame = new byte[2] {
                0x0A, 0x0D
            };

            // send data via serial port
            AGV agv = AGV.ListAGV.Find(a => a.ID == (int)agvID);

            if (!agv.IsInitialized)
            {
                return;
            }
            try { Communicator.SerialPort.Write(requestFrame.ToArray(), 0, requestFrame.ToArray().Length); }
            catch { };

            // Display ComStatus
            Display.UpdateComStatus("send", requestFrame.AGVID, "Request AGV info", System.Drawing.Color.Blue);

            // wait ack
            System.Timers.Timer timer1 = new System.Timers.Timer(timeout);
            timer1.Elapsed += (sender, e) => timer1_Elapsed(sender, e, requestFrame);
            timer1.Start();
        }
        private void UpdateMonitoringData(AGV agv, double linetrackingError)
        {
            // update agv info
            lbStatus.Text      = agv.Status;
            lbExitNode.Text    = agv.ExitNode.ToString();
            lbOrient.Text      = agv.Orientation.ToString();
            lbDistance.Text    = Math.Round(agv.DistanceToExitNode, 1).ToString() + " cm";
            lbVelocity.Text    = Math.Round(agv.Velocity, 1).ToString() + " cm/s";
            lbBattery.Text     = agv.Battery.ToString() + "%";
            prgrbBattery.Value = agv.Battery;

            // update current path and highlight current node
            rtxtbCurrentPath.Clear();
            if (agv.Path.Count == 0)
            {
                rtxtbCurrentPath.SelectedText = "None";
            }
            foreach (int n in agv.Path)
            {
                if (n == agv.ExitNode)
                {
                    rtxtbCurrentPath.SelectionBackColor = Color.Yellow;
                }
                else
                {
                    rtxtbCurrentPath.SelectionBackColor = Color.Lavender;
                }
                if (n != agv.Path[agv.Path.Count - 1])
                {
                    rtxtbCurrentPath.SelectedText = n.ToString() + "->";
                }
                else
                {
                    rtxtbCurrentPath.SelectedText = n.ToString();
                }
            }

            // update graph
            DrawGraph(zedGraphVelocity, agv.Velocity);
            DrawGraph(zedGraphLineTrack, linetrackingError);
        }
        private static int CalculateTotalRemainingDistance(AGV agv)
        {
            int totalDistance = 0;

            // get list remaning path of this agv
            List <List <int> > listRemainingPath = new List <List <int> >();

            for (int i = 0; i < agv.Tasks.Count; i++)
            {
                if (i == 0 && agv.Tasks[0].Status == "Doing")
                {
                    listRemainingPath.Add(Algorithm.A_starFindPath(agv.ExitNode, agv.Tasks[0].DropNode));
                }
                else if (i == 0 && agv.Tasks[0].Status == "Waiting")
                {
                    listRemainingPath.Add(Algorithm.A_starFindPath(agv.ExitNode, agv.Tasks[0].PickNode));
                    listRemainingPath.Add(Algorithm.A_starFindPath(agv.Tasks[0].PickNode, agv.Tasks[0].DropNode));
                }
                else
                {
                    listRemainingPath.Add(Algorithm.A_starFindPath(agv.Tasks[i].PickNode, agv.Tasks[i].DropNode));
                }

                if (i < agv.Tasks.Count - 1)
                {
                    listRemainingPath.Add(Algorithm.A_starFindPath(agv.Tasks[i].DropNode, agv.Tasks[i + 1].PickNode));
                }
            }

            // calculate total distance of list remaining path
            foreach (List <int> path in listRemainingPath)
            {
                for (int i = 0; i < path.Count - 1; i++)
                {
                    totalDistance += Node.MatrixNodeDistance[path[i], path[i + 1]];
                }
            }

            return(totalDistance);
        }
Example #7
0
        private void btnRemove_Click(object sender, EventArgs e)
        {
            // Remove an AGV has ID in comboBox
            if (String.IsNullOrEmpty(cbbID.Text) == false)
            {
                List <AGV> listAll     = listOldAGV.Concat(listNewAGV).ToList();
                AGV        agvToRemove = listAll.Find(a => { return(a.ID == Convert.ToInt16(cbbID.Text)); });
                if (listOldAGV.Contains(agvToRemove))
                {
                    listOldAGV.Remove(agvToRemove);
                }
                if (listNewAGV.Contains(agvToRemove))
                {
                    listNewAGV.Remove(agvToRemove);
                }
            }

            // Put remaining AGV in listView
            listViewAGV.Items.Clear();
            foreach (AGV agv in listOldAGV.Concat(listNewAGV).ToList())
            {
                listViewAGV.Items.Add(" AGV#" + agv.ID, 0);
            }
        }
Example #8
0
        private void btnAdd_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(txbID.Text) || String.IsNullOrEmpty(cbbExitNode.Text) ||
                String.IsNullOrEmpty(cbbOrientation.Text) || String.IsNullOrEmpty(txbDistance.Text))
            {
                return;
            }

            // Check whether AGV ID exist in old and new list or not
            foreach (AGV a in listOldAGV.Concat(listNewAGV).ToList())
            {
                if (Convert.ToInt16(txbID.Text) == a.ID)
                {
                    MessageBox.Show("AGV ID already exists.\nPlease choose other AGV ID.", "Error",
                                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
            }

            // If not exist, add new AGV into listNewAGV
            AGV agv = new AGV(Convert.ToInt16(txbID.Text), Convert.ToInt16(cbbExitNode.Text),
                              Convert.ToChar(cbbOrientation.Text), Convert.ToSingle(txbDistance.Text), "Stop");

            if (Display.Mode == "Simulation")
            {
                agv.IsInitialized = true;
            }
            listNewAGV.Add(agv);

            // Put new AGV ID in listView
            listViewAGV.Items.Add(" AGV#" + agv.ID, 0);

            // Clear textBox for next adding
            txbID.Clear();
            txbDistance.Clear();
        }
        // Send path information packet
        public static void SendPathData(uint agvID, bool isPick, int pickLevel, string strNavigationFrame, bool isDrop, int dropLevel)
        {
            PathInfoSendPacket sendFrame = new PathInfoSendPacket();

            // get path info (to byte array)
            string[] arrNavigationFrame = strNavigationFrame.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            byte[]   arrPathFrame       = new byte[arrNavigationFrame.Length + 4]; // 4 bytes of pick/drop info

            if (isPick == true)
            {
                arrPathFrame[0] = (byte)'P';
            }
            else
            {
                arrPathFrame[0] = (byte)'N';
            }
            arrPathFrame[1] = Convert.ToByte(pickLevel);

            if (isDrop == true)
            {
                arrPathFrame[arrNavigationFrame.Length + 2] = (byte)'D';
            }
            else
            {
                arrPathFrame[arrNavigationFrame.Length + 2] = (byte)'N';
            }
            arrPathFrame[arrNavigationFrame.Length + 3] = Convert.ToByte(dropLevel);

            for (int i = 0; i < arrNavigationFrame.Length; i++)
            {
                if (i % 2 == 0)
                {
                    arrPathFrame[i + 2] = (byte)(arrNavigationFrame[i][0]);
                }
                else
                {
                    arrPathFrame[i + 2] = Convert.ToByte(arrNavigationFrame[i]);
                }
            }

            // set frame to send as struct
            // note: send reversed Header and End-of-frame because of Intel processors (in my laptop) use little endian
            sendFrame.Header = new byte[2] {
                0xAA, 0xFF
            };
            sendFrame.FunctionCode  = (byte)FUNC_CODE.WR_PATH;
            sendFrame.AGVID         = Convert.ToByte(agvID);
            sendFrame.PathByteCount = Convert.ToByte(arrPathFrame.Length);
            sendFrame.Path          = arrPathFrame;
            // calculate check sum
            ushort crc = 0;

            crc += sendFrame.Header[0];
            crc += sendFrame.Header[1];
            crc += sendFrame.FunctionCode;
            crc += sendFrame.AGVID;
            crc += sendFrame.PathByteCount;
            Array.ForEach(arrPathFrame, x => crc += x);
            sendFrame.CheckSum   = crc;
            sendFrame.EndOfFrame = new byte[2] {
                0x0A, 0x0D
            };

            // send data via serial port
            AGV agv = AGV.ListAGV.Find(a => a.ID == (int)agvID);

            if (!agv.IsInitialized)
            {
                return;
            }
            try { Communicator.SerialPort.Write(sendFrame.ToArray(), 0, sendFrame.ToArray().Length); }
            catch { };

            // Display ComStatus
            Display.UpdateComStatus("send", sendFrame.AGVID, "Write Path", System.Drawing.Color.Blue);

            // wait ack
            System.Timers.Timer timer2 = new System.Timers.Timer(timeout);
            timer2.Elapsed += (sender, e) => timer2_Elapsed(sender, e, sendFrame);
            timer2.Start();
        }
        public static void GetData()
        {
            int rxBufferSize = 25;

            byte[] rxBuffer    = new byte[rxBufferSize];
            int    rxByteCount = Communicator.SerialPort.Read(rxBuffer, 0, rxBufferSize);

            // add to a list of bytes received
            for (int i = 0; i < rxByteCount; i++)
            {
                bytesReceived.Add(rxBuffer[i]);
            }

            int  startIndex   = 0;
            byte functionCode = new byte();

            // check header
            if (bytesReceived.Count < 3)
            {
                return;
            }
            for (int i = 0; i < bytesReceived.Count - 3; i++)
            {
                if (bytesReceived[i] == 0xAA && bytesReceived[i + 1] == 0xFF)
                {
                    startIndex   = i;
                    functionCode = bytesReceived[i + 2];

                    if (functionCode == (byte)FUNC_CODE.RESP_AGV_INFO) // receive AGV info except line tracking error
                    {
                        // waitting for receive enough frame data of this function code
                        if (bytesReceived.Count - startIndex < AGVInfoReceivePacketSize)
                        {
                            return;
                        }

                        // put data in an array
                        byte[] data = new byte[AGVInfoReceivePacketSize];
                        for (int j = 0; j < AGVInfoReceivePacketSize; j++)
                        {
                            data[j] = bytesReceived[startIndex + j];
                        }

                        AGVInfoReceivePacket receiveFrame = AGVInfoReceivePacket.FromArray(data);

                        // check sum
                        ushort crc = 0;
                        for (int j = 0; j < AGVInfoReceivePacketSize - 4; j++)
                        {
                            crc += data[j];
                        }
                        if (crc != receiveFrame.CheckSum)
                        {
                            continue;
                        }

                        bytesReceived.RemoveRange(0, startIndex + AGVInfoReceivePacketSize - 1);

                        // update AGV info to lists of AGVs (real-time mode)
                        var agv = AGV.ListAGV.Find(a => a.ID == receiveFrame.AGVID);
                        if (agv == null)
                        {
                            continue;
                        }
                        switch (Convert.ToChar(receiveFrame.Status).ToString())
                        {
                        case "R": agv.Status = "Running"; break;

                        case "S": agv.Status = "Stop"; break;

                        case "P": agv.Status = "Picking"; break;

                        case "D": agv.Status = "Dropping"; break;
                        }
                        switch (Convert.ToChar(receiveFrame.Orient).ToString())
                        {
                        case "E": agv.Orientation = 'E'; break;

                        case "W": agv.Orientation = 'W'; break;

                        case "S": agv.Orientation = 'S'; break;

                        case "N": agv.Orientation = 'N'; break;
                        }
                        agv.ExitNode           = receiveFrame.ExitNode;
                        agv.DistanceToExitNode = receiveFrame.DisToExitNode;
                        agv.Velocity           = receiveFrame.Velocity;
                        agv.Battery            = receiveFrame.Battery;
                    }
                    else if (functionCode == (byte)FUNC_CODE.RESP_LINE_TRACK_ERR) // receive Line tracking error
                    {
                        // waitting for receive enough frame data of this function code
                        if (bytesReceived.Count - startIndex < AGVLineTrackErrorReceivePacketSize)
                        {
                            return;
                        }

                        // get data and take it out of the queue
                        byte[] data = new byte[AGVLineTrackErrorReceivePacketSize];
                        for (int j = 0; j < AGVLineTrackErrorReceivePacketSize; j++)
                        {
                            data[j] = bytesReceived[startIndex + j];
                        }

                        AGVLineTrackErrorReceivePacket receiveFrame = AGVLineTrackErrorReceivePacket.FromArray(data);

                        // check sum
                        ushort crc = 0;
                        for (int j = 0; j < AGVLineTrackErrorReceivePacketSize - 4; j++)
                        {
                            crc += data[j];
                        }
                        if (crc != receiveFrame.CheckSum)
                        {
                            continue;
                        }

                        bytesReceived.RemoveRange(0, startIndex + AGVLineTrackErrorReceivePacketSize - 1);

                        // update Line tracking error value
                        if (AGVMonitoringForm.selectedAGVID == receiveFrame.AGVID)
                        {
                            lineTrackError = receiveFrame.LineTrackError;
                        }
                        else
                        {
                            lineTrackError = 0;
                        }
                    }
                    else if (functionCode == (byte)FUNC_CODE.RESP_ACK_PATH ||
                             functionCode == (byte)FUNC_CODE.RESP_ACK_AGV_INFO ||
                             functionCode == (byte)FUNC_CODE.RESP_ACK_WAITING ||
                             functionCode == (byte)FUNC_CODE.RESP_ACK_VELOCITY ||
                             functionCode == (byte)FUNC_CODE.RESP_ACK_INIT) // receive ack
                    {
                        // waitting for receive enough frame data of this function code
                        if (bytesReceived.Count - startIndex < AckReceivePacketSize)
                        {
                            return;
                        }

                        // get data and take it out of the queue
                        byte[] data = new byte[AckReceivePacketSize];
                        for (int j = 0; j < AckReceivePacketSize; j++)
                        {
                            data[j] = bytesReceived[startIndex + j];
                        }

                        AckReceivePacket receiveFrame = AckReceivePacket.FromArray(data);

                        // check sum
                        ushort crc = 0;
                        for (int j = 0; j < AckReceivePacketSize - 4; j++)
                        {
                            crc += data[j];
                        }
                        if (crc != receiveFrame.CheckSum)
                        {
                            continue;
                        }

                        bytesReceived.RemoveRange(0, startIndex + AckReceivePacketSize - 1);

                        // update received ack
                        ackReceived = receiveFrame;

                        // if receive ACK of Initialization, set agv.Initialization = true
                        if (functionCode == (byte)FUNC_CODE.RESP_ACK_INIT && receiveFrame.ACK == (byte)'Y')
                        {
                            AGV agv = AGV.ListAGV.Find(a => a.ID == (int)receiveFrame.AGVID);
                            agv.IsInitialized = true;
                            agv.Status        = "Initialized";
                        }

                        // Display ComStatus
                        string message = "";
                        if (receiveFrame.ACK == (byte)'Y')
                        {
                            if (functionCode == (byte)FUNC_CODE.RESP_ACK_PATH)
                            {
                                message = "ACK (path)";
                            }
                            else if (functionCode == (byte)FUNC_CODE.RESP_ACK_AGV_INFO)
                            {
                                message = "ACK (request AGV info)";
                            }
                            else if (functionCode == (byte)FUNC_CODE.RESP_ACK_WAITING)
                            {
                                message = "ACK (waiting)";
                            }
                            else if (functionCode == (byte)FUNC_CODE.RESP_ACK_VELOCITY)
                            {
                                message = "ACK (velocity setting)";
                            }
                            else if (functionCode == (byte)FUNC_CODE.RESP_ACK_INIT)
                            {
                                message = "ACK (initialized)";
                            }
                            Display.UpdateComStatus("receive", receiveFrame.AGVID, message, System.Drawing.Color.Green);
                        }
                        else if (receiveFrame.ACK == (byte)'N')
                        {
                            if (functionCode == (byte)FUNC_CODE.RESP_ACK_PATH)
                            {
                                message = "NACK (path)";
                            }
                            else if (functionCode == (byte)FUNC_CODE.RESP_ACK_AGV_INFO)
                            {
                                message = "NACK (request AGV info)";
                            }
                            else if (functionCode == (byte)FUNC_CODE.RESP_ACK_WAITING)
                            {
                                message = "NACK (waiting)";
                            }
                            else if (functionCode == (byte)FUNC_CODE.RESP_ACK_VELOCITY)
                            {
                                message = "NACK (velocity setting)";
                            }
                            else if (functionCode == (byte)FUNC_CODE.RESP_ACK_INIT)
                            {
                                message = "NACK (initialize)";
                            }
                            Display.UpdateComStatus("receive", receiveFrame.AGVID, message, System.Drawing.Color.Red);
                        }
                    }
                }
            }
        }
        // Update position AGV icon in simulation mode (speed: cm/s)
        public static Point SimUpdatePositionAGV(int agvID, float speed)
        {
            // Find AGV in SimListAGV, get current point
            var   index    = AGV.SimListAGV.FindIndex(a => a.ID == agvID);
            AGV   agv      = AGV.SimListAGV[index];
            Point position = Display.SimLabelAGV[agvID].Location;

            // Handle (waiting method) collision if it happens
            CollisionStatus status = Collision.SimHandle(agv, Collision.SimListCollision);

            if (status == CollisionStatus.Handling)
            {
                // Update agv status and velocity
                agv.Velocity = 0;
                agv.Status   = "Stop";

                return(position);
            }

            // return old point when agv has no path
            if (agv.Path.Count == 0)
            {
                return(position);
            }

            // Get navigation frame array. Note: string is a reference type,
            // so any change in navigationArr is also in AGV.SimListAGV[index].navigationArr
            string[] navigationArr = agv.navigationArr;

            // Check whether current point is a node or not
            // Note: shift position of label to center (+LabelAGV[].Width/2, +LabelAGV[].Height/2)
            var node = Node.ListNode.FirstOrDefault(n =>
            {
                return((n.X == position.X + SimLabelAGV[agvID].Width / 2) &&
                       (n.Y == position.Y + SimLabelAGV[agvID].Height / 2));
            });

            // Current point is not a node and current position is start node,
            // it means initDistance to start node != 0, so go backward once then keep go ahead
            char orient = new char();

            if (node == null && agv.ExitNode == agv.Path[0])
            {
                switch (navigationArr[0])
                {
                case "A":
                    orient = UpdateOrient(agv.Orientation, "A");
                    break;

                case "B":
                    orient           = UpdateOrient(agv.Orientation, "B");
                    navigationArr[0] = "A";
                    break;
                }
            }
            // Current point is not a node and current position is not start node,
            // so keep go ahead
            else if (node == null)
            {
                orient = UpdateOrient(agv.Orientation, "A");
            }
            // If goal was reached, no update position, remove old path, get next path (if exist)
            else if (node.ID == agv.Path.LastOrDefault())
            {
                // Update AGV information
                agv.ExitNode           = node.ID; // Update ExitNode
                orient                 = UpdateOrient(agv.Orientation, "A");
                agv.Orientation        = orient;  // Update Orientation
                agv.DistanceToExitNode = 0f;      // Update Distance to ExitNode
                agv.Status             = "Stop";  // Update Status
                agv.Velocity           = 0;       // Update Velocity

                // Add next path
                Task.AddNextPathOfSimAGV(agv);

                return(position);
            }
            // Current point is at start node and initDistance to start node == 0
            // Turn direction once then keep go ahead
            else if (node.ID == agv.Path[0] && agv.DistanceToExitNode == 0f)
            {
                switch (navigationArr[0])
                {
                case "A":
                    orient = UpdateOrient(agv.Orientation, "A");
                    break;

                case "B":
                    orient           = UpdateOrient(agv.Orientation, "B");
                    navigationArr[0] = "A";
                    break;

                case "L":
                    orient           = UpdateOrient(agv.Orientation, "L");
                    navigationArr[0] = "A";
                    break;

                case "R":
                    orient           = UpdateOrient(agv.Orientation, "R");
                    navigationArr[0] = "A";
                    break;
                }

                // If this node is pick node, remove pallet code that was picked and save this time
                if (agv.Tasks.Count != 0 && node.ID == agv.Tasks[0].PickNode)
                {
                    RackColumn column = RackColumn.SimListColumn.Find(c => c.AtNode == agv.ExitNode);

                    Pallet.SaveDeliveryTime(column.PalletCodes[agv.Tasks[0].PickLevel - 1], Pallet.SimListPallet);
                    DBUtility.UpdatePalletDB("SimPalletInfoTable", column.PalletCodes[agv.Tasks[0].PickLevel - 1], false,
                                             DateTime.Now.ToString("dddd, MMMM dd, yyyy  h:mm:ss tt"), Pallet.SimListPallet);

                    column.PalletCodes[agv.Tasks[0].PickLevel - 1] = null;
                }
            }
            // Current point is a node but start node
            else
            {
                int    idx = Array.IndexOf(navigationArr, node.ID.ToString());
                string dir = navigationArr[idx + 1];
                orient = UpdateOrient(agv.Orientation, dir);
            }

            // Modify speed to make sure AGV icon can reach next node
            int i        = agv.Path.IndexOf(agv.ExitNode);
            int nextNode = agv.Path[i + 1];
            int dnx      = (position.X + SimLabelAGV[agvID].Width / 2) - Node.ListNode[nextNode].X;
            int dny      = (position.Y + SimLabelAGV[agvID].Height / 2) - Node.ListNode[nextNode].Y;
            int nd       = (int)Math.Sqrt(dnx * dnx + dny * dny);

            if (agv.ExitNode == agv.Path[0])
            {
                // At first node of path, having 4 cases of agv position
                // (dnx * dny != 0) for 2 cases and (dnx * dnx0 > 0 || dny * dny0 > 0) for the others
                int dnx0 = (position.X + SimLabelAGV[agvID].Width / 2) - Node.ListNode[agv.Path[0]].X;
                int dny0 = (position.Y + SimLabelAGV[agvID].Height / 2) - Node.ListNode[agv.Path[0]].Y;
                int nd0  = (int)Math.Sqrt(dnx0 * dnx0 + dny0 * dny0);
                if (dnx * dny != 0 || dnx * dnx0 > 0 || dny * dny0 > 0)
                {
                    nextNode = agv.Path[0];
                    nd       = nd0;
                }
            }
            int sp   = (int)Math.Round(speed * Display.Scale * (100.0 / 1000)); // timer1.Interval = 100ms
            int step = (nd % sp == 0) ? sp : (nd % sp);

            // Update AGV information before update position
            if (node != null)
            {
                agv.ExitNode = node.ID; // Update ExitNode
            }
            agv.Orientation = orient;   // Update Orientation
            int exitNode = agv.ExitNode;
            int dx       = (position.X + SimLabelAGV[agvID].Width / 2) - Node.ListNode[exitNode].X;
            int dy       = (position.Y + SimLabelAGV[agvID].Height / 2) - Node.ListNode[exitNode].Y;

            agv.DistanceToExitNode = (float)Math.Sqrt(dx * dx + dy * dy) / Display.Scale; // Update Distance to ExitNode
            agv.Status             = "Running";                                           // Update Status

            // Update next position of AGV icon in panel
            switch (orient)
            {
            case 'E':
                position = new Point(position.X + step, position.Y);
                break;

            case 'W':
                position = new Point(position.X - step, position.Y);
                break;

            case 'S':
                position = new Point(position.X, position.Y + step);
                break;

            case 'N':
                position = new Point(position.X, position.Y - step);
                break;
            }

            return(position);
        }
        // Collision detection
        public static void Detect(List <AGV> listAGV)
        {
            if (listAGV.Count < 2)
            {
                return;
            }

            // matrix of distance from node to other node (pixel)
            int[,] D = Node.MatrixNodeDistance;

            // Check each pair of agvs
            for (int i = 0; i < listAGV.Count - 1; i++)
            {
                for (int j = i + 1; j < listAGV.Count; j++)
                {
                    AGV agv1 = listAGV[i];
                    AGV agv2 = listAGV[j];

                    // if no path, do not detect
                    if (agv1.Path.Count == 0 || agv2.Path.Count == 0)
                    {
                        continue;
                    }

                    // predict coming time to remaining node on the path
                    List <DateTime> timePath1 = new List <DateTime>();
                    List <DateTime> timePath2 = new List <DateTime>();
                    DateTime        timeNow   = DateTime.Now;
                    timePath1 = PredictTimeOnPath(agv1, timeNow);
                    timePath2 = PredictTimeOnPath(agv2, timeNow);

                    // get remaining path of each agv
                    List <int> remainingPath1 = new List <int>();
                    List <int> remainingPath2 = new List <int>();
                    foreach (int n in agv1.Path)
                    {
                        // don't get node which is passed
                        if (agv1.Path.IndexOf(n) < agv1.Path.IndexOf(agv1.ExitNode))
                        {
                            continue;
                        }
                        remainingPath1.Add(n);
                    }
                    foreach (int n in agv2.Path)
                    {
                        // don't get node which is passed
                        if (agv2.Path.IndexOf(n) < agv2.Path.IndexOf(agv2.ExitNode))
                        {
                            continue;
                        }
                        remainingPath2.Add(n);
                    }

                    List <int> remainingPath2_reverse = new List <int>(remainingPath2);
                    remainingPath2_reverse.Reverse();

                    // detect overlap nodes on two paths
                    List <int> overLapNodes = new List <int>();

                    for (int w_size = remainingPath1.Count; w_size > 0; w_size--)
                    {
                        for (int idx1 = 0; idx1 <= remainingPath1.Count - w_size; idx1++)
                        {
                            for (int idx2 = 0; idx2 <= remainingPath2_reverse.Count - w_size; idx2++)
                            {
                                var w1 = remainingPath1.GetRange(idx1, w_size);
                                var w2 = remainingPath2_reverse.GetRange(idx2, w_size);

                                if (w1.SequenceEqual(w2))
                                {
                                    overLapNodes = w1;
                                    break;
                                }
                            }
                            if (overLapNodes.Count > 0)
                            {
                                break;
                            }
                        }
                        if (overLapNodes.Count > 0)
                        {
                            break;
                        }
                    }

                    // no overlapping
                    if (overLapNodes.Count < 1)
                    {
                        continue;
                    }

                    // overall time of overlapping
                    int      idxOverLapBegin1 = remainingPath1.IndexOf(overLapNodes[0]);
                    int      idxOverLapEnd1   = remainingPath1.IndexOf(overLapNodes[overLapNodes.Count - 1]);
                    TimeSpan deltaTOverLap    = timePath1[idxOverLapEnd1].Subtract(timePath1[idxOverLapBegin1]);

                    int      idxOverLapBegin2 = remainingPath2.IndexOf(overLapNodes[overLapNodes.Count - 1]);
                    DateTime time1            = timePath1[idxOverLapBegin1];
                    DateTime time2            = timePath2[idxOverLapBegin2];
                    TimeSpan deltaT           = time1.Subtract(time2).Duration();

                    // detect head-on collision
                    if (deltaT < deltaTOverLap)
                    {
                        // waiting for later AGV to come close to the overlapping zone
                        if ((time1 >= time2 && time1 > timeNow.AddSeconds(AGV.Length / AGV.SimSpeed)) ||
                            (time1 < time2 && time2 > timeNow.AddSeconds(AGV.Length / AGV.SimSpeed)))
                        {
                            continue;
                        }

                        // AGV coming to overlap-begin-node later must wait or re-route (put at collision.OnAGVs[0])
                        int[] collisionOnAGVs = new int[2];
                        if (time1 >= time2)
                        {
                            collisionOnAGVs[0] = agv1.ID; collisionOnAGVs[1] = agv2.ID;
                        }
                        else
                        {
                            collisionOnAGVs[0] = agv2.ID; collisionOnAGVs[1] = agv1.ID;
                        }

                        // -----------add real-time later--------------
                        Collision collision = new Collision(CollisionStatus.New, CollisionType.HeadOn,
                                                            collisionOnAGVs, overLapNodes.ToArray(), DateTime.Now,
                                                            (float)deltaTOverLap.TotalSeconds + AGV.Length / AGV.SimSpeed);

                        // skip collision that exists
                        if (SimListCollision.Exists(c => c.Type == collision.Type &&
                                                    c.OnAGVs.SequenceEqual(collision.OnAGVs)))
                        {
                            continue;
                        }
                        Collision.SimListCollision.Add(collision);
                    }

                    // detect creoss collision
                    else if (deltaTOverLap == TimeSpan.Zero) // overlap only one node
                    {
                        // find next node and distance to next node of agv1
                        int idx1 = agv1.Path.IndexOf(agv1.ExitNode);
                        if (idx1 == agv1.Path.Count - 1)
                        {
                            continue;                              // at goal node
                        }
                        int   nextNodeAGV1      = agv1.Path[idx1 + 1];
                        float disToNextNodeAGV1 = D[agv1.ExitNode, nextNodeAGV1] / Display.Scale - agv1.DistanceToExitNode;

                        // find next node and distance to next node of agv2
                        int idx2 = agv2.Path.IndexOf(agv2.ExitNode);
                        if (idx2 == agv2.Path.Count - 1)
                        {
                            continue;                              // at goal node
                        }
                        int   nextNodeAGV2      = agv2.Path[idx2 + 1];
                        float disToNextNodeAGV2 = D[agv2.ExitNode, nextNodeAGV2] / Display.Scale - agv2.DistanceToExitNode;

                        // conditions for cross collisions to occur
                        float delta = disToNextNodeAGV1 - disToNextNodeAGV2; // uint: cm
                        if (nextNodeAGV1 == nextNodeAGV2 && Math.Abs(delta) < AGV.Length)
                        {
                            // waiting for later AGV to come close to the collision node
                            if ((delta >= 0 && disToNextNodeAGV1 > AGV.Length) ||
                                (delta < 0 && disToNextNodeAGV2 > AGV.Length))
                            {
                                continue;
                            }

                            int[] collisionOnAGVs = new int[2];
                            if (delta >= 0)
                            {
                                collisionOnAGVs[0] = agv1.ID; collisionOnAGVs[1] = agv2.ID;
                            }
                            else
                            {
                                collisionOnAGVs[0] = agv2.ID; collisionOnAGVs[1] = agv1.ID;
                            }

                            // -----------add real-time later--------------
                            Collision collision = new Collision(CollisionStatus.New, CollisionType.Cross,
                                                                collisionOnAGVs, overLapNodes.ToArray(), DateTime.Now, 2.5f);

                            // skip collision that exists
                            if (SimListCollision.Exists(c => c.OnAGVs.SequenceEqual(collision.OnAGVs) ||
                                                        c.OnAGVs.SequenceEqual(new int[2] {
                                collision.OnAGVs[1], collision.OnAGVs[0]
                            })))
                            {
                                continue;
                            }
                            Collision.SimListCollision.Add(collision);
                        }
                    }
                }
            }
        }
        // Add next path to agv when previous path reach goal (agv.ExitNode is being goal) (Simulation Mode)
        public static void AddNextPathOfSimAGV(AGV agv)
        {
            // Clear old path
            agv.Path.Clear();

            // Remove old task
            if (agv.Tasks.Count != 0 && agv.ExitNode == agv.Tasks[0].DropNode)
            {
                // Store pallet code to SimListColumn at this goal node
                RackColumn column = RackColumn.SimListColumn.Find(c => c.AtNode == agv.ExitNode);
                column.PalletCodes[agv.Tasks[0].DropLevel - 1] = agv.Tasks[0].PalletCode;
                if (column.AtNode != 51 & column.AtNode != 52)
                {
                    Pallet pallet = new Pallet(agv.Tasks[0].PalletCode, true,
                                               DateTime.Now.ToString("dddd, MMMM dd, yyyy  h:mm:ss tt"),
                                               column.Block, column.Number, agv.Tasks[0].DropLevel);
                    Pallet.SimListPallet.Add(pallet);
                    DBUtility.InsertNewPalletToDB("SimPalletInfoTable", pallet.Code, pallet.InStock, pallet.StoreTime,
                                                  pallet.AtBlock, pallet.AtColumn, pallet.AtLevel);
                }

                // Note: remove task in agv.Tasks and also in Task.SimListTask
                Task.SimListTask.Remove(Task.SimListTask.Find(a => a.Name == agv.Tasks[0].Name));
                agv.Tasks.RemoveAt(0);
            }

            // Add path to parking when don't have any task
            if (agv.Tasks.Count == 0)
            {
                // find all parking node
                List <int> parkingNode = new List <int>();
                foreach (Node n in Node.ListNode)
                {
                    if (n.LocationCode.Length == 0)
                    {
                        continue;
                    }
                    if (n.LocationCode[0] == 'P')
                    {
                        parkingNode.Add(n.ID);
                    }
                }
                // If current node is parking node or don't have parking node for this agv, do nothing,
                // otherwise, add path to park agv (agv will park at location in order of index)
                if (parkingNode.Contains(agv.ExitNode))
                {
                    return;
                }
                int parkAtNode = parkingNode.Find(n => parkingNode.IndexOf(n) == AGV.SimListAGV.IndexOf(agv));
                if (parkAtNode == 0)
                {
                    return;                  // be careful: in my node definition, parking nodes don't have node 0
                }
                agv.Path = Algorithm.A_starFindPath(agv.ExitNode, parkAtNode);

                // Add navigation frame of parking path
                string fr = Navigation.GetNavigationFrame(agv.Path, agv.Orientation, agv.DistanceToExitNode);
                agv.navigationArr = fr.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                return;
            }

            // Add next path
            if (agv.ExitNode != agv.Tasks[0].PickNode)
            {
                agv.Path = Algorithm.A_starFindPath(agv.ExitNode, agv.Tasks[0].PickNode);
            }
            else
            {
                agv.Path            = Algorithm.A_starFindPath(agv.Tasks[0].PickNode, agv.Tasks[0].DropNode);
                agv.Tasks[0].Status = "Doing";

                // update button add pallet
                if (agv.Tasks[0].PickNode == 53)
                {
                    HomeScreenForm.isPickSimPalletInput1 = true;
                }
                else if (agv.Tasks[0].PickNode == 54)
                {
                    HomeScreenForm.isPickSimPalletInput2 = true;
                }
            }

            // Add next navigation frame of next path
            string frame = Navigation.GetNavigationFrame(agv.Path, agv.Orientation, agv.DistanceToExitNode);

            agv.navigationArr = frame.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
        }