<file_sep>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using TwinCAT.Ads;

namespace PlcSimInterface
{

    class Program
    {

        static void Main(string[] args)
        {
            ArrayList plcInterfaceList = new ArrayList();
            ArrayList simInterfaceList = new ArrayList();

            string filename = "config.xml";
            var currentDirectory = Directory.GetCurrentDirectory();
            var configFilepath = Path.Combine(currentDirectory, filename);
            
            System.Xml.XmlDataDocument xmldoc = new System.Xml.XmlDataDocument();
            XmlNodeList xmlAddresses;
            
            string str = null;
            FileStream fs = new FileStream(configFilepath, FileMode.Open, FileAccess.Read);
            xmldoc.Load(fs);
            xmlAddresses = xmldoc.GetElementsByTagName("Address");

            var numOfPlcs = xmlAddresses.Count;
    
            for(int i = 0; i<numOfPlcs; i++)
            {
                string amsAddress = xmlAddresses[i].ChildNodes.Item(0).InnerText.Trim();
                Console.WriteLine("AmsNetId: {0}",amsAddress);
                PlcInterface plcInterface = new PlcInterface(amsAddress);
                plcInterfaceList.Add(plcInterface);
                plcInterface.Fetch();
                SimInterface simInterface = new SimInterface(plcInterface);
                simInterfaceList.Add(simInterface);
                simInterface.Init();
            }
            Console.WriteLine("Press enter to end the program.");
            Console.WriteLine();
            while (true)
            {
                if (Console.KeyAvailable)
                {
                    ConsoleKeyInfo key = Console.ReadKey(true);
                    if(key.Key == ConsoleKey.Enter)
                    {
                        break;
                    }
                }
            }

            Console.WriteLine("Cleaning up and exiting.");
            foreach(SimInterface simInterface in simInterfaceList)
            {
                    simInterface.Dispose();
            }
            
            foreach(PlcInterface plcInterface in plcInterfaceList)
            {
                    plcInterface.Dispose();
            }
        }
    }
}
<file_sep>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Threading; 
using EngineIO;
namespace PlcSimInterface
{
    class SimInterface
    {
        private PlcInterface plcInterface;
        private string plcHash, simuHash;
        private MD5 md5Hash;
        private int[] inputDecoder,outputDecoder;
        private MemoryBit[] memoryInputBits,memoryOutputBits;
        private Thread inputThread,outputThread;
        Boolean endThread;
        public SimInterface(PlcInterface plcInterface)
        {
            this.plcInterface = plcInterface;
            endThread = new Boolean();
            endThread = false;
            inputDecoder = new int[this.plcInterface.IBoolInPathCtr-1];
            outputDecoder = new int[this.plcInterface.IBoolOutPathCtr-1];
           
            for(int i = 0; i < this.plcInterface.IBoolInPathCtr -1 ; i++)
            {
                inputDecoder[i] = -1;
            }

            for(int i = 0; i < this.plcInterface.IBoolOutPathCtr -1 ; i++)
            {
                outputDecoder[i] = -1;
            }

            md5Hash = MD5.Create();
        }

        public void Dispose()
        {
            endThread = true;
            //Wait for threads to clean up
            while(inputThread.IsAlive || outputThread.IsAlive);

            Console.WriteLine("SimInterface disposed.");
            ((IDisposable)md5Hash).Dispose();
        }
        public void Init()
        {
            //MemoryMap.Instance.Update();
            memoryInputBits = MemoryMap.Instance.GetBitMemories(MemoryType.Input);
            for (int i = 0; i < memoryInputBits.Length - 1; i++)
            {
                MemoryBit memoryBit = memoryInputBits[i];
                simuHash = memoryBit.Name;
                if(simuHash.Equals("")
                    || simuHash.Length!=32)
                    continue;
                //Search for the hash in the list of plc symbols
                //Console.WriteLine("simuHash: {0} index: {1} ", simuHash, i );
                for (int k = 0; k < plcInterface.IBoolInPathCtr - 1; k++)
                {
                    plcHash = plcInterface.GetMd5Hash(md5Hash, plcInterface.ArrBoolInPaths[k]);
                    //Console.WriteLine("PLCHash: {0} SimuHash: {1}", plcHash, simuHash );
                    if (plcHash.Equals(simuHash))
                    {
                        
                        //Console.Write("plcHash: {0} index: {1} ", plcHash, k );
                        //Console.WriteLine("Memory input found: index plc: {0} index simulation: {1}", k, i );
                        //Store the simuHash index
                        inputDecoder[k] = i;
                        //Console.WriteLine("Input PLCHash: {0} Input SimuHash: {1} k: {2} inputDecoder[k]: {3}", plcHash, memoryInputBits[inputDecoder[k]].Name,k ,inputDecoder[k] );

                        //Exit the loop
                        break;
                    }
                }
            }

            //MemoryMap.Instance.Update();
            memoryOutputBits = MemoryMap.Instance.GetBitMemories(MemoryType.Output);
            for (int i = 0; i < memoryOutputBits.Length - 1; i++)
            {
                //Get outputs from the plc and transfer them to the simulation.
                if(memoryOutputBits[i].Name.Equals("")
                    || memoryOutputBits[i].Name.Length!=32)
                    continue;

                for (int k = 0; k < plcInterface.IBoolOutPathCtr - 1; k++)
                {
                    plcHash = plcInterface.GetMd5Hash(md5Hash, plcInterface.ArrBoolOutPaths[k]);    
                    if (plcHash.Equals(memoryOutputBits[i].Name) )
                    {

                   
                        outputDecoder[k] = i;
                         //Console.WriteLine("Output PLCHash: {0} Output SimuHash: {1} k: {2} ouputDecoder[k]: {3}", plcHash, memoryOutputBits[outputDecoder[k]].Name,k ,outputDecoder[k] );
                        //Exit the loop
                        break;
                    }
                }    
            }

            inputThread = new Thread(InputsExchange);
            outputThread = new Thread(OutputsExchange);
            inputThread.Start();
            outputThread.Start();
        }

        public void InputsExchange()
        {
            
            while(!endThread)
            {
                //var watch = System.Diagnostics.Stopwatch.StartNew();
                MemoryMap.Instance.Update();
           
                for (int k = 0; k < (plcInterface.IBoolInPathCtr - 1) ; k++)
                {
                    if(inputDecoder[k] == -1)
                        continue;
                    //plcHash = plcInterface.GetMd5Hash(md5Hash, plcInterface.ArrBoolInPaths[k]);
                    //Console.WriteLine("Input PLCHash: {0} Input SimuHash: {1} k: {2} inputDecoder[k]: {3}", plcHash, memoryInputBits[inputDecoder[k]].Name,k ,inputDecoder[k] );
                    plcInterface.writeBooleanInput(k, memoryInputBits[inputDecoder[k]].Value);              
                }
                  
                //watch.Stop();
                //var elapsedMs = watch.ElapsedMilliseconds;
                //Console.WriteLine("InputsExchange elapsed time: {0}",elapsedMs);
            }
        }
        public void OutputsExchange()
        {
            while(!endThread)
            {
                //var watch = System.Diagnostics.Stopwatch.StartNew();
                for (int k = 0; k < (plcInterface.IBoolOutPathCtr - 1) ; k++)
                {
                    if(outputDecoder[k] == -1)
                        continue;

                    memoryOutputBits[outputDecoder[k]].Value = plcInterface.readBooleanOutput(k);
               
                }
           
                MemoryMap.Instance.Update();
                //watch.Stop();
                //var elapsedMs = watch.ElapsedMilliseconds;
                //Console.WriteLine("OutputsExchange elapsed time: {0}",elapsedMs);
            }
        }

        public void Execute()
        {
            while(true)
            {
                var watch = System.Diagnostics.Stopwatch.StartNew();
                MemoryMap.Instance.Update();
           
                for (int k = 0; k < (plcInterface.IBoolInPathCtr - 1) ; k++)
                {
                    if(inputDecoder[k] == -1)
                        continue;
                    //plcHash = plcInterface.GetMd5Hash(md5Hash, plcInterface.ArrBoolInPaths[k]);
                    //Console.WriteLine("Input PLCHash: {0} Input SimuHash: {1} k: {2} inputDecoder[k]: {3}", plcHash, memoryInputBits[inputDecoder[k]].Name,k ,inputDecoder[k] );
                    plcInterface.writeBooleanInput(k, memoryInputBits[inputDecoder[k]].Value);              
                }
           
            
                for (int k = 0; k < (plcInterface.IBoolOutPathCtr - 1) ; k++)
                {
                    if(outputDecoder[k] == -1)
                        continue;

                    //plcHash = plcInterface.GetMd5Hash(md5Hash, plcInterface.ArrBoolOutPaths[k]);
                    //Console.WriteLine("Output PLCHash: {0} Output SimuHash: {1} k: {2} outputDecoder[k]: {3}", plcHash, memoryOutputBits[outputDecoder[k]].Name,k ,outputDecoder[k] );
                    memoryOutputBits[outputDecoder[k]].Value = plcInterface.readBooleanOutput(k);
               
                }
           
                MemoryMap.Instance.Update();
                watch.Stop();
                var elapsedMs = watch.ElapsedMilliseconds;
                Console.WriteLine("Elapsed time: {0}",elapsedMs);
            }
        // the code that you want to measure comes here
           
        }
    }
}
<file_sep>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using TwinCAT.Ads;

namespace PlcSimInterface
{
    class PlcInterface
    {
        public PlcInterface(string amsNetId)
        {
            this.amsNetId = amsNetId;
            readerReadBoolOut = new AdsBinaryReader(streamReadBoolOut);
        }

        private int
            hBoolInPaths,
            hBoolInPathCtr,
            hBoolOutPaths,
            hBoolOutPathCtr,
            iBoolInPathCtr,
            iBoolOutPathCtr;
        
        public int IBoolInPathCtr { get => iBoolInPathCtr; }
        public int IBoolOutPathCtr { get => iBoolOutPathCtr; }
        public string[] ArrBoolInPaths { get => arrBoolInPaths;  }
        public string[] ArrBoolOutPaths { get => arrBoolOutPaths;}

        private AdsStream streamReadBoolOut = new AdsStream(1);
        private AdsBinaryReader readerReadBoolOut; 
        private TcAdsClient tcClient;
        private string[] arrBoolInPaths = null; 
        private string[] arrBoolOutPaths = null;
        private int[] inputHandleArray, outputHandleArray;
        private string amsNetId;

        public void Dispose()
        {
            for (int i = 0; i < IBoolInPathCtr-1; i++)
            {
                tcClient.DeleteVariableHandle(inputHandleArray[i]);
            }
            for (int i = 0; i < IBoolOutPathCtr-1; i++)
            {
                tcClient.DeleteVariableHandle(outputHandleArray[i]);
            }
            
            tcClient.Disconnect();
            tcClient.Dispose();
        }

        public string GetMd5Hash(MD5 md5Hash, string input)
        {

            // Convert the input string to a byte array and compute the hash.
            byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));

            // Create a new Stringbuilder to collect the bytes
            // and create a string.
            StringBuilder sBuilder = new StringBuilder();

            // Loop through each byte of the hashed data 
            // and format each one as a hexadecimal string.
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }

            // Return the hexadecimal string.
            return sBuilder.ToString();
        }

        public void writeBooleanInput(int index, bool value)
        {           
            tcClient.WriteAny(inputHandleArray[index], value);  
        }

        public bool readBooleanOutput(int index)
        {
    
            tcClient.Read(outputHandleArray[index], streamReadBoolOut);
            bool val = readerReadBoolOut.ReadBoolean();
            streamReadBoolOut.Position = 0;
            
            return val;
        }

        public void Fetch()
        {
            tcClient = new TcAdsClient();
            tcClient.Connect(amsNetId,851);
            try
            {
                //get the input paths

                hBoolInPathCtr = tcClient.CreateVariableHandle("SymbolPathStorage.uiBoolInPathCtr");
                AdsStream streamBoolInPathCtr = new AdsStream(2);
                AdsBinaryReader readerBoolInPathCtr = new AdsBinaryReader(streamBoolInPathCtr);
                tcClient.Read(hBoolInPathCtr, streamBoolInPathCtr);
                iBoolInPathCtr = readerBoolInPathCtr.ReadInt16();

                arrBoolInPaths = new string[iBoolInPathCtr - 1];

                Console.WriteLine("Input variables nr: {0}", IBoolInPathCtr);

                hBoolInPaths = tcClient.CreateVariableHandle("SymbolPathStorage.aBoolInPaths");

                AdsStream streamBoolInPaths = new AdsStream((IBoolInPathCtr - 1) * 256);
                BinaryReader readerBoolInPaths = new BinaryReader(streamBoolInPaths);

                tcClient.Read(hBoolInPaths, streamBoolInPaths);
                inputHandleArray = new int[IBoolInPathCtr-1];
                for (int i = 1; i < IBoolInPathCtr; i++)
                {
                    byte[] buffer = readerBoolInPaths.ReadBytes(256);
                    string var = Encoding.UTF8.GetString(buffer, 0, buffer.Length);

                    int index = var.IndexOf('\0');
                    var = var.Remove(index);
                    arrBoolInPaths[i - 1] = var;
                    inputHandleArray[i - 1] = tcClient.CreateVariableHandle(arrBoolInPaths[i - 1]);
                    using (MD5 md5Hash = MD5.Create())
                    {
                        string hash = GetMd5Hash(md5Hash, var);
                        Console.WriteLine("Input {0} : {1} - Hash: {2}", i, var, hash);
                    }
                }

                //get the output paths

                hBoolOutPathCtr = tcClient.CreateVariableHandle("SymbolPathStorage.uiBoolOutPathCtr");
                AdsStream streamBoolOutPathCtr = new AdsStream(2);
                AdsBinaryReader readerBoolOutPathCtr = new AdsBinaryReader(streamBoolOutPathCtr);
                tcClient.Read(hBoolOutPathCtr, streamBoolOutPathCtr);
                iBoolOutPathCtr = readerBoolOutPathCtr.ReadInt16();
                arrBoolOutPaths = new string[iBoolOutPathCtr - 1];
                Console.WriteLine("Output variables nr: {0}", IBoolOutPathCtr);

                hBoolOutPaths = tcClient.CreateVariableHandle("SymbolPathStorage.aBoolOutPaths");

                AdsStream streamBoolOutPaths = new AdsStream((IBoolOutPathCtr - 1) * 256);
                BinaryReader readerBoolOutPaths = new BinaryReader(streamBoolOutPaths);

                tcClient.Read(hBoolOutPaths, streamBoolOutPaths);
                outputHandleArray = new int[IBoolOutPathCtr-1];
                for (int i = 1; i < IBoolOutPathCtr; i++)
                {
                    byte[] buffer = readerBoolOutPaths.ReadBytes(256);
                    string var = Encoding.UTF8.GetString(buffer, 0, buffer.Length);

                    int index = var.IndexOf('\0');
                    var = var.Remove(index);
                    arrBoolOutPaths[i - 1] = var;
                    outputHandleArray[i - 1] = tcClient.CreateVariableHandle(arrBoolOutPaths[i - 1]);
                    using (MD5 md5Hash = MD5.Create())
                    {
                        string hash = GetMd5Hash(md5Hash, var);
                        Console.WriteLine("Output {0} : {1} - Hash: {2}", i, var, hash);
                    }
                }
                
            }
            catch (Exception err)
            {
                Console.WriteLine("Error while retrieving handle.");
            }
        }
    }
}
